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