Rozdział monografii: `Bazy Danych: Struktury, Algorytmy, Metody

Transkrypt

Rozdział monografii: `Bazy Danych: Struktury, Algorytmy, Metody
Rozdział monografii: 'Bazy Danych: Struktury, Algorytmy, Metody', Kozielski S., Małysiak B., Kasprowski P., Mrozek D. (red.), WKŁ 2006
Rozdział 24
w
Złączenia zewnętrzne i hierarchiczne
w bazach danych
w
w
1 Wstęp
da
.b
Streszczenie. W rozdziale pokazano problemy związane ze stosowaniem złączeń (ang. joins) tabel w modelu relacyjnym. Pozornie proste złączenia (należące do kanonu pracy z modelem relacyjnym) będące fragmentem zapytania SQL mogą być źródłem istotnych błędów. Zasygnalizowane problemy
zilustrowano przykładami zapytań budowanych w wybranym komercyjnym
systemie bazodanowym (Oracle), który dopuszcza stosowanie zarówno „firmowej” składni (nie w pełni zgodnej z normą ANSI) jak i składni zdefiniowanej w normie ANSI. W rozdziale omówiono tylko problemy dotyczące
złączeń zewnętrznych (ang. Outer Joins) oraz tzw. złączeń hierarchicznych
(ang. Self Joins) Drugi podstawowy typ złączeń (złączenia wewnętrzne; ang.
Inner Joins) omówiono w rozdziale „Złączenia wewnętrzne w bazach danych”.
pl
s.
Niniejszy rozdział jest kontynuacją rozdziału „Złączenia wewnętrzne w bazach danych”.
Nie powtarzamy więc w tym miejscu podanych tam informacji ogólnych – patrz podrozdział drugi („Model danych”) oraz trzeci („Złączenia w systemie Oracle”).
2 Złączenia zewnętrzne
Problemy omawiane w niniejszym rozdziale dotyczą najogólniej mówiąc sytuacji, gdy
wiele wierszy będących de facto oczekiwanym wynikiem zapytania, nie zostanie (na przykład) wyświetlonych z powodu błędów logicznych w wykonywanym zapytaniu. Analizator
składni SQL nie zasygnalizuje błędu, gdyż jest ono z formalnego punktu widzenia poprawne. Wynik jednak będzie mimo wszystko błędny (analizator nie jest w stanie domyśleć się
Artur Gramacki, Jarosław Gramacki: Uniwersytet Zielonogórski, Instytut Informatyki i
Elektroniki, ul. Podgórna 50, 65-246, Zielona Góra
e-mail: {j.gramacki, A.Gramacki}@iie.uz.zgora.pl
(c) Copyright by Politechnika Śląska, Instytut Informatyki, Gliwice 2006
Rozdział monografii: 'Bazy Danych: Struktury, Algorytmy, Metody', Kozielski S., Małysiak B., Kasprowski P., Mrozek D. (red.), WKŁ 2006
A. Gramacki, J. Gramacki
w
intencji użytkownika). Z reguły sytuacja taka będzie spowodowana istnieniem pewnej
ilości kolumn typu foreign key dopuszczających wartości NULL. W efekcie pewna ilość
wierszy nie będzie spełniała warunków złączenia i nie pojawi się w finalnym wyniku. Błędy wynikające z istnienia w schemacie kolumn NULL są bardzo trudne do wykrycia, szczególnie gdy zapytanie zwraca dużą ilość danych i nie jest łatwo na pierwszy rzut oka zorientować się, że ilość ta jest niewłaściwa.
Do obsługi takich przypadków od dawna stosuje się tzw. złączenia zewnętrzne w kilku
wersjach: lewostronne, prawostronne, pełne (ang. Left Outer Join, Right Outer Join, Full
Outer Join). Należy podkreślić, ze system Oracle wraz z pojawieniem się wersji 9i oraz
10g nie wprowadza jeśli chodzi o obsługę takich złączeń żadnych merytorycznych zmian1.
Istotnie jednak zmienia (upraszcza) odpowiednią do obsługi takich przypadków składnię
SQL [4], [5].
Pierwszy przykład [1], [2], [3], [7] niech zilustruje wybrane aspekty tzw. prawostronnego złączenia zewnętrznego związane z możliwością wyboru składni SQL.
w
w
Przykład 1
Składnia ANSI
SELECT
NVL(D.department_name, '???')
Dept, SUM(E.salary) Sal
FROM
Departments D RIGHT OUTER JOIN
Employees E
ON D.department_id = E.department_id
GROUP BY D.department_name;
da
.b
Składnia dotychczasowa
SELECT
NVL(D.department_name, '???')
Dept, SUM(E.salary) Sal
FROM
Departments D, Employees E
WHERE
D.department_id(+) =
E.department_id
GROUP BY D.department_name;
pl
s.
Komentarz:
− intencją zapytania było podsumowanie kosztów płacowych całej firmy. Brak prawostronnego złączenia zewnętrznego NIE uwzględni wypłat pracowników, którzy NIE
są przypisani do jakiegokolwiek wydziału w strukturze firmy,
− w przypadku tradycyjnej składni kolejność kolumn w klauzuli WHERE jest nieistotna.
Wersja WHERE E.department_id = D.department_id(+) też będzie poprawna
(oczywiście znak (+) musi stać zawsze przy tej samej kolumnie). Niestety wersja
ANSI z warunkiem: Employees E RIGHT OUTER JOIN Departments D da już zupełnie inny wynik. Wynik będzie taki jak dla lewostronnego złączenia zewnętrznego
czyli tak, jakby napisano: Employees E LEFT OUTER JOIN Departments D,
− zamiana na: ON E.department_id = D.department_id nie wprowadza podobnego
niebezpieczeństwa,
− podsumowania (funkcja SUM wraz z klauzulą GROUP BY) mogą w niezamierzony sposób zamaskować zarówno błędy w złączeniach relacyjnych jak i inne „potknięcia”
programisty w klauzuli WHERE. W tym konkretnym przypadku błąd wynikający z braku (+) łatwo przeoczyć, gdyż liczba danych w tabelach jest stosunkowo duża i błędy
nie są na pierwszy rzut oka widoczne. Podsumowywać więc należy tylko wtedy, gdy
jesteśmy pewni, że podsumowujemy właściwe (kompletne) dane.
1
W dokumentacji wymienione są jednak pewne ograniczenia składni z (+), których nie ma w składni ANSI. Szczegóły patrz w [ORASQL].
238
(c) Copyright by Politechnika Śląska, Instytut Informatyki, Gliwice 2006
Rozdział monografii: 'Bazy Danych: Struktury, Algorytmy, Metody', Kozielski S., Małysiak B., Kasprowski P., Mrozek D. (red.), WKŁ 2006
Złączenia zewnętrzne i hierarchiczne w bazach danych
Inny charakter ew. błędów pokazuje przykład z lewostronnym złączeniem zewnętrznym.
W tym przypadku chodzi jednak raczej o niepełne informacje dostarczane przez zapytanie
niż błędne dane.
Przykład 2a
w
Składnia dotychczasowa
SELECT
D.department_name Dept,
NVL(SUM(E.salary), 0) Sal
FROM
Departments D, Employees E
WHERE
D.department_id =
E.department_id(+)
GROUP BY D.department_name;
w
Składnia ANSI
SELECT
D.department_name Dept,
NVL(SUM(E.salary), 0) Sal
FROM
Departments D LEFT OUTER JOIN
Employees E
ON D.department_id = E.department_id
GROUP BY D.department_name;
Przykład 2b
da
.b
w
Komentarz:
− intencją zapytania było wyświetlenie kosztów płacowych wszystkich wydziałów.
Brak lewostronnego złączenia zewnętrznego NIE uwzględni na liście wydziałów,
w których nikt nie jest formalnie zatrudniony,
− w przeciwieństwie do poprzedniego przykładu, problem jest tutaj innej natury. Dane,
które otrzymamy, są co prawda poprawne (suma zgadza się), jednak błędny jest ich
charakter informacyjny („puste” wydziały nie będą widoczne),
− uwagi dotyczące kolejności argumentów operatora są takie same, jak w poprzednim
przykładzie.
Nie ma żadnych ograniczeń na ilość zewnętrznie łączonych kolumn z użyciem ON.
Składnia ANSI
SELECT
S.discipline,
S.season_ID,
SE.employee_ID
FROM
Sports S LEFT OUTER JOIN
Sport_enrollment SE
ON S.discipline = SE.discipline AND
S.season_id = SE.season_id;
pl
s.
Składnia dotychczasowa
SELECT
S.discipline,
S.season_ID,
SE.employee_ID
FROM
Sports S, Sport_enrollment SE
WHERE
S.discipline = SE.discipline(+)
AND
S.season_id = SE.season_id(+);
Komentarz:
− w wyniku wykonania zapytania pojawi się lista sekcji sportowych zarówno obsadzonych przez chociaż jednego pracownika, jak i tych, do których nie zapisał się żaden
pracownik,
− tabela Sport_enrollment powinna zawierać podwójny klucz obcy:
ALTER TABLE Sport_enrollment ADD CONSTRAINT xxx_FK FOREIGN KEY
(discipline, season_ID) REFERENCES Sports (discipline, season_ID);
Jak zostało jednak wspomniane w rozdziale „Złączenie wewnętrzne w bazach danych”, nie został on w rzeczywistości utworzony lub jest nieaktywny (być może
z powodu przeoczenia projektanta).
Składnia ANSI istotnie upraszcza zapytania wymagające obustronnego złączenia zewnętrznego. W dotychczasowej składni, wydające się intuicyjnie poprawne użycie dwa ra-
239
(c) Copyright by Politechnika Śląska, Instytut Informatyki, Gliwice 2006
Rozdział monografii: 'Bazy Danych: Struktury, Algorytmy, Metody', Kozielski S., Małysiak B., Kasprowski P., Mrozek D. (red.), WKŁ 2006
A. Gramacki, J. Gramacki
zy operatora (+) (D.department_id(+)
wanie informacji o błędzie.
= E.department_id(+)),
spowoduje wygenero-
Przykład 3a
w
Składnia dotychczasowa
SELECT --lewostronne
D.department_name Dept,
NVL(SUM(E.salary), 0) Sal
FROM
Departments D, Employees E
WHERE
D.department_id = E.department_id(+)
GROUP BY
D.department_name
UNION
SELECT --prawostronne
NVL(D.department_name, '???') Dept,
SUM(E.salary)
FROM
Departments D, Employees E
WHERE
D.department_id(+) = E.department_id
GROUP BY D.department_name;
w
w
Składnia ANSI
SELECT
NVL(D.department_name, '???')
Dept, NVL(SUM(E.salary), 0) Sal
FROM
Departments D
FULL OUTER JOIN Employees E
ON D.department_id =
E.department_id
GROUP BY D.department_name;
da
.b
Komentarz:
− powyższy przykład jest jakby złączeniem poprzednich zapytań ilustrujących lewo
i prawostronne złączenia zewnętrzne,
− w przeciwieństwie do poprzednich przykładów, nowa składnia ANSI istotnie upraszcza zapis,
− obie wersje nieco inaczej sortują wyniki. W przypadku składni dotychczasowej zarobki pracowników, którzy nie są przypisani do żadnego działu (w naszym przypadku chodzi o wiersz "??? 18000") pojawiają się na początku. Dla składni ANSI pojawiają się one na końcu,
− w przypadku tradycyjnej składni mamy możliwość użycia UNION lub UNION ALL.
Nowa składnia NIE daje możliwości takiego wyboru. W wersji z UNION ALL otrzymamy co prawda wiele duplikatów, jednak zapytanie zostanie wykonane dużo bardziej efektywnie niż wersja z UNION, gdyż nie wykonuje się kosztowne SORT UNIQUE. Duplikaty można następnie próbować usunąć „lokalnie”, np. w aplikacji raportującej,
− uwaga! zgodnie z teorią relacyjną wymienność UNION i UNION ALL jest możliwa tylko
wtedy, gdy UNION-owane zbiory nie mają wspólnych wierszy. W naszym przypadku
warunek ten NIE ma miejsca, jednak zgodnie z poprzednią uwagą „zgodziliśmy” się
na duplikaty. Warunek taki spełnia na przykład poniższe zapytanie. Tu bezwzględnie
preferowane będzie użycie UNION ALL:
pl
s.
SELECT
department_name, department_ID FROM Departments
WHERE
department_ID > 100
UNION ALL
SELECT
department_name, department_ID FROM Departments
WHERE
department_ID <= 100
ORDER BY 1;
240
(c) Copyright by Politechnika Śląska, Instytut Informatyki, Gliwice 2006
Rozdział monografii: 'Bazy Danych: Struktury, Algorytmy, Metody', Kozielski S., Małysiak B., Kasprowski P., Mrozek D. (red.), WKŁ 2006
Złączenia zewnętrzne i hierarchiczne w bazach danych
W niektórych szczególnych przypadkach użycie składni ANSI upraszczającej obustronne złączenia zewnętrzne, może „zemścić się” pewnym subtelnym błędem.
Przykład 3b
w
Składnia dotychczasowa
SELECT
S.discipline, S.season_ID,
SE.employee_ID
FROM
Sports S, Sport_enrollment SE
WHERE
S.discipline = SE.discipline(+)
AND
S.season_id = SE.season_id(+)
w
UNION
SELECT
discipline,
season_ID,
employee_ID
FROM
Sports S FULL OUTER JOIN
Sport_enrollment SE
USING (discipline, season_id)
-- z ON
-- wynik "POPRAWNY"
SELECT
S.discipline,
S.season_ID,
SE.employee_ID
FROM
Sports S FULL OUTER JOIN
Sport_enrollment SE
ON S.discipline = SE.discipline AND
S.season_id = SE.season_id;
da
.b
w
SELECT
S.discipline, S.season_ID,
SE.employee_ID
FROM
Sports S, Sport_enrollment SE
WHERE
S.discipline(+) = SE.discipline
AND
S.season_id(+) = SE.season_id;
Składnia ANSI
-- z USING
-- wynik "BŁĘDNY"
pl
s.
Komentarz:
− jak widać (patrz niżej) pracownik o numerze 105 zapisał się „z wyprzedzeniem” do
sekcji tenisa na sezon summer06. W tym sezonie tenis nie jest jednak (jeszcze) oferowany. Było to możliwe, gdyż nie istnieje odpowiedni klucz obcy lub jest on nieaktywny (była o tym mowa w rozdziale „Złączenia wewnętrzne w bazach danych”),
− wiele ofert uprawiania sportu nie zostało jeszcze wybranych. Aby pokazać je na wydruku zastosowano zwykłe pełne złączenie zewnętrzne FULL OUTER JOIN,
− w wersji zapytania z USING konsekwencją użycia klauzuli USING jest w pewnym sensie „błędny” wynik. Jest on oczywiście zgodny z wpisanymi do tabel danymi, jednak
niepoprawny merytorycznie, gdyż na jego podstawie otrzymamy mylące informacje.
Dla wiersza „tennis summer06 105” nie ma odpowiedniego wiersza w tabeli Sports. Warto również zauważyć, że nie mamy tu do czynienia z wartościami NULL w kolumnach. W klauzuli USING podano jedynie podług których kolumn ma odbywać się
złączenie,
SELECT
discipline, season_ID, employee_ID
FROM
Sports S FULL OUTER JOIN Sport_enrollment SE
USING (discipline, season_id);
DISCIPLINE
-------------------hockey
tennis
volleyball
volleyball
tennis
SEASON_ID EMPLOYEE_ID
--------- ----------spring05
100
summer05
200
spring05
winter05
spring05
241
(c) Copyright by Politechnika Śląska, Instytut Informatyki, Gliwice 2006
Rozdział monografii: 'Bazy Danych: Struktury, Algorytmy, Metody', Kozielski S., Małysiak B., Kasprowski P., Mrozek D. (red.), WKŁ 2006
A. Gramacki, J. Gramacki
tennis
hockey
football
football
volleyball
tennis
spring06
autumn05
spring05
winter05
summer05
summer06
105
w
− w wersji zapytania z ON otrzymujemy wynik „poprawny”. Tu z kolei nie jest on
zgodny z wpisanymi w bazie danymi, jednak jest poprawny merytorycznie (nie można zapisać się do nie oferowanej w danym sezonie sekcji),
w
SELECT
S.discipline, S.season_ID, SE.employee_ID
FROM
Sports S FULL OUTER JOIN Sport_enrollment SE
ON
S.discipline = SE.discipline AND
S.season_id = SE.season_id;
SEASON_ID EMPLOYEE_ID
--------- ----------spring05
100
summer05
200
winter05
spring05
spring06
spring05
winter05
summer05
autumn05
spring05
105
da
.b
w
DISCIPLINE
-------------------hockey
tennis
volleyball
tennis
tennis
football
football
volleyball
hockey
volleyball
− w tradycyjnej składni opisane wyżej podobne „dylematy” nie mają miejsca. Zawsze
otrzymujemy wynik jak dla wersji z ON.
3 Złączenia hierarchiczne
pl
s.
Pierwszymi komercyjnie stosowanymi bazami danych były bazy hierarchiczne. Miały one
strukturę drzewiastą i dlatego do manipulacji zgromadzonymi w nich danymi używano
często algorytmów o charakterze rekurencyjnym (algorytmy tego typu to kanon przedmiotów algorytmicznych; patrz przykładowo [6]). Szybko jednak bazy hierarchiczne wyparte
zostały przez bazy relacyjne, jako bardziej uniwersalne i łatwiejsze w eksploatacji.
Otaczający nas świat ma jednak często naturalną strukturę hierarchiczną (państwo →
województwa → powiat → gmina → wieś, samochód → silnik → wał → zawory →
uszczelki, prezes → dyrektorzy → szefowie działów). Przykłady takie można mnożyć.
Związki hierarchiczne w bazach relacyjnych modelować można na dwa sposoby:
1) o ustalonej liczbie poziomów hierarchii
2) o dowolnej jej liczbie.
Pierwsze podejście wymaga „usztywnienia” na etapie projektu struktury tabel typu master-detail. Drugie korzysta z możliwości tworzenia powiązań miedzy kolumnami w ramach jednej tabeli. W pracy skupiono się wyłącznie na drugim sposobie implementacji hierarchii, gdyż nie wymaga on założeń co do maksymalnej ilości poziomów w hierarchii.
Bazy relacyjne nie obsługują bezpośrednio takich hierarchicznych modeli z uwagi na
pryncypialne założenia podejścia relacyjnego. Wiele systemów baz danych nie obsługuje
ponadto na poziomie języka SQL obsługi zamodelowanych związków hierarchicznych, co
242
(c) Copyright by Politechnika Śląska, Instytut Informatyki, Gliwice 2006
Rozdział monografii: 'Bazy Danych: Struktury, Algorytmy, Metody', Kozielski S., Małysiak B., Kasprowski P., Mrozek D. (red.), WKŁ 2006
Złączenia zewnętrzne i hierarchiczne w bazach danych
w
wiąże się z brakiem implementacji rekurencji w tym języku zapytań. Oczywiście odpowiedni algorytm rekurencyjny można zaimplementować samodzielnie, (gdy system dostarcza stosownych do tego narzędzi programistycznych, np. procedury składowane), jednak
nie wydaje się, aby w praktyce często tak postępowano.
Tak więc, wygodna na etapie modelowania relacja wewnętrzna: Employees.employee_ID → Employees.manager_ID, zastosowana w modelu danych, jest w wielu
systemach baz danych trudna do późniejszego wykorzystania.
W dalszej części pracy pokazano, że jakkolwiek system Oracle dostarcza mechanizmy
obsługi zapytań hierarchicznych, to w praktyce mogą sprawiać one trudności oraz być podatne na błędy.
Najprostszym przykładem jest proste rekurencyjne złączenie w obrębie jednaj tabeli
(ang. recursive self-join). Praktyczna użyteczność tego rozwiązania ograniczona jest do
przypadków gdzie NIE interesuje nas odzwierciedlenie hierarchii zależności pomiędzy poszczególnymi wierszami.
w
w
Przykład 4
SELECT employee_ID, last_name, job_ID, manager_id, department_id
FROM Employees;
EMPLOYEE_ID
----------100
101
102
...
114
115
116
117
...
da
.b
-- Zawartości tabeli Employees (wybrane wiersze)
-- Zwróćmy uwagę, że pracownik o numerze 114 ma wpisaną wartość NULL
-- w polu DEPARTMENT_ID
LAST_NAME
------------------------King
Kochhar
De Haan
JOB_ID
MANAGER_ID DEPARTMENT_ID
---------- ---------- ------------AD_PRES
90
AD_VP
100
90
AD_VP
100
90
Raphaely
Khoo
Baida
Tobias
PU_MAN
PU_CLERK
PU_CLERK
PU_CLERK
100
114
114
114
NULL
30
30
30
107 rows selected
pl
s.
-- Składnia tradycyjna
-- Dobrze
SELECT
E1.employee_ID,
E1.last_name||'''s boss: '||E2.last_name "BOSS NAME",
E1.manager_ID
FROM
Employees E1, Employees E2
WHERE
E2.employee_ID = E1.manager_ID AND E1.last_name LIKE 'K%';
EMPLOYEE_ID
----------122
115
156
101
173
BOSS NAME
MANAGER_ID
-------------------------------------- ---------Kaufling's boss: King
100
Khoo's boss: Raphaely
114
King's boss: Partners
146
Kochhar's boss: King
100
Kumar's boss: Cambrault
148
-- Składnia tradycyjna
-- Źle
243
(c) Copyright by Politechnika Śląska, Instytut Informatyki, Gliwice 2006
Rozdział monografii: 'Bazy Danych: Struktury, Algorytmy, Metody', Kozielski S., Małysiak B., Kasprowski P., Mrozek D. (red.), WKŁ 2006
A. Gramacki, J. Gramacki
SELECT
E1.employee_ID,
E1.last_name||'''s boss: '||E2.last_name "BOSS NAME",
E2.employee_ID
FROM
Employees E1, Employees E2
WHERE
E1.employee_ID = E2.manager_ID AND E1.last_name LIKE 'K%';
w
w
EMPLOYEE_ID BOSS NAME
EMPLOYEE_ID
----------- -------------------------------------- ----------122 Kaufling's boss: Mallin
133
122 Kaufling's boss: Rogers
134
122 Kaufling's boss: Gee
135
122 Kaufling's boss: Philtanker
136
122 Kaufling's boss: Chung
188
122 Kaufling's boss: Dilly
189
122 Kaufling's boss: Gates
190
....
27 rows selected
w
da
.b
Komentarz:
− użyto dwóch aliasów tej samej tabeli – jest to obowiązkowe. Bardzo częsty błąd polega na późniejszym niewłaściwym użyciu tych aliasów. Porównaj wyniki obu zapytań.
− możliwych błędów, będących efektem zamiany aliasów, jest więcej. Przykładowo
kolejny, inny (jednak ciągle błędny) wynik otrzymamy, gdy ostatnia linia pierwszej
wersji zapytania wyglądać będzie tak: WHERE E2.employee_ID = E1.manager_ID
AND E2.last_name LIKE 'K%'
pl
s.
− użycie składni ANSI w przypadku takich złączeń jest dosyć dyskusyjne. Wersja
z USING, nie działa niestety poprawnie. Jedyną wersją zwracającą poprawne wyniki
jest wersja z użyciem ON. Jak już jednak pokazano na wcześniejszych przykładach,
użycie ON jedynie rozdziela warunki złączenia od innych warunków (np. zawężających) wyniki zapytania i w żadnym razie nie eliminuje wcześniej opisywanych błędów,
− złączenia rekurencyjne w ramach jednej tabeli, jakkolwiek eleganckie, są podatne na
błędy w zapytaniach.
-- Źle
SELECT
employee_ID,
E1.last_name||'''s boss: '||E2.last_name "BOSS NAME",
manager_ID
FROM
Employees E1 INNER JOIN Employees E2
USING (employee_ID, manager_ID)
WHERE
E1.last_name LIKE 'K%';
-- Wynik
101
Kochhar's boss: Kochhar
115
Khoo's boss: Khoo
122
Kaufling's boss: Kaufling
156
King's boss: King
173
Kumar's boss: Kumar
100
114
100
146
148
244
(c) Copyright by Politechnika Śląska, Instytut Informatyki, Gliwice 2006
Rozdział monografii: 'Bazy Danych: Struktury, Algorytmy, Metody', Kozielski S., Małysiak B., Kasprowski P., Mrozek D. (red.), WKŁ 2006
Złączenia zewnętrzne i hierarchiczne w bazach danych
-- Dobrze
w
SELECT
E1.employee_ID,
E1.last_name||'''s boss: '||E2.last_name Kto_pod_kim,
E1.manager_ID
FROM
Employees E1 INNER JOIN Employees E2
ON (E2.employee_ID = E1.manager_ID)
WHERE
E1.last_name LIKE 'K%';
w
-- Wynik
122
Kaufling's boss: King
101
Kochhar's boss: King
115
Khoo's boss: Raphaely
156
King's boss: Partners
173
Kumar's boss: Cambrault
100
100
114
146
148
Przykład 5a
da
.b
w
Poprzedni przykład nie odzwierciedlał pełnej hierarchii zależności pomiędzy wierszami.
Z uwagi na fakt, że zachowanie hierarchii w pełnej formie jest bardzo istotne, w systemie
Oracle wprowadzono klauzulę CONNECT BY, która z formalnego punktu widzenia jest niczym innym niż szczególną specyfikacją sposobu wykonania danego złączenia. Przy stosowaniu nowej klauzuli łatwo jednak o pomyłkę.
0 rows selected
pl
s.
SELECT
E.employee_ID,
E.manager_ID,
RPAD( RPAD(' ' , 2*(LEVEL)-1, '.')||E.first_name ||' '||E.last_name, 30)
NAME,
D.department_name,
D.manager_ID
FROM
Departments D, Employees E
WHERE
D.department_ID = E.department_ID
START WITH
E.employee_ID = 114
CONNECT BY
E.manager_ID = PRIOR E.employee_ID;
Komentarz:
− w wyniku wykonania zapytania nie zostanie zwrócony żaden wiersz. Powód – pracownik o numerze 114 NIE jest przypisany do żadnego wydziału.
− pierwszorzędne znaczenie ma tu kolejność wykonywanych przez Oracle czynności.
Kolejność jest następująca:
− złączenie jest materializowane co oznacza, że w pierwszej kolejności brane są
pod uwagę warunki złączenia.
− do zwróconych w poprzednim kroku wierszy stosowana jest klauzula CONNECT
BY.
− do zwróconych w poprzednim kroku wierszy stosowana jest klauzula WHERE
z ew. innymi ograniczeniami.
− zawsze należy więc sprawdzać, czy warunek złączenia nie „wyfiltrował” nam koniecznych w zapytaniu hierarchicznym danych (tzw. korzenia, od którego pobierane
245
(c) Copyright by Politechnika Śląska, Instytut Informatyki, Gliwice 2006
Rozdział monografii: 'Bazy Danych: Struktury, Algorytmy, Metody', Kozielski S., Małysiak B., Kasprowski P., Mrozek D. (red.), WKŁ 2006
A. Gramacki, J. Gramacki
są dane w hierarchii zlokalizowane poniżej). W naszym przypadku „zniknął” pracownik o numerze 114 i w konsekwencji wszyscy jego podwładni.
− problem eliminuje zamiana wiersza WHERE D.department_ID = E.department_ID
na WHERE D.department_ID(+) = E.department_ID
-- Poprawny wynik zapytania
-- lewostronne złączenie zewnętrzne
w
w
EMPLOYEE_ID MANAGER_ID NAME
DEPARTMENT_NAME MANAGER_ID
----------- ---------- ------------------- --------------- ---------114
100 Den Raphaely
119
114 ..Karen Colmenares Purchasing
114
118
114 ..Guy Himuro
Purchasing
114
117
114 ..Sigal Tobias
Purchasing
114
116
114 ..Shelli Baida
Purchasing
114
115
114 ..Alexander Khoo
Purchasing
114
W różnych wersjach systemów otrzymywać możemy różnie posortowane wyniki zapytań hierarchicznych.
w
Przykład 5b
-- Składnia dotychczasowa, Oracle 9i
da
.b
SELECT
E.employee_ID, E.manager_ID, RPAD(
RPAD(' ' , 2*(LEVEL)-1, '.')||E.first_name ||' '||E.last_name, 30) NAME,
D.department_name, D.manager_ID
FROM
Departments D, Employees E
WHERE
D.department_ID(+) = E.department_ID AND D.manager_ID > 200
START WITH E.employee_ID = 100
CONNECT BY E.manager_ID = PRIOR E.employee_ID;
EMPLOYEE_ID MANAGER_ID NAME
DEPARTMENT_NAME
MANAGER_ID
----------- ---------- --------------------- ----------------- ---------204
101 ....Hermann Baer
Public Relations
204
205
101 ....Shelley Higgins Accounting
205
206
205 ......William Gietz Accounting
205
203
101 ....Susan Mavris
Human Resources
203
201
100 ..Michael Hartstein Marketing
201
202
201 ....Pat Fay
Marketing
201
pl
s.
-- Składnia ANSI, Oracle 9i
SELECT
E.employee_ID, E.manager_ID, RPAD(
RPAD(' ' , 2*(LEVEL)-1, '.')||E.first_name ||' '||E.last_name, 30) NAME,
D.department_name, D.manager_ID
FROM
Departments D RIGHT OUTER JOIN Employees E
ON D.department_ID = E.department_ID
WHERE
D.manager_ID > 200
START WITH E.employee_ID = 100
CONNECT BY E.manager_ID = PRIOR E.employee_ID;
EMPLOYEE_ID MANAGER_ID NAME
DEPARTMENT_NAME
MANAGER_ID
----------- ---------- ---------------------- ------------------ ----------203
101 ....Susan Mavris
Human Resources
203
204
101 ....Hermann Baer
Public Relations
204
205
101 ....Shelley Higgins
Accounting
205
206
205 ......William Gietz
Accounting
205
201
100 ..Michael Hartstein
Marketing
201
202
201 ....Pat Fay
Marketing
201
246
(c) Copyright by Politechnika Śląska, Instytut Informatyki, Gliwice 2006
Rozdział monografii: 'Bazy Danych: Struktury, Algorytmy, Metody', Kozielski S., Małysiak B., Kasprowski P., Mrozek D. (red.), WKŁ 2006
Złączenia zewnętrzne i hierarchiczne w bazach danych
Komentarz:
− niestety analogiczne jak wydawałoby się zapytanie w notacji ANSI, zwraca wynik
inaczej posortowany. Na szczęście hierarchia jest zachowana poprawnie,
− w bazie 10g oba sortowania są takie same (jak dla ANSI w 9i).
Klauzula WHERE może zawierać zarówno warunki złączenia (tu: D.department_ID(+) =
E.department_ID) jak i inne ograniczenia (tu: D.manager_ID > 110) . W takim wypadku
z ostrożnością należy podchodzić do kwestii użycia składni ANSI w danej wersji systemu !
w
Przykład 5c
-- Składnia dotychczasowa, Oracle 9i
da
.b
w
w
SELECT
E.employee_ID,
E.manager_ID,
RPAD(RPAD(' ' , 2*(LEVEL)-1, '.')||E.first_name ||' '||E.last_name, 30)
NAME,
D.department_name,
D.manager_ID
FROM
Departments D, Employees E
WHERE
D.department_ID(+) = E.department_ID
AND D.manager_ID > 110
START WITH
E.employee_ID = 114
CONNECT BY
E.manager_ID = PRIOR E.employee_ID;
EMPLOYEE_ID MANAGER_ID NAME
DEPARTMENT_NAME
MANAGER_ID
----------- ---------- ------------------ ----------------- ---------115
114 ..Alexander Khoo Purchasing
114
116
114 ..Shelli Baida
Purchasing
114
118
114 ..Guy Himuro
Purchasing
114
-- Składnia ANSI, Oracle 9i
0 rows returned
pl
s.
SELECT
E.employee_ID,
E.manager_ID,
RPAD(RPAD(' ' , 2*(LEVEL)-1, '.')||E.first_name ||' '||E.last_name, 30)
NAME,
D.department_name, D.manager_ID
FROM
Departments D RIGHT OUTER JOIN Employees E
ON D.department_ID = E.department_ID
WHERE
D.manager_ID > 110
START WITH E.employee_ID = 114
CONNECT BY E.manager_ID = PRIOR E.employee_ID;
Komentarz:
− zapytanie wykonywano w systemie Oracle 9i (ciągle w powszechnym użyciu). Oba
zapytania są merytorycznie poprawne,
− co do zasady (patrz komentarz do Przykładu 5a) warunki z klauzuli WHERE powinny
być uwzględnione na końcu zapytania, czyli po CONNECT BY. W wersji ANSI
uwzględniane są jednak PRZED tą klauzulą co powoduje, że nie odnajdujemy ko247
(c) Copyright by Politechnika Śląska, Instytut Informatyki, Gliwice 2006
Rozdział monografii: 'Bazy Danych: Struktury, Algorytmy, Metody', Kozielski S., Małysiak B., Kasprowski P., Mrozek D. (red.), WKŁ 2006
A. Gramacki, J. Gramacki
rzenia hierarchii (zostaje on wcześniej wyfiltrowany). W efekcie lista pracowników
jest pusta,
− opisany błąd nie występuje w wersji 10g bazy.
4 Wnioski
w
Literatura
1.
2.
3.
4.
5.
6.
7.
da
.b
w
w
W artykule pokazano, że z pozoru proste zagadnienia jakim są złączenia relacyjne, po
uwzględnieniu wielu zagadnień szczegółowych, stają się podatne na błędy. Jeśli dodatkowo uwzględni się możliwość korzystania z nowej składni ANSI, ewentualnych problemów
przybędzie. Składnia ANSI SQL nie zawsze jest w 100% odpowiednikiem składni dotychczasowej. Warto o tym zawsze pamiętać.
Dużo miejsca poświęcono zagadnieniu istnienia w tabelach kolumn NULL. Pokazano, że
jest to często bardzo niebezpieczne. Łatwo wyobrazić sobie sytuację, gdy z „dobrodziejstwa” nie wpisania wartości do kolumny skorzystamy długo po napisaniu np. aplikacji raportującej. Jeśli na etapie jej powstawania nie uwzględnimy tego faktu, to któregoś dnia
raport po prostu wyprodukuje błędne (mylące) wyniki. Innymi słowy dopiero po jakimś
czasie, na skutek modyfikacji danych, z których korzysta zapytanie, pojawi się błąd.
pl
s.
Oracle® Database, Sample Schemas, 10g Release 1 (10.1), Part No. B10771-01
Gennick J.: An Incremental Approach to Developing SQL Queries. Oracle Magazine,
July/August 2000, Volume XIV, Issue 4
Czuprynski J.: Getting ANSI About Joins, dostępne na otn.oracle.com
Oracle® Database SQL Reference 10g Release 1 (10.1) Part Number B10759-01
Oracle® Database SQL Reference 9i Release 2 (9.2) Part Number A96540-02
Drozdek A., Simon D. L.: Struktury danych w języku C, WNT Warszawa 1996
Gramacki J., Gramacki A.: Połączenia - pułapki i nowe możliwości, Systemy informatyczne.
Projektowanie, implementowanie, eksploatowanie - PLOUG : X Konferencja użytkowników
i deweloperów ORACLE. Kościelisko, Polska, 2004, Warszawa, Stowarzyszenie Polskiej Grupy
Użytkowników Systemu Oracle, 2004, strony 259-277
248
(c) Copyright by Politechnika Śląska, Instytut Informatyki, Gliwice 2006

Podobne dokumenty