Урок 5.10 · Время чтения: ~8 мин
Этот урок знакомит с операциями над наборами данных в SQL. Вы узнаете, как объединять результаты нескольких запросов, находить общие строки и исключать лишние значения. Мы посмотрим на UNION, UNION ALL, INTERSECT и EXCEPT на примерах Sakila. К концу урока вы сможете выбирать правильный оператор для разных аналитических сценариев.
Операции над наборами данных
В предыдущих уроках вы научились соединять таблицы через JOIN и разбирать, как движок выполняет такие соединения. Теперь перейдем к другой идее: иногда нужно не объединять строки по ключам, а собирать и сравнивать целые наборы результатов.
Операции над наборами данных полезны, когда вы хотите свести вместе данные из нескольких выборок, найти пересечение аудиторий или исключить строки, которые уже попали в другой список. На практике это часто встречается в отчетах, проверке качества данных и подготовке итоговых списков.
Что такое операции над наборами данных
Операции над наборами данных работают не со строками одной таблицы, а с результатами двух или более запросов. С точки зрения SQL каждый SELECT возвращает набор строк, а операторы вроде UNION или INTERSECT комбинируют эти наборы по определенным правилам.
Чаще всего используют четыре оператора:
UNION— объединяет результаты и удаляет дубликаты;UNION ALL— объединяет результаты и сохраняет дубликаты;INTERSECT— оставляет только общие строки;EXCEPT— возвращает строки из первого набора, которых нет во втором.
Важно: не все СУБД поддерживают все эти операторы одинаково. При переносе запросов между движками всегда проверяйте версию и совместимость.
Общие правила
Чтобы использовать наборную операцию, оба SELECT должны возвращать совместимые результаты.
Требования к запросам
- одинаковое количество столбцов;
- совместимые типы данных в соответствующих позициях;
- одинаковый порядок столбцов;
- при необходимости одинаковый смысл значений.
Если нужно отсортировать итоговый результат, ORDER BY пишут в самом конце всего выражения.
SELECT column1, column2
FROM table_a
UNION
SELECT column1, column2
FROM table_b
ORDER BY column1;
UNION и UNION ALL
UNION и UNION ALL выглядят похоже, но решают разные задачи.
UNIONубирает дубликаты из итогового набора.UNION ALLоставляет все строки, даже если они повторяются.
Пример: единый список городов клиентов и сотрудников
Представим, что мы хотим получить один список городов, в которых живут клиенты и сотрудники Sakila.
SELECT
ci.city
FROM customer AS c
JOIN address AS a ON c.address_id = a.address_id
JOIN city AS ci ON a.city_id = ci.city_id
UNION
SELECT
ci.city
FROM staff AS s
JOIN address AS a ON s.address_id = a.address_id
JOIN city AS ci ON a.city_id = ci.city_id
ORDER BY city;
Результат: вы получаете уникальный список городов без повторов, даже если в одном городе живут и клиенты, и сотрудники.
Если вам важны все источники данных, используйте UNION ALL:
SELECT
ci.city
FROM customer AS c
JOIN address AS a ON c.address_id = a.address_id
JOIN city AS ci ON a.city_id = ci.city_id
UNION ALL
SELECT
ci.city
FROM staff AS s
JOIN address AS a ON s.address_id = a.address_id
JOIN city AS ci ON a.city_id = ci.city_id
ORDER BY city;
Примечание: UNION ALL полезен, когда дубликаты несут смысл — например, если вы хотите потом посчитать количество строк по объединенному списку.
Когда выбирать UNION ALL
UNION ALL обычно быстрее, чем UNION, потому что СУБД не тратит время на удаление дубликатов. Поэтому, если вам не нужна уникализация, почти всегда лучше использовать именно его.
INTERSECT
INTERSECT возвращает только те строки, которые присутствуют в обоих наборах результатов. Это удобно, когда нужно найти пересечение двух списков.
Пример: города, где живут и клиенты, и сотрудники
SELECT
ci.city
FROM customer AS c
JOIN address AS a ON c.address_id = a.address_id
JOIN city AS ci ON a.city_id = ci.city_id
INTERSECT
SELECT
ci.city
FROM staff AS s
JOIN address AS a ON s.address_id = a.address_id
JOIN city AS ci ON a.city_id = ci.city_id
ORDER BY city;
Результат: вы увидите только те города, которые встречаются и среди клиентов, и среди сотрудников.
Где это полезно
INTERSECT удобно применять для поиска общего сегмента между двумя аудиториями, сравнения списков из разных систем или проверки совпадений между выборками.
EXCEPT
EXCEPT возвращает строки из первого набора, которых нет во втором. Это оператор разности множеств.
Пример: города, где живут клиенты, но нет сотрудников
SELECT
ci.city
FROM customer AS c
JOIN address AS a ON c.address_id = a.address_id
JOIN city AS ci ON a.city_id = ci.city_id
EXCEPT
SELECT
ci.city
FROM staff AS s
JOIN address AS a ON s.address_id = a.address_id
JOIN city AS ci ON a.city_id = ci.city_id
ORDER BY city;
Результат: вы получите список городов, где есть клиенты, но нет сотрудников.
Важное замечание
В некоторых СУБД EXCEPT может называться MINUS или поддерживаться только в определенных версиях. Если вы пишете переносимый SQL, это стоит проверять отдельно.
Практическое применение
Наборные операции особенно полезны в аналитике и проверке данных.
UNIONпомогает собрать единый справочный список из нескольких источников.UNION ALLудобен для объединения потоков данных перед последующей агрегацией.INTERSECTпоказывает общий сегмент или совпадающие записи.EXCEPTпомогает найти расхождения, пропуски и «лишние» значения.
Иногда наборные операции можно заменить JOIN, но это не всегда удобно. Если вам нужно именно сравнить результаты запросов, а не связать таблицы по ключам, операции над наборами данных читаются намного проще.
Когда несколько OR лучше переписать через UNION
Иногда один длинный WHERE с несколькими условиями OR становится трудным для чтения и сопровождения. В таких случаях запрос можно разбить на отдельные логические ветки и объединить их через UNION.
Такой подход особенно удобен, когда:
- каждая ветка описывает отдельный бизнес-сценарий;
- условия сильно отличаются по смыслу;
- вы хотите упростить чтение и дальнейшую поддержку запроса.
Пример: найти фильмы, которые либо имеют рейтинг R, либо длиннее 180 минут.
SELECT
title,
rating,
length
FROM film
WHERE rating = 'R'
UNION
SELECT
title,
rating,
length
FROM film
WHERE length > 180
ORDER BY title;
Результат: вместо одного сложного WHERE ... OR ... вы получаете два понятных запроса, которые легче читать, менять и тестировать. Если строки могут попадать в обе ветки, UNION уберет дубликаты автоматически. Если дубликаты не страшны и условия не пересекаются, можно использовать UNION ALL.
Примечание: если условия относятся к одному и тому же столбцу, часто достаточно IN (...). UNION полезен именно тогда, когда ветки запроса логически разные или опираются на разные поля.
Вопросы для собеседования
В чем разница между UNION и UNION ALL?
UNION объединяет результаты двух запросов и удаляет дубликаты, а UNION ALL сохраняет все строки. На практике UNION ALL обычно работает быстрее, потому что не выполняет дополнительную обработку для поиска повторов.
Почему для операций над наборами данных нужны совместимые SELECT-запросы?
Потому что SQL объединяет результат по позициям столбцов, а не по именам. Если у двух запросов разное число столбцов или несовместимые типы данных, СУБД не сможет корректно собрать итоговый набор.
Когда лучше использовать INTERSECT, а когда EXCEPT?
INTERSECT подходит, когда нужно найти общие строки двух списков. EXCEPT полезен, когда нужно отнять второй список от первого и увидеть только те значения, которые остались.
Чем наборные операции отличаются от JOIN?
JOIN связывает строки по ключам и обычно расширяет одну строку данными из другой таблицы. Наборные операции работают с целыми результатами запросов и сравнивают их как множества, что удобно для объединения списков, поиска совпадений и выявления расхождений.
Ключевые выводы урока
UNIONобъединяет результаты и удаляет дубликаты.UNION ALLобъединяет результаты без удаления дубликатов.INTERSECTоставляет только общие строки двух наборов.EXCEPTвозвращает строки, которые есть в первом наборе и отсутствуют во втором.- Для всех наборных операций нужны совместимые
SELECT-запросы с одинаковым числом столбцов. ORDER BYдля итогового результата пишут в самом конце всего выражения.- В прикладной аналитике такие операции удобны для объединения списков, поиска пересечений и сравнения данных между источниками.
В следующем уроке мы перейдем к подзапросам и посмотрим, как использовать вложенные SELECT для более гибких условий и вычислений.