Złączenia i operatory zbiorowe
Dane, które chcemy uzyskać z bazy danych zazwyczaj znajdują się w więcej niż jednej tabeli bazodanowej. W takim przypadku nasze zapytanie musi się odpowiednio odwołać do więcej niż jednej tabeli. Mechanizm ten nazywa się w SQL złączeniami.
Złączenie kartezjańskie
Aby zapytanie czerpało dane z kilku tabel, należy po klauzuli FROM zamiast jednej tebeli wymienić wszystkie wymagane, rozdzielając je przecinkami.
Przykład
Wypisać adres klientów i opis zamówienia.
SELECT klt_adres, zam_opis
FROM klienci, zamowienia;
Zapytanie takie zwróci iloczyn kartezjański wierszy z obu tabel.
Złączenia kartezjańskie stosuje się bardzo rzadko, gdyż produkują bardzo dużą ilość wierszy, które nie zawierają logicznie spójnych informacji.
Jeżeli w zapytaniu chcemy odwołać się do kolumny, która występuje w kilku tabelach, to, aby uniknąć niejednoznaczności, odwołanie to musi zawierać nazwę kolumny poprzedzoną nazwą tabeli.
Przykład
Wypisać adres klientów i opis zamówienia.
SELECT klienci.klt_adres,
zamowienia.zam_opis
FROM klienci, zamowienia;
Stosowanie pełnej nazwy kolumn (wraz z nazwą tabeli) w przypadku pisania zapytań do kilku tabel należy do dobrego zwyczaju, nawet gdy nie jest to konieczne ze względu na niejednoznaczność nazw. Zapytanie staje się dzięki temu czytelniejsze.
Złączenia wewnętrzne
Złączenia kartezjańskie stosuje się niezwykle rzadko. Najczęściej, kiedy zadajemy zapytanie do dwu tabel, zależy nam na uzyskaniu tylko takich kombinacji wierszy z obu tabel, które sobie w jakiś sposób odpowiadają, np. chcemy klientów i tylko zamówienia przez nich złożone.
Złączenia, które zwracają właśnie taki zbiór wierszy z obu tabel, które sobie w jakimś stopniu odpowiadają nazywamy złączeniami wewnętrznym (inner join).
Aby zrealizować złączenie wewnętrzne w Oracle SQL należy w klauzuli WHERE dodać tzw. warunek złączenia, czyli warunek, w którym określamy jakie wiersze z obu tabel odpowiadają sobie nawzajem.
Przykład
Wypisać nazwisko i nazwę klientów oraz opisy zamówień jakie złożyli.
SELECT klienci.klt_nazwisko, klienci.klt_nazwa, zamowienia.zam_opis
FROM klienci, zamowienia
WHERE klienci.klt_id = zamowienia.zam_klt_id;
Zamiast odwoływać się do kolumn poprzez pełne nazwy tabel można tabelom, podobnie jak kolumnom, nadać aliasy i odwoływać się do kolumn poprzez te aliasy.
Przykład
Wypisać e-maile klientów oraz opisy i daty złożonych przez nich zamówień.
SELECT K.klt_email,
Z.zam_opis,
Z.zam_data_zamowienia
FROM klienci K, zamowienia Z
WHERE K.klt_id = Z.zam_klt_id;
Jeżeli nadamy tabeli alias, to nie można się już będzie do niej odwoływać przez nazwę.
W złączeniu mogą brać udział więcej niż 2 tabele. W takim przypadku, aby uniknąć złączenia kartezjańskiego należy wyspecyfikować więcej warunków łączących tabele.
Przykład
Wypisać wszystkie zamówienia (ich opis) oraz nazwy towarów z każdego zamówienia wraz z ilością sztuk każdego towaru w zamówieniu. Wynik uporządkować według opisu zamówienia.
SELECT Z.zam_opis, P.pzm_ilosc, T.tow_nazwa
FROM zamowienia Z, pozycje_zamowien P, towary T
WHERE Z.zam_id = P.pzm_zam_id
AND T.tow_id = P.pzm_tow_id
ORDER BY Z.zam_opis;
W przypadku łączenia N tabel powinniśmy wyspecyfikować N-1 złączeń pomiędzy tabelami.
W przypadku zapytań odnoszących się do wielu tabel, oprócz warunku złączenia w klauzuli WHERE mogą pojawić się także inne warunki wpływające na selekcję wierszy.
Przykład
Wypisać nazwy towarów (bez powtórzeń) które są zawarte w zamówieniach, które zostały złożone a jeszcze nie zrealizowane.
SELECT DISTINCT T.tow_nazwa
FROM zamowienia Z, pozycje_zamowien P, towary T
WHERE Z.zam_id = P.pzm_zam_id
AND T.tow_id = P.pzm_tow_id
AND Z.zam_data_realizacji IS NULL;
We wszystkich złączeniach można również dokonywać agregacji i grupowań.
Przykład
Wypisać imiona, nazwiska klientów indywidualnych, oraz ilość zamówień jakie złożyli w każdym roku kalendarzowym.
SELECT K.klt_imie, K.klt_nazwisko, count(*)
FROM zamowienia Z, klienci K
WHERE K.klt_id = Z.zam_klt_id
AND K.klt_typ = 'IND'
GROUP BY K.klt_imie, K.klt_nazwisko;
W złączeniach można również korzystać z klauzuli HAVING do wprowadzania warunków zawierających funkcje grupowe.
Przykład
Wypisać opisy zamówień ale tylko tych, które zawierają więcej niż jedną pozycję.
SELECT Z.zam_opis
FROM zamowienia Z, pozycje_zamowien P
WHERE Z.zam_id = P.pzm_zam_id
GROUP BY Z.zam_opis
HAVING count(*) > 1;
Warunek definiujący złączenie nie koniecznie musi się składać z jednego porównania. Może to być również warunek złożony z operatorów większości, zakresu, itp.
Przykład
Wypisać nazwy towarów, ich ceny oraz rabat jaki przysługiwałby przy zakupie 20 sztuk każdego towaru.
SELECT T.tow_nazwa, T.tow_cena, G.grc_rabat
FROM towary T, grupy_cen G
WHERE T.tow_cena*20 BETWEEN G.grc_cena_od AND G.grc_cena_do;
Złączenia zewnętrzne
W przypadku złączeń wewnętrznych, jeżeli w jednej z tabel istnieje rekord, który nie ma żadnego odpowiednika w drugiej tabeli, to rekord ten jest pomijany w końcowym wyniku.
Przykład
Wypisać nazwiska klientów indywidualnych oraz opisy zamówień przez nich złożonych.
SELECT K.klt_nazwisko,
Z.zam_opis
FROM klienci K, zamowienia Z
WHERE K.klt_id = Z.zam_klt_id
AND K.klt_typ = 'IND';
Czasami taka sytuacja nas nie zadawala. W takim wypadku możemy wyróżnić jedną z tabel i wykonać tzw. złączenie zewnętrzne (outer join), aby otrzymać z wyróżnionej tabeli wszystkie rekordy, nawet te, które nie mają swojego odpowiednika w drugiej z tabel.
Aby wykonać złączenie zewnętrzne musimy skorzystać z operatora złączenia zewnętrznego (+). Umieszczamy go w warunku złączenia po nazwie kolumny z tabeli która jest „uboższa”, czyli tam, gdzie „brakuje” rekordów.
Przykład
Wypisać nazwiska wszystkich klientów indywidualnych oraz opisy zamówień przez nich złożonych.
SELECT K.klt_nazwisko,
Z.zam_opis
FROM klienci K, zamowienia Z
WHERE K.klt_id = Z.zam_klt_id(+)
AND K.klt_typ = 'IND';
Nie jest obojętne przy której kolumnie postawimy operator złączenia zewnętrznego. W przypadku zamiany miejsc, zapytanie nabierze zupełnie innego kształtu.
Samozłączenia
Czasami zachodzi potrzeba odwołania się w jednym zapytaniu dwukrotnie do tej samej tabeli.
Przykład
Wypisać nazwy wszystkich towarów oraz nazwy towarów, których są składnikami
SELECT SK.tow_nazwa,
T.tow_nazwa
FROM towary SK, towary T
WHERE SK.tow_tow_id = T.tow_id;
W przypadku samozłączeń tę samą tebelę traktuje się jako dwie niezależne tabele. W związku z tym można traktować takie złączenie jak każde inne, stosując dowolne poznane konstrukcje.
Przykład
Wypisać nazwy i ceny wszystkich towarów ale pod warunkiem, że cały towar jest tańszy od sumy cen swoich składników. Wypisać również te sumy.
SELECT T.tow_nazwa,
T.tow_cena,
sum(SK.tow_cena)
FROM towary SK, towary T
WHERE SK.tow_tow_id = T.tow_id
GROUP BY T.tow_nazwa, T.tow_cena
HAVING T.tow_cena < sum(SK.tow_cena);
Operatory zbiorowe
Operatory zbiorowe służą do odpowiedniego łączenia kilku zbiorów wierszy wygenerowanych przez niezależne zapytania.
Operatory zbiorowe stosuje się w następującej składni:
SELECT ...
operator_zbiorowy
SELECT ...
W Oracle SQL istnieją cztery rodzaje operatorów zbiorowych:
UNION ALL-
Operator ten powoduje, że w wyniku zapytania otrzymujemy sumę wszystkich wierszy, które znajdowały się w obu zapytaniach składowych.
Przykład
Wypisać imiona i nazwiska klientów indywidualnych oraz nazwy i ceny towarów objętych podatkiem 7%.SELECT klt_imie, klt_nazwisko FROM klienci WHERE klt_typ = 'IND' UNION ALL SELECT tow_nazwa, to_char(tow_cena) FROM towary WHERE tow_podatek = 7; UNION-
Operator ten działa podobnie jak
UNION ALL, ale z wyniku końcowego eliminowane są wiersze, które powtarzają się w obu zbiorach składowych.Przykład
Wypisać zamówienia złożone w 2000 roku oraz zamówienia niezrealizowane do tej pory. Wyeliminować powtórzenia.SELECT * FROM zamowienia WHERE to_char(zam_data_zamowienia, 'yyyy') = '2000' UNION SELECT * FROM zamowienia WHERE zam_data_realizacji IS NULL; INTERSECT-
Operator ten powoduje, że w ostatecznym zbiorze wynikowym znajdują się tylko wiersze, które znajdowały się jednocześnie w obu zbiorach składowych (zbiór wspólny).
Przykład
Znaleźć nazwiska klientów, którzy nazywają się tak jak nazwa któregokolwiek towaru.SELECT tow_nazwa FROM towary INTERSECT SELECT klt_nazwisko FROM klienci; MINUS-
Operator ten powoduje wyselekcjonowanie wierszy, które znajdują się w zbiorze wierszy zwróconym przez pierwsze zapytanie, a nie znajdują się w zbiorze wierszy zwróconym przez drugie zapytanie.
Przykład
Znaleźć klientów, którzy nie złożyli ani jednego zrealizowanego zamówienia.SELECT * FROM klienci MINUS SELECT K.* FROM klienci K, zamowienia Z WHERE K.klt_id = Z.zam_klt_id AND Z.zam_data_realizacji IS NOT NULL;
Różnymi operatorami zbiorowymi można jednocześnie traktować więcej niż dwa zapytania.
Przykład
Wypisać klientów gospodarczych oraz tych, którzy nie złożyli ani jednego zrealizowanego zamówienia.
SELECT *
FROM klienci
WHERE klt_typ = 'GSP'
UNION
(SELECT *
FROM klienci
MINUS
SELECT K.*
FROM klienci K, zamowienia Z
WHERE K.klt_id = Z.zam_klt_id);
W przypadku stosowania operatorów zbiorowych należy pamiętać o następujących rzeczach:
- Wszystkie zapytania składowe muszą zwracać tę samą ilość kolumn
- Kolumny na tych samych pozycjach muszą być tego samego typu we wszystkich zapytaniach składowych
- Nazwy kolumn różnych zapytań składowych mogą być inne, natomiast nazwy kolumn zapytania końcowego pobierane są z pierwszego zapytania składowego
- Poszczególne zapytania składowe nie mogą być sortowane. Sortować można tylko wynik całego zapytania złożonego
