
Ważne: to nie jest JOIN.
JOIN łączy tabele „w bok” (dokłada kolumny).
Operacje zbiorowe łączą wyniki „w dół” (dokładają wiersze).
1) Co to są operacje zbiorowe w SQL?
Operacje zbiorowe działają tak, jak w matematyce na zbiorach/licie danych:
- masz wynik zapytania A (np. lista e-maili uczniów),
- masz wynik zapytania B (np. lista e-maili nauczycieli),
- i chcesz:
- połączyć te listy (UNION),
- znaleźć część wspólną (INTERSECT),
- albo odjąć jedną listę od drugiej (EXCEPT).
W SQL wygląda to tak, że piszesz kilka SELECT, a między nimi wstawiasz operator.
2) UNION — wyniki jako jedna lista
UNION (bez duplikatów)
UNION łączy wyniki dwóch (lub więcej) zapytań i usuwa powtórki.
Czyli: jeśli ten sam wiersz pojawi się w A i w B, to w wyniku będzie tylko raz.
SELECT kolumny
FROM ...
UNION
SELECT kolumny
FROM ...;
UNION ALL (z duplikatami)
UNION ALL robi to samo, ale nie usuwa duplikatów.
Często jest szybszy i bywa po prostu wygodniejszy, jeśli powtórki Ci nie przeszkadzają albo chcesz je zobaczyć.
SELECT kolumny
FROM ...
UNION ALL
SELECT kolumny
FROM ...;
3) INTERSECT — pokazuje tylko to, co jest w obu wynikach
INTERSECT to część wspólna: zostają tylko te wiersze, które występują jednocześnie w A i w B.
Brzmi super, ale… musimy być szczerzy:
Ważne o MySQL / MariaDB (phpMyAdmin)
UNIONdziała normalnie w MySQL/MariaDB.INTERSECTiEXCEPTw wielu szkolnych instalacjach MySQL/MariaDB nie działają (bo nie są obsługiwane albo są wyłączone / zależy od wersji).
Dlatego na lekcjach i egzaminach często uczy się:
- idei
INTERSECT, - ale wykonania w MySQL przez
IN,EXISTS,JOIN.
I to jest naprawdę praktyczne, bo działa zawsze.
4) EXCEPT — „A bez B” (różnica zbiorów)
EXCEPT zwraca wiersze, które są w A, ale nie ma ich w B.
Podobnie jak przy INTERSECT:
- w MySQL/MariaDB często nie ma EXCEPT,
- więc robimy to przez
NOT EXISTS,NOT INalbo czasemLEFT JOIN ... IS NULL(ale na tej stronie skupimy się naNOT EXISTS, bo jest najpewniejsze).
5) Najważniejsze zasady składni (to się myli najczęściej)
Zasada 1: ta sama liczba kolumn
Każdy SELECT w UNION/INTERSECT/EXCEPT musi zwrócić dokładnie tyle samo kolumn.
Czyli to zadziała:
SELECT email FROM uczniowie
UNION
SELECT email FROM nauczyciele;
A to nie:
SELECT id, email FROM uczniowie
UNION
SELECT email FROM nauczyciele; -- błąd: różna liczba kolumn
Zasada 2: pasujące typy danych
Kolumny powinny mieć sensownie zgodne typy.
Np. nie mieszaj liczb z datą albo tekstu z ceną (chyba że wiesz, co robisz i użyjesz CAST).
Zasada 3: kolejność kolumn ma znaczenie
SQL dopasowuje kolumny po kolejności, nie po nazwach.
Zasada 4: ORDER BY dajemy na końcu
Jeśli chcesz sortować wynik, ORDER BY dajesz na końcu całej konstrukcji:
SELECT email FROM uczniowie
UNION
SELECT email FROM nauczyciele
ORDER BY email;
BAZA DO CWICZEŃ
DROP DATABASE IF EXISTS szkolenie_sql;
CREATE DATABASE szkolenie_sql
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
USE szkolenie_sql;
-- UCZNIOWIE
CREATE TABLE uczniowie (
id INT PRIMARY KEY AUTO_INCREMENT,
imie VARCHAR(40) NOT NULL,
nazwisko VARCHAR(60) NOT NULL,
email VARCHAR(120) NOT NULL UNIQUE,
klasa VARCHAR(10) NOT NULL
);
INSERT INTO uczniowie (imie, nazwisko, email, klasa) VALUES
('Ala', 'Nowak', 'ala.nowak@szkola.pl', '2A'),
('Bartek', 'Kowalski', 'bartek.kowalski@szkola.pl', '2A'),
('Celina', 'Zielińska', 'celina.zielinska@szkola.pl', '3B'),
('Damian', 'Wiśniewski', 'damian.wisniewski@szkola.pl', '3B');
-- NAUCZYCIELE
CREATE TABLE nauczyciele (
id INT PRIMARY KEY AUTO_INCREMENT,
imie VARCHAR(40) NOT NULL,
nazwisko VARCHAR(60) NOT NULL,
email VARCHAR(120) NOT NULL UNIQUE,
przedmiot VARCHAR(60) NOT NULL
);
INSERT INTO nauczyciele (imie, nazwisko, email, przedmiot) VALUES
('Ewa', 'Maj', 'ewa.maj@szkola.pl', 'Informatyka'),
('Filip', 'Lis', 'filip.lis@szkola.pl', 'Matematyka'),
-- celowo: ten sam e-mail co uczeń, żeby było widać sens INTERSECT/duplikatów
('Celina', 'Zielińska', 'celina.zielinska@szkola.pl', 'Zajęcia dodatkowe');
-- KURSY
CREATE TABLE kursy (
id INT PRIMARY KEY AUTO_INCREMENT,
nazwa VARCHAR(80) NOT NULL
);
INSERT INTO kursy (nazwa) VALUES
('SQL od podstaw'),
('Python podstawy'),
('Sieci komputerowe');
-- ZAPISY UCZNIÓW NA KURSY (relacja wiele-do-wielu)
CREATE TABLE zapisy_uczniow (
uczen_id INT NOT NULL,
kurs_id INT NOT NULL,
PRIMARY KEY (uczen_id, kurs_id),
FOREIGN KEY (uczen_id) REFERENCES uczniowie(id),
FOREIGN KEY (kurs_id) REFERENCES kursy(id)
);
INSERT INTO zapisy_uczniow (uczen_id, kurs_id) VALUES
(1, 1), -- Ala -> SQL
(2, 1), -- Bartek -> SQL
(3, 2), -- Celina -> Python
(4, 3); -- Damian -> Sieci
-- KLIENCI (żeby pokazać przykład "sklepowy")
CREATE TABLE klienci (
id INT PRIMARY KEY AUTO_INCREMENT,
nazwa VARCHAR(120) NOT NULL,
email VARCHAR(120) NOT NULL UNIQUE
);
INSERT INTO klienci (nazwa, email) VALUES
('Firma Alfa', 'kontakt@alfa.pl'),
('Firma Beta', 'kontakt@beta.pl'),
('Osoba Prywatna', 'celina.zielinska@szkola.pl'); -- celowo ten sam mail co wcześniej
-- ZAMOWIENIA
CREATE TABLE zamowienia (
id INT PRIMARY KEY AUTO_INCREMENT,
klient_id INT NOT NULL,
data_zamowienia DATETIME NOT NULL,
status ENUM('nowe','oplacone','wyslane','anulowane') NOT NULL,
FOREIGN KEY (klient_id) REFERENCES klienci(id)
);
INSERT INTO zamowienia (klient_id, data_zamowienia, status) VALUES
(1, '2026-05-01 10:00:00', 'oplacone'),
(2, '2026-05-03 12:30:00', 'nowe'),
(1, '2026-05-10 09:15:00', 'wyslane');
Przykłady UNION I UNION ALL
Przykład 1 (UNION): jedna lista maili: uczniowie + nauczyciele (bez powtórek)
SELECT email
FROM uczniowie
UNION
SELECT email
FROM nauczyciele
ORDER BY email;
Co się dzieje krok po kroku?
- Pierwszy SELECT bierze wszystkie maile uczniów.
- Drugi SELECT bierze wszystkie maile nauczycieli.
- UNION skleja te wyniki w jedną kolumnę.
- Jeśli jakiś mail powtórzył się w obu tabelach (u nas
celina.zielinska@szkola.pl) → w wyniku będzie tylko raz. ORDER BYsortuje całość.
Przykład 2 (UNION ALL): to samo, ale chcemy widzieć powtórki
SELECT email
FROM uczniowie
UNION ALL
SELECT email
FROM nauczyciele
ORDER BY email;
Różnica w praktyce:celina.zielinska@szkola.pl pojawi się dwa razy, bo jest w obu tabelach.
Przykład 3: „ładniejszy wynik” — dodajemy etykietę skąd jest rekord
SELECT email, 'uczen' AS rola
FROM uczniowie
UNION ALL
SELECT email, 'nauczyciel' AS rola
FROM nauczyciele
ORDER BY email, rola;
Dlaczego UNION ALL, a nie UNION?
Bo nawet jeśli e-mail się powtarza, to wiersze nie są identyczne (różni się rola), więc i tak by nie zniknęły. A ALL jest po prostu uczciwsze i szybsze.
Przykłady INTERSECT
Pokaż e-maile, które są i w uczniach, i w nauczycielach
W SQL z INTERSECT wyglądałoby to tak (ale może nie działać w MySQL):
SELECT email FROM uczniowie
INTERSECT
SELECT email FROM nauczyciele;
Obejście 1: IN (proste)
SELECT email
FROM uczniowie
WHERE email IN (SELECT email FROM nauczyciele);
Krok po kroku:
- Wewnętrzny SELECT tworzy listę maili nauczycieli.
- Zewnętrzny SELECT bierze tylko tych uczniów, których email jest na tej liście.
- Wynik to „część wspólna” — czyli INTERSECT.
Wynik w naszej bazie: celina.zielinska@szkola.pl
Obejście 2: EXISTS (bardziej „bezpieczne” i często polecane)
SELECT u.email
FROM uczniowie u
WHERE EXISTS (
SELECT 1
FROM nauczyciele n
WHERE n.email = u.email
);
Przykłady EXCEPT
Pokaż e-maile uczniów, których NIE ma w nauczycielach
SELECT email FROM uczniowie
EXCEPT
SELECT email FROM nauczyciele;
Obejście: NOT EXISTS
SELECT u.email
FROM uczniowie u
WHERE NOT EXISTS (
SELECT 1
FROM nauczyciele n
WHERE n.email = u.email
)
ORDER BY u.email;
Krok po kroku:
- Bierzemy każdego ucznia.
- Sprawdzamy, czy istnieje nauczyciel z takim samym mailem.
- Jeśli nie istnieje → uczeń zostaje w wyniku.
Typowe błędy
- różna liczba kolumn – oba SELECT-y muszą zwracać tyle samo kolumn.
- ORDER BY w złym miejscu – dajemy na samiutkim końcu, po wszystkich zapytaniach
- INTERSECT/EXCEPT nie działa, MySQL krzyczy, że nie zna składni (
You have an error in your SQL syntax...). Użyj INTERSECT →IN/EXISTS;EXCEPT →NOT EXISTS(najlepiej) - mieszanie typów danych: np.
SELECT id UNION SELECT email– nawet jeśli przejdzie, wynik jest bez sensu.
Tabela porównawcza
| Operator | Co robi „po ludzku” | Duplikaty | Wsparcie w MySQL/MariaDB |
|---|---|---|---|
| UNION | A + B | usuwa | tak |
| UNION ALL | A + B | zostawia | tak |
| INTERSECT | A i B | zależy od silnika | często brak → IN/EXISTS |
| EXCEPT | A bez B | zależy od silnika | często brak → NOT EXISTS |
Ćwiczenia
Zadania rób na bazie szkolenia.sql (nad „Przykłady UNION I UNION ALL”)
- Zrób jedną listę maili: uczniowie + nauczyciele (bez powtórek).
- Zrób jedną listę maili: uczniowie + nauczyciele (z powtórkami).
- Zrób listę maili: uczniowie + nauczyciele + klienci, ale dodaj kolumnę
zrodlo. - Pokaż tylko te e-maile uczniów, które występują też u nauczycieli (INTERSECT → IN).
- Pokaż e-maile uczniów, których nie ma u nauczycieli (EXCEPT → NOT EXISTS).
- Pokaż e-maile klientów, które nie występują ani w uczniach, ani w nauczycielach.
- Zrób listę osób (email + typ_osoby) dla uczniów i nauczycieli (UNION ALL), posortuj po email rosnąco, a gdy ten sam email — niech „nauczyciel” będzie nad „uczen” (podpowiedź: sortowanie po drugiej kolumnie).
- Pokaż e-maile, które występują jednocześnie w tabeli
klienciiuczniowie(INTERSECT → EXISTS). - Pokaż e-maile nauczycieli, którzy nie występują w
klienci(EXCEPT → NOT EXISTS). - Zrób raport:
emailorazile_razy_wystepujew całym systemie (uczniowie+nauczyciele+klienci).
Podpowiedź: użyjUNION ALL, a potem policz (tu będzie potrzebneGROUP BY).