Wykład 5. Odczytywanie danych z wielu tabel.

Wykład piąty zawiera omówienie zagadnienie odczytywania danych z więcej niż jednej tabeli. Omówione zostanie zarówno odczytywanie danych z relacji utworzonych na kilku tabelach, jak też tworzenia wyników poprzez uzycie operatorów algebraicznych UNION, INTERSECT, EXCEPT.

Dotychczas wszystkie operacje wykonywane na danych dotyczyły zawsze tylko jednej tabeli. Odczytywaliśmy dane ze wszystkich, albo tylko z wybranych kolumn, z wszystkich wierszy, albo tylko z tych, dla których warunek zdefiniowany w klauzuli WHERE przyjmował wartość TRUE. Jednak, znając podstawy relacyjnego modelu danych wiemy, że będą one zapisywane w wielu tabelach, połączonych ze sobą związkami. Związki, które możemy zdefiniować i opisać werbalnie na poziomie koncepcyjnym, na poziomie logicznym realizowane są poprzez układ klucz główny – klucz obcy, czyli swego rodzaju wskaźniki. Poprzez porównanie wartości klucza głównego w tabeli nadrzędnej i wartości odpowiadającego mu klucza obcego w tabeli podrzędnej, możemy zidentyfikować powiązania pomiędzy rekordami. Każda znacząca, (czyli nie NULL) wartość klucza obcego w tabeli podrzędnej, wskazuje na dokładnie jeden rekord w tabeli nadrzędnej. I odwrotnie – każda wartość klucza głównego w tabeli nadrzędnej może wskazywać na jeden lub więcej rekordów w tabeli nadrzędnej. Identyfikacja tych powiązań sprowadza się do odszukania jednakowych wartości par {klucz główny, klucz obcy}.

Tyle teoria, spróbujmy obecnie przełożyć to na język SQL. Zaczniemy od pewnego eksperymentu. W celu poznania nazw i lokalizacji departamentów, w których zatrudnieni są poszczególni pracownicy, odczytajmy wynik polecenia

SELECT	Ename, Dname, Loc
FROM	EMP, DEPT;

I tutaj spotyka nas pewne zaskoczenie. W tabeli EMP jest 14 rekordów, w tabeli DEPT 4, a wynik powyższego polecenia zawiera 56 rekordów. Przyjrzyjmy się im dokładnie. Wynika z nich, że nazwisko każdego pracownika pojawia się czterokrotnie – w połączeniu z nazwą i lokalizacją każdego departamentu. Z kolei każdy departament jest wyświetlany w połączeniu z nazwiskami wszystkich pracowników. Mamy to do czynienia z iloczynem kartezjańskim określonym na zbiorach wierszy z tabel EMP i DEPT.

Czy ten wynik, jako całość, jest prawdziwy? Raczej nie! Czy zawiera w sobie prawdziwe informacje? Oczywiście, że tak. Prawdziwymi wierszami wyniku są te, które łączą nazwisko pracownika z nazwą i lokalizacją tego departamentu, w którym jest faktycznie zatrudniony. A skoro w wyniku są połączone nazwiska WSZYSTKICH pracowników z danymi WSZYSTKICH departamentów, stąd prosty wniosek, że wynik zawiera prawdziwe rekordy. Trzeba je tylko znaleźć.

Złączenie (powiązanie) tabel deklarowane w klauzuli WHERE

Każdy rekord w tabeli DEPT zawiera znaczącą wartość Deptno – klucz główny. Każdy rekord pracownika w tabeli EMP zawiera wartość Deptno, klucz obcy, wskazujący na numer departamentu, w którym dany pracownik jest zatrudniony. Zatem aby z wyniku naszego zapytania odczytać rekordy prawdziwe, należy do polecenia dodać sformułowany powyżej warunek równości wartości Deptno w obu tabelach. Obecnie nasze polecenie będzie wyglądało następująco:

Przykład 5.1.

Wypisz nazwiska pracowników (Ename), wraz z nazwami (Dname) i lokalizacjami (Loc) departamentów, w których są zatrudnieni, deklarując warunek złączenia tabel w klauzuli WHERE.

SELECT	Ename, Dname, Loc
FROM	EMP, DEPT
WHERE	EMP.Deptno = DEPT.Deptno;

Tym razem w wyniku pojawi się 14 wierszy, czyli tyle, ilu jest w tabeli EMP zapisanych pracowników. Zatem będą to tylko wiersze zawierające prawdziwe informacje. Będą to dane pracowników, wraz z nazwami i lokalizacją departamentów, w których są zatrudnieni. Jeżeli w tabeli DEPT istnieją rekordy, opisujące departamenty, których numer (Deptno) nie występuje w rekordach tabeli żadnych pracowników, ich dane w wyniku się nie pojawią. Jeżeli w tabeli EMP znajdują się rekordy opisujące pracowników, które w kolumnie Deptno mają wartość NULL (nie są przypisani do żadnego departamentu), ich dane w wyniku się nie pojawią.

Wyjaśnienie zamieszczone w poprzednim akapicie dotyczy przypadku, w którym we wszystkich wierszach w kolumnie Deptno tabeli EMP znajdują się wartości znaczące, będące elementami zbioru wartości klucza głównego Deptno tabeli DEPT. W naszym przykładzie (tabela EMP przedstawiona w wykładzie 4) tak nie jest - w rekordzie pracownika o numerze 7369, w kolumnie Deptno jest warość NULL. Stąd też w takim przypadku, w wyniku powyższej instrukcji SELECT zostanie zwrócone 13 wierszy.

To jest przykład złączenia dwóch tabel. Jeżeli zachodzi potrzeba złączenia więcej niż dwóch tabel, w klauzuli WHERE muszą znaleźć się wszystkie warunki definiujące warunki powiązań wszystkich tabel wchodzących w skład zapytania.
Jedna sprawa wymaga jeszcze wyjaśnienia. Dotychczas, odczytując dane z jednej tabeli, nigdy nie mieliśmy do czynienia z niejednoznacznością nazw kolumn. Zgodnie z postulatami Codd’a nazwy kolumn w każdej tabeli muszą być jednoznaczne. Jednak nazwy kolumn w różnych tabelach mogą się powtarzać, przy spełnionym postulacie zachowania unikalności nazw tabel. Aby w przypadku odwoływania się w zapytaniu do tabel, w których występują jednakowe nazwy kolumn, zachować wymaganą jednoznaczność, powtarzające się nazwy poprzedzane są (mówimy też: kwalifikowane) nazwami tabel, co oczywiście gwarantuje jednoznaczność. W przypadku kolumn o nazwach jednoznacznych w obrębie zapytania, poprzedzająca nazwa tabeli może się pojawić, ale także może zostać pominięta. Ta zasada dotyczy wszystkich klauzul.

Podsumowując przedstawioną powyżej metodę dochodzenia do prawidłowego wyniku zapytania, można sobie wyobrazić dwuetapowy schemat jego wykonania. W pierwszym etapie tworzony jest iloczyn kartezjański na zbiorach rekordów wszystkich rekordów tabel, na których operuje instrukcja SELECT, w drugim etapie, eliminowane są z niego te rekordy, dla których klauzula WHERE nie przyjmuje wartości TRUE. Oczywiście w klauzuli WHERE mogą się pojawić, powiązane operatorami w jedną całość, zarówno warunki powiązania tabel, jak też inne warunki ograniczające zwracane przez instrukcję rekordy.

Złączenie (powiązanie) tabel deklarowane w klauzuli FROM

Istnieje inny, przewidziany przez składnię języka SQL sposób wskazywania w instrukcji SELECT sposobu złączenia tabel. W swojej idei odwołuje się on do definicji związku tabel (więzów referencyjnych – zostaną one omówione w dalszej części wykładu). Składnia tej metody pozwala na wskazanie w klauzuli FROM tabel, które są ze sobą złączane, jak również kolumn, które to złączenie realizują. Z reguły są to kolumny klucza głównego tabeli nadrzędnej i odpowiadającego mu klucza obcego tabeli podrzędnej. W ogólności mogą to być dowolne kolumny, o tych samych typach danych (typy mogą być konwertowane). Składnia tej definicji powiązania wygląda następująco:

Tab1 JOIN Tab2 ON Tab1.Kol1 = Tab2.Kol2

gdzie T1 i T2 to tabele, z których dane będą odczytywane, Kol1 i Kol2 kolumny realizujące powiązanie tabel.

Złączenie tabel definiowane przez JOIN może występować w następujących wariantach:

INNER JOIN

LEFT [OUTER] JOIN

RIGHT [OUTER] JOIN

FULL [OUTER] JOIN

INNER JOIN

Najczęściej używanym wariantem łączenia tabel przy użyciu omawianej składni jest INNER JOIN, realizujący następującą odczytaj z bazy i włącz do rozwiązania wszystkie rekordy ze wskazanych w JOIN tabel, dla których wartości w kolumnach wskazanych w ON są sobie równe.

Przykład 5.2.

Wypisz nazwiska pracowników (Ename), wraz z nazwami (Dname) i lokalizacjami (Loc) departamentów, w których są zatrudnieni, deklarując warunek złączenia tabel poprzez JOIN.

SELECT		Ename, Dname, Loc
FROM		EMP
INNER JOIN	DEPT
ON		EMP.Deptno = DEPT.Deptno;

Powyższy przykład jest równoważny poprzedniemu, wykorzystującemu do związania tabel klauzulę WHERE, czyli włącza do wyniku te wiersze z tabel Tab1 i Tab2, dla których Tab1.Kol1 są równe wartościom Tab2.Kol2. Wyklucza to, oczywiście, rekordy z dowolnej z tabel Tab1 lub Tab2, w których w kolumnie Tab1.Kol1 lub Tab2.Kol2 znajdują się wartości NULL (porównanie jakiejkolwiek wartości z NULL daje w wyniku NULL, a do wynikowego zestawu rekordów włączane są tylko te, dla których wartość logiczna porównania kolumn jest TRUE, odrzucane są te, dla których ta wartość jest FALSE lub NULL).

W przypadku, gdy instrukcja SELECT odwołuje się do większej niż dwie liczby tabel, składnia definicji związku wygląda następująco:

SELECT		W1, W2, ….
FROM		T1
INNER JOIN	T2
ON		T1.K1 = T2.K2
INNER JOIN	T3
ON		T2.K3 = T3.K3
INNER JOIN	...
ON		...

Natomiast w sytuacji, gdy związek dwóch tabel zdefiniowany jest na więcej niż jednej parze kolumn (klucz główny tabeli nadrzędnej i klucz obcy tabeli podrzędnej są kluczami złożonymi), składnia definicji związku musi tą złożoność uwzględnić:

SELECT		W1, W2, ….
FROM		T1
INNER JOIN	T2
ON		T1.K1 = T2.K2 AND T1.K3 = T2.K4

gdzie K1 i K3 to kolumny złożonego klucza głównego tabeli nadrzędnej T1, kolumny K2 i K4 to kolumny złożonego klucza obcego tabeli podrzędnej T2.
Podsumowując, istotę wiązania tabel poprzez INNER JOIN można sformułować następująco: odczytaj z tabel powiązanych INNER JOIN tylko te rekordy, dla których wartości w kolumnach wskazanych przez ON są równe w obu tabelach.
Warto zwrócić uwagę na fakt, że rozdzielenie warunków złączenia tabel (przez zadeklarowanie ich poprzez INNER JOIN) od warunków ograniczających (deklarowanych w klauzuli WHERE) czyni całość kodu znacznie czytelniejszą, niż umieszczenie w klauzuli WHERE zarówno warunków złączeń jak i warunków ograniczających.

Zastanówmy się chwilę, co naprawdę oznacza podana powyżej reguła, dotycząca odczytywania danych z tabel powiązanych INNER JOIN. Załóżmy, że w ON podane są kolumny odpowiednio klucza głównego tabeli nadrzędnej i powiązanego z nim klucza obcego tabeli podrzędnej (jest to najczęściej spotykany przypadek, aczkolwiek nie jedyny możliwy). W tym przypadku w wyniku, każda wartość klucza głównego tabeli nadrzędnej pojawi się tyle razy, ile razy została użyta w roli klucza obcego tabeli podrzędnej. Natomiast patrząc od strony tabeli podrzędnej, nie pojawią się w wyniku te rekordy, dla których wartość kolumny klucza obcego jest NULL (zakładamy, że nie mogą się tam pojawić wartości nieużyte w roli klucza głównego tabeli nadrzędnej).

OUTER JOIN

Drugim, obok INNER JOIN sposobem złączania tabel jest OUTER JOIN (słowo OUTER jest opcjonalne, może zostać opuszczone). OUTER JOIN występuje w trzech wariantach: LEFT, RIGHT i FULL. Konstrukcja ta używana jest w celu włączenia do wynikowych rekordów złączenia tabel, także tych rekordów, których nie włącza INNER JOIN. Dyrektywa OUTER JOIN umożliwia odczytanie ze wskazanych tabel zarówno powiązanych, jak też niepowiązanych rekordów i występuje w trzech wariantach:

LEFT JOIN

LEFT [OUTER] JOIN (lewostronne złączenie zewnętrzne) – do wyniku włączane są rekordy, dla których wartości powiązanych pól w obu tabelach są równe, (czyli tak, jak w INNER JOIN), oraz wszystkie pozostałe rekordy z tabeli wymienionej po lewej stronie słów LEFT JOIN. Zatem wracając do powyższego przykładu:

Przykład 5.3.

Wypisz nazwiska (Ename) wszystkich pracowników wraz z nazwami (Dname) i lokalizacjami (Loc) departamentów, w których są zatrudnieni. Uwzględnij także tych pracowników, którzy nie zostali przypisani do żadnego departamentu (w kolumnie Deptno opisującego ich rekordu jest wartość Null).

SELECT 		Ename, Dname, Loc
FROM 		EMP
LEFT JOIN	DEPT
ON		EMP.Deptno = DEPT.Deptno;

Tym razem w wyniku pojawią się wszystkie wiersze pracowników oraz zatrudniających ich departamentów, ale także dane tych pracowników, którzy nie są zatrudnieni w żadnym z departamentów (w polach Dname i Loc pojawi się w wyniku NULL).

RIGHT JOIN

RIGHT [OUTER] JOIN (prawostronne złączenie zewnętrzne, symetryczne dla złączenia lewostronnego) – do wyniku włączane są rekordy, dla których wartości powiązanych pól w obu tabelach są równe, (czyli tak, jak w INNER JOIN), oraz wszystkie pozostałe rekordy z tabeli wymienionej po prawej stronie słów RIGHT JOIN. Teraz nasz przykład zdefiniujemy następująco:

Przykład 5.4.

Wypisz nazwiska (Ename) wszystkich pracowników wraz z nazwami (Dname) i lokalizacjami (Loc) departamentów, w których są zatrudnieni. Uwzględnij także te departamenty, w których nikt nie jest zatrudniony (wartość Deptno nie występuje w żadnym rekordzie tabeli EMP).

SELECT 		Ename, Dname, Loc
FROM 		EMP
RIGHT JOIN 	DEPT
ON		EMP.Deptno = DEPT.Deptno;

Tym razem w wyniku pojawią się wszystkie wiersze pracowników oraz zatrudniających ich departamentów, ale także dane tych departamentów, w których nie są zatrudnieni pracownicy (w polu Ename pojawi się w wyniku NULL).

FULL JOIN

FULL [OUTER] JOIN (pełne złączenie zewnętrzne) – jest sumą, (ale bez powtórzeń) wyników LEFT i RIGHT. Do wyniku włączane są rekordy, dla których wartości powiązanych pól w obu tabelach są równe, (czyli tak, jak w INNER JOIN), oraz wszystkie pozostałe rekordy z tabel wymienionych po obu stronach słów FULL JOIN. W naszym przykładzie:

Przykład 5.5.

Wypisz nazwiska (Ename) wszystkich pracowników wraz z nazwami (Dname) i lokalizacjami (Loc) departamentów, w których są zatrudnieni. Uwzględnij także te departamenty, w których nikt nie jest zatrudniony (wartość Deptno nie występuje w żadnym rekordzie tabeli EMP), a także tych pracowników, którzy nie zostali przypisani do żadnego departamentu (w kolumnie Deptno opisującego ich rekordu jest wartość Null).

SELECT 		Ename, Dname, Loc
FROM 		EMP
FULL JOIN	DEPT
ON		EMP.Deptno = DEPT.Deptno;

Teraz w wyniku pojawią się wszystkie wiersze pracowników oraz zatrudniających ich departamentów, ale także dane tych departamentów, w których nie są zatrudnieni pracownicy (w polu Ename pojawi się w wyniku NULL) oraz dane tych pracowników, którzy nie są zatrudnieni w żadnym z departamentów (w polach Dname i Loc pojawi się w wyniku NULL).

Uzyskanie wyniku tożsamego z wynikiem FULL JOIN jest możliwe także poprzez inną konstrukcję (UNION), omówiona w dalszej części wykładu. Jednak wariant FULL JOIN został włączony do składni z uwagi na zachowanie jej spójności.

Należy jeszcze wspomnieć, że ORACLE implementuje jeszcze inny zapis, realizujący złącznie jednostronne:

SELECT	Ename, Dname, Loc
FROM	EMP, DEPT
WHERE	EMP.Deptno (+)= DEPT.Deptno;

Powyższa formuła jest równoważna z zapisem

SELECT		Ename, Dname, Loc
FROM		EMP
RIGHT JOIN 	DEPT
ON		EMP.Deptno = DEPT.Deptno;

W wyniku zwracane są wszystkie rekordy tabeli DEPT, oraz te rekordy tabeli EMP, dla których wartości wiążących pól Deptno są równe.

Inne sposoby definiowania związków tabel

Dotychczas zostały przedstawione sposoby utworzenia związków tabel w oparciu o równość wartości zapisanych w porównywanych kolumnach. Nie jest to jednak jedyna możliwość. Co więcej, można sformułować regułę, że prawidłowa jest każda konstrukcja, usuwająca z iloczynu kartezjańskiego (wyprodukowanego na wszystkich rekordach wszystkich tabel wymienionych w klauzuli SELECT) te rekordy, które zawierają nieprawdziwe dane.

Jako przykład posłuży nam związek tabel EMP i SALGRADE. Tabele te, jak to już było wcześniej omówione, nie są powiązane poprzez układ klucz główny – klucz obcy. Jeżeli utworzymy iloczyn kartezjański wszystkich rekordów z obu tabel, będzie on zawierał rekordy wszystkich pracowników (także ich miesięczną płacę Sal) w połączeniu ze wszystkimi rekordami tabeli SALGRADE, zawierającymi wartości płacy maksymalnej (Maxsal), minimalnej (Minsal) oraz numer grupy zaszeregowania. Liczba wynikowych rekordów będzie równa wynikowi przemnożenia liczby rekordów tabeli EMP przez liczbę rekordów tabeli SALGRADE. Ponieważ wynik ten zawiera wszystkie wartości płac w połączeniu ze wszystkimi grupami zarobkowymi, zatem zawiera także te, które odpowiadają właściwej grupie zarobkowej. Z omówienia zawartości tabel EMP i DEPT wynika, że każda płaca Sal mieści się w przedziale pomiędzy Minsal i Maxsal z tabeli SALGRADE, co odpowiada jednej grupie zarobkowej. Zatem wystarczy umieścić ten warunek w klauzuli WHERE, aby uzyskać prawidłowy wynik.

Przykład 5.6.

Wypisz nazwiska (Ename), ich płace (Sal) oraz grupę zaszeregowania (Grade) wszystkich pracowników.

SELECT	Ename, Sal, Grade
FROM	EMP, SALGRADE
WHERE	Sal BETWEEN Losal AND Hisal;

Identyczny rezultat uzyskamy, używając warunku złączenia definiowanego w JOIN:

SELECT		Ename, Sal, Grade
FROM		EMP
INNER JOIN	SALGRADE
ON		Sal BETWEEN Losal AND Hisal;

Samozłączenie tabel

Dopuszczalność, w relacyjnym modelu danych, istnienia związków rekurencyjnych, czyli możliwość tworzenia związku jednoznacznego na jednej tabeli, będącej jednocześnie stroną jeden i stroną wiele tego związku, implikuje możliwość odczytu danych zapisanych z użyciem takiego związku. Ponieważ nazwa tabeli musi być przy zapisie użyta zarówno w roli tabeli nadrzędnej, jak i podrzędnej, zatem w celu zachowania wymaganej jednoznaczności nazw tabel, należy używać aliasów ich nazw.

Przykład 5.7.

Wypisz nazwiska (Ename) pracowników, oraz nazwiska (Ename) ich szefów.

SELECT		K.Ename Szef, P.Ename Pracownik
FROM		EMP K
INNER JOIN	EMP P
ON		K.Empno = P.Mgr;

Jak to zostało opisane w Wykładzie I, w tabeli EMP każdy pracownik (jego wiersz) identyfikowany jest wartością klucza głównego Empno, a jego przełożonego (szefa) wskazuje wartość w kolumnie Mgr, będąca kluczem obcym. Zatem zrealizowany jest związek rekurencyjny na tabeli EMP. Aby z niego skorzystać, należy dwukrotnie odwołać się do tabeli EMP, gdyż występuje ona po obu stronach związku. Z uwagi na konieczność zachowania jednoznaczności nazw, należy użyć aliasu nazwy tabel (w powyższym przykładzie zostały użyte aliasy „obu” tabel, choć wystarczyłby tylko jeden). Po wykonaniu tego zabiegu, można odwołać się w klauzuli SELECT oraz w warunku złączenia ON do „rozmnożonej” tabeli EMP, raz występującej pod nazwą K (z tej tabeli odczytujemy dane szefów), a raz pod nazwą P (z tej tabeli odczytujemy dane pracowników).

Podsumowując zagadnienia związane z odczytywaniem danych z wielu tabel, należy podkreślić, że poza bardzo nielicznymi przypadkami, prawidłowy wynik uwarunkowany jest zdefiniowaniem warunków złączenia tabel. Można powiedzieć, że w wyniku wymienienia w klauzuli SELECT nazw tabel, z których dane będą odczytywane, tworzona jest wirtualna tabela, zawierająca wyłącznie te rekordy, które pozostały po usunięciu z iloczynu kartezjańskiego utworzonego na wszystkich rekordach wymienionych tabel, tych rekordów, które nie spełniają warunków złączenia i warunków ograniczających zadeklarowanych w JOIN i/lub w klauzuli WHERE. Ta wirtualna tabela tworzona (koncepcyjnie), tylko w celach odczytu danych, nie ma żadnego wpływu (nie zmienia, nie narusza) na zasadniczą strukturę bazy danych, zdefiniowaną poprzez tabele oraz więzy integralności (omówienie w dalszej części wykładu). W dalszej części wykładu przyjmiemy, że nie ma znaczenia fakt, na ilu tabelach wykonywana jest operacja. Istotna jest prawidłowość ich złączeń, dająca w wyniku RELACJĘ, na której dalsze operacje będą wykonywane.

Operatory algebraiczne

Wyniki instrukcji SELECT mogą być traktowane, jako zbiory rekordów. Na dwóch, lub większej liczbie wyników można wykonywać operacje dopuszczalne na zbiorach – sumy, różnicy i przecięcia (iloczynu) zbiorów. Warunkiem wykonalności tych operacji, jest jednakowa struktura wszystkich zbiorów wyników, biorących w niej udział. Przez jednakową strukturę rozumiemy, że każdy zbiór zawiera identyczną liczbę wynikowych kolumn, identycznie uporządkowanych, a każda kolumna zawiera wartości o identycznym typie danych.

Operatory realizujące te operacje to odpowiednio:

UNION [ALL]	– suma zbiorów wyników
INTERSECT	– iloczyn (przecięcie) zbiorów wyników
EXCEPT / MINUS	– różnica zbiorów wyników (EXCEPT jest przewidziany przez standard języka SQL, MINUS zaimplementowany w ORACLE).

Krótkie przypomnienie pojęć z teorii mnogości:

I jeszcze uściślenie sposobu działania operatora UNION. W wyniku jego użycia zwracany jest zbiór wynikowych rekordów bez powtórzeń (rekordy powtarzające się zwracane są tylko jeden raz), czyli zwracany jest wynik ściśle odpowiadający definicji sumy zbiorów. Natomiast użycie opcjonalnego rozszerzenia ALL wstrzymuje eliminację powtórzeń – do wynikowego zbioru rekordów wchodzą wszystkie rekordy występujące w zbiorach wyjściowych.

Poniżej przedstawiamy trzy proste przykłady prezentujące działanie operatorów algebraicznych.

Przykład 5.8.

Wypisz stanowiska (job) pracowników z działów 10 i 30.

SELECT	Job
FROM 	EMP
WHERE 	Deptno = 10
UNION
SELECT 	Job
FROM 	EMP
WHERE 	Deptno = 30;

Użycie w powyższym przykładzie operatora UNION ALL spowoduje wypisanie każdego stanowiska tyle razy, ilu pracowników z działów 10 i 30 łącznie, jest na tym stanowisku zatrudnionych.

Przykład 5.9.

Wypisz stanowiska (job) występujące zarówno w dziale 10 jak i 30.

SELECT 	Job
FROM 	EMP
WHERE 	Deptno = 10
INTERSECT
SELECT 	Job
FROM 	EMP
WHERE 	Deptno = 30;

Przykład 5.10.

Wypisz stanowiska (job) występujące w dziale 10, a niewystępujące w dziale 30.

SELECT 	Job
FROM 	EMP
WHERE 	Deptno = 10
EXCEPT
SELECT 	Job
FROM 	EMP
WHERE 	Deptno = 30;

Aliasy nazw tabel

Tabelom użytym w konstrukcji zapytania, można zmienić nazwy, czyli nadać aliasy. Tak jak w przypadku aliasów kolumn, alias może występować w postaci prostego identyfikatora, czyli napisu złożonego z liter, cyfr i znaków podkreślenia. Identyfikator ograniczony różni się od identyfikatora prostego tym, że MOŻE zawierać spacje i MUSI być ujęty w podwójne cudzysłowy. W przypadku aliasowania nazw tabel praktycznie nie używa się identyfikatorów ograniczonych. Alias może zostać poprzedzony słowem AS przewidzianym przez standard języka, dopuszczalnym, ale niewymaganym w większości implementacji.

Oczywiście zmiana nazwy tabeli poprzez użycie aliasu, dotyczy tylko tej instrukcji SELECT, w której został on zdefiniowany.
O ile użycie aliasów nazw wyrażeń w klauzuli SELECT jest na ogół spowodowane wygodą użytkownika, lub dążeniem do elegancji wyniku, o tyle używanie aliasów nazw tabel jest konieczne w przypadku wielokrotnych odwołań do jednej tabeli w przypadku związków rekurencyjnych i podzapytań skorelowanych, omawianych w jednym z kolejnych rozdziałów.

W przypadku odwoływania się do tabel z innych schematów, lub baz danych, użycie aliasów nazw tabel pozwala radykalnie skrócić całość nazwy, zawierającą nazwę bazy, schematu i tabeli. To zagadnienie zostanie szerzej omówione w wykładzie z przedmiotu Administrowanie Bazami Danych.