UNION, INTERSECT, EXCEPT и CROSS JOIN
Накратко: UNION събира резултатите на две заявки, INTERSECT оставя общите редове, EXCEPT маха от първия резултат редовете на втория, а CROSS JOIN дава всяка двойка редове.
Проблем
Един списък от две места
Магазинът праща есенен бюлетин на активните клиенти. Активен е всеки, който е поръчал от 1 септември 2026 г. или е писал отзив от 1 юни насам.
Поръчките и отзивите са в различни таблици. Ако ги свържеш с JOIN, ще останат само клиентите, които имат и двете. Трябват ни двата списъка, сложени един под друг.
Идеята
UNION
UNION слага резултата на втората заявка под този на първата и маха повторенията:
11 клиенти. Всяка от двете заявки е цяла, със свой FROM, JOIN и WHERE. Имената на колоните идват от първата.
UNION ALL пази и повторенията:
17 реда: петима клиенти се срещат повече от веднъж (с няколко поръчки или и с поръчка, и с отзив). За бюлетина е нужен UNION, иначе някой ще получи писмото три пъти. UNION ALL е за случаите, когато всеки ред е отделно събитие и трябва да се брои.
Пробвай
Дегустация на кафе
Магазинът кани на дегустация всички, които са купували кафе на зърна (продукт 17) или са писали отзив за него. Покажи имената им, всяко веднъж.
Решение
SELECT c.name FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN order_items oi ON oi.order_id = o.id
WHERE oi.product_id = 17
UNION
SELECT c.name FROM reviews r
JOIN customers c ON c.id = r.customer_id
WHERE r.product_id = 17;Две заявки с UNION: купувачите през orders, customers и order_items с oi.product_id = 17, и авторите на отзиви през reviews и customers с r.product_id = 17. Шестима души.
Виж го
Колоните трябва да съвпадат
Двете заявки дават редове за една и съща таблица, затова броят на колоните им трябва да е еднакъв:
Колоните се сдвояват по реда си, а не по имената. Ако първата заявка връща name, email, втората също трябва да връща две колони, и то в смислен ред: името под името.
За обща история на клиента сложи колона, която казва откъде е редът. Поръчките и отзивите на Мария, по дата:
ORDER BY в края подрежда целия резултат, а не само втората заявка. Колоните се казват day и event, както в първата заявка.
Идеята
INTERSECT и EXCEPT
INTERSECT оставя редовете, които ги има и в двата резултата. Продуктите, които са и поръчвани, и с отзив:
13 продукта. Това са точно продуктите с отзив, защото всеки продукт е поръчван (видя го в урока за LEFT JOIN).
EXCEPT оставя редовете от първия резултат, които ги няма във втория. Продуктите без отзив:
12 продукта. Същото дава и LEFT JOIN reviews … WHERE r.id IS NULL. EXCEPT се чете като изречение: „всички продукти, без тези с отзив“. При EXCEPT редът на заявките е важен, а при UNION и INTERSECT не е.
Пробвай
Да поискаме отзив
Кои продукти са купувани от 1 септември 2026 г. насам, но нямат нито един отзив? На купувачите им ще се изпрати молба за отзив. Покажи имената на продуктите.
Решение
SELECT p.name FROM order_items oi
JOIN orders o ON o.id = oi.order_id
JOIN products p ON p.id = oi.product_id
WHERE o.ordered_at >= '2026-09-01'
EXCEPT
SELECT p.name FROM reviews r
JOIN products p ON p.id = r.product_id;Купуваните от септември (order_items, orders, products с o.ordered_at >= '2026-09-01') EXCEPT продуктите с отзив. 10 продукта, сред тях подаръчната карта и баницата.
Идеята
CROSS JOIN
Понякога ти трябва всяка двойка. Отчетът по градове и категории трябва да има ред за всеки град и всяка подкатегория, дори там, където продажби няма. Мрежата се прави с CROSS JOIN: всеки ред на едната таблица с всеки ред на другата, без ON.
6 града по 6 подкатегории, 36 реда. DISTINCT е нужен, защото в customers градът се повтаря: без него редовете са 114, по един за всеки клиент с град и всяка подкатегория.
Да попълниш мрежата с продажбите (и с нулите) ще можеш в модула за подзаявките. Мрежата е първата половина.
Виж го
Внимание с броя на редовете
CROSS JOIN умножава. 60 поръчки и 152 реда на поръчки:
9 120 двойки, почти всички безсмислени. Същото се случва, ако забравиш ON. В SQLite JOIN без ON е CROSS JOIN, без грешка:
1 200 реда (60 × 20) вместо 60. PostgreSQL в този случай дава грешка. Ако отчетът изведнъж покаже много повече редове или пари от очакваното, първо провери условията в ON.
Упражнение
Мрежата на главните категории
Годишният отчет е по град и главна категория („Храни“, „Дом“). Направи мрежата: всеки град (без празния) с всяка главна категория, всяка двойка веднъж.
Решение
SELECT DISTINCT cu.city, cat.name AS category
FROM customers cu
CROSS JOIN categories cat
WHERE cu.city IS NOT NULL AND cat.parent_id IS NULL;FROM customers cu CROSS JOIN categories cat WHERE cu.city IS NOT NULL AND cat.parent_id IS NULL, с DISTINCT. 6 града по 2 категории, 12 реда. Главните категории са тези без родител.
По-точно, за любопитните
UNION в SQLite често връща редовете подредени, защото така маха повторенията. Това не е обещано. Щом редът е важен, пиши ORDER BY в края.
INTERSECT и EXCEPT ги има в SQLite, PostgreSQL и SQL Server. В Oracle EXCEPT се казва MINUS, а новите версии разбират и EXCEPT. MySQL ги има от версия 8.0.31.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Агрегатни функции: COUNT, SUM, AVG · JOIN: две таблици в една заявка · LEFT JOIN: и редовете без двойка · Много таблици и таблица със себе си
- Продължава в: Отчет на стъпки: WITH · Пул от връзки, резервни копия и права · Търсене по смисъл
- Задачи по темата: Свързване на таблици