INTERSECT
Поиск строк, которые одновременно есть в результатах нескольких SELECT-запросов
Теория
Оператор INTERSECT применяется для того, чтобы из нескольких SELECT-запросов извлечь только те строки, которые присутствуют в каждом из них. Сначала каждый запрос генерирует свой набор строк, а затем PostgreSQL находит пересечение этих наборов.
INTERSECT отвечает на вопрос: какие строки есть и в первом результате SELECT запроса, и во втором. Запросов может быть и больше, тогда пересечение по совпадающим строкам определяется по всем запросам.
Оператор применяется, когда необходимо выявить общие значения в двух наборах данных. Например, можно взять имена из таблицы users, затем имена из таблицы workers и оставить только те имена, которые встречаются и там, и там. Таким образом мы найдем одинаковые имена как для клиентов, так и для сотрудников. Или можно сравнить две выборки из одной таблицы по разным условиям и посмотреть, какие строки одновременно подходят под оба фильтра. Например, определить клиентов, которые совершали покупки как в апреле, так и в мае месяце 2025 года.
INTERSECT убирает полные дубликаты строк. Если одна и та же строка встречается в обеих выборках много раз, в итоговом результате она всё равно останется один раз.
В PostgreSQL есть и вариант INTERSECT ALL. Он тоже ищет общие строки, но работает с повторами немного иначе. Если одна строка встретилась в первой выборке три раза, а во второй два раза, в результате через INTERSECT ALL она останется два раза.
У этой операции те же базовые ограничения, что и у UNION:
• количество столбцов в объединяемых запросах должно совпадать;
• типы данных в соответствующих столбцах должны совпадать или быть совместимыми;
• PostgreSQL сравнивает столбцы по позиции, а не по имени.
INTERSECT отвечает на вопрос: какие строки есть и в первом результате SELECT запроса, и во втором. Запросов может быть и больше, тогда пересечение по совпадающим строкам определяется по всем запросам.
Оператор применяется, когда необходимо выявить общие значения в двух наборах данных. Например, можно взять имена из таблицы users, затем имена из таблицы workers и оставить только те имена, которые встречаются и там, и там. Таким образом мы найдем одинаковые имена как для клиентов, так и для сотрудников. Или можно сравнить две выборки из одной таблицы по разным условиям и посмотреть, какие строки одновременно подходят под оба фильтра. Например, определить клиентов, которые совершали покупки как в апреле, так и в мае месяце 2025 года.
INTERSECT убирает полные дубликаты строк. Если одна и та же строка встречается в обеих выборках много раз, в итоговом результате она всё равно останется один раз.
В PostgreSQL есть и вариант INTERSECT ALL. Он тоже ищет общие строки, но работает с повторами немного иначе. Если одна строка встретилась в первой выборке три раза, а во второй два раза, в результате через INTERSECT ALL она останется два раза.
У этой операции те же базовые ограничения, что и у UNION:
• количество столбцов в объединяемых запросах должно совпадать;
• типы данных в соответствующих столбцах должны совпадать или быть совместимыми;
• PostgreSQL сравнивает столбцы по позиции, а не по имени.
Синтаксис
SELECT column1, column2 -- формируем первый набор строк
FROM table_1
INTERSECT -- оставляет только строки, которые есть в обоих наборах
SELECT column1, column2 -- формируем второй набор строк
FROM table_2;
SELECT column1
FROM table_1
INTERSECT ALL -- оставляет совпадающие строки с учётом повторений
SELECT column1
FROM table_2
ORDER BY column1; -- сортирует общий результат
SQL
Каждая часть конструкции INTERSECT — это обычный SELECT-запрос. Сначала пишется первый запрос, затем оператор INTERSECT, потом второй запрос. Если нужно, таких частей может быть и больше.
PostgreSQL сопоставляет столбцы по позиции:
• первый столбец первого запроса сравнивается с первым столбцом второго;
• второй — со вторым;
• третий — с третьим.
Оба запроса должны возвращать одинаковое количество столбцов в одинаковом порядке и с совместимыми типами данных.
В PostgreSQL имена столбцов берутся из первой части. Если требуется получить осмысленный псевдоним для результата, его следует указывать в первом запросе.
Если типы данных не совпадают, их можно привести явно. Например, запись worker_id::text позволяет сравнивать числовой идентификатор с текстовым значением в другой части запроса. Если этого не сделать, PostgreSQL может вернуть ошибку.
Если количество столбцов разное, запрос не выполнится. При необходимости структуру можно выровнять вручную: добавить NULL AS some_column или константу в ту часть, где столбца не хватает. Хотя, в отличие от UNION, в этом мало пользы.
WHERE, вычисления, функции и псевдонимы пишутся отдельно внутри каждого SELECT. А вот ORDER BY и LIMIT обычно ставят в самом конце всей конструкции, когда общее пересечение уже найдено.
Если сортировка или LIMIT нужны именно внутри отдельной части запроса до пересечения, эту часть придётся обернуть в скобки. Иначе PostgreSQL выдаст ошибку.
PostgreSQL сопоставляет столбцы по позиции:
• первый столбец первого запроса сравнивается с первым столбцом второго;
• второй — со вторым;
• третий — с третьим.
Оба запроса должны возвращать одинаковое количество столбцов в одинаковом порядке и с совместимыми типами данных.
В PostgreSQL имена столбцов берутся из первой части. Если требуется получить осмысленный псевдоним для результата, его следует указывать в первом запросе.
Если типы данных не совпадают, их можно привести явно. Например, запись worker_id::text позволяет сравнивать числовой идентификатор с текстовым значением в другой части запроса. Если этого не сделать, PostgreSQL может вернуть ошибку.
Если количество столбцов разное, запрос не выполнится. При необходимости структуру можно выровнять вручную: добавить NULL AS some_column или константу в ту часть, где столбца не хватает. Хотя, в отличие от UNION, в этом мало пользы.
WHERE, вычисления, функции и псевдонимы пишутся отдельно внутри каждого SELECT. А вот ORDER BY и LIMIT обычно ставят в самом конце всей конструкции, когда общее пересечение уже найдено.
Если сортировка или LIMIT нужны именно внутри отдельной части запроса до пересечения, эту часть придётся обернуть в скобки. Иначе PostgreSQL выдаст ошибку.
Примеры
1. Ищем одинаковые имена среди пользователей и сотрудников
Обе части запроса возвращают по одному текстовому столбцу. В результате останутся только те имена, которые встречаются и в таблице users, и в таблице workers.
🔄 Попробуйте изменить запрос:
- • Замените INTERSECT на UNION и сравните, насколько изменится сам смысл результата
- • Добавьте в первый запрос фильтр WHERE registration_date >= '2023-01-01' и посмотрите, как изменится пересечение
2. Ищем пользователей, у которых есть и доставленные, и отменённые заказы
Первый запрос возвращает пользователей с доставленными заказами, второй — с отменёнными. После INTERSECT останутся только те user_id, которые встретились в обоих наборах. Используя логический оператор AND в WHERE такое условие уже не выразить, потому что один заказ не может иметь оба статуса сразу.
🔄 Попробуйте изменить запрос:
- • Выполните сначала первый запрос отдельно, потом второй отдельно, а затем оба через INTERSECT как в примере. Объясните результат
- • Замените во втором запросе статус 'отменён' на 'доставлен' и сравните новый результат
3. Ищем пользователей, которые делали заказы как в апреле, так и в мае
Первая выборка возвращает пользователей с апрельскими заказами 2023 года, вторая — с майскими. После INTERSECT останутся только те user_id, которые встретились в обоих месяцах.
🔄 Попробуйте изменить запрос:
- • 1. Выполните сначала апрельский запрос отдельно, потом майский отдельно, а затем оба через INTERSECT как в примере. Объясните результат
- • 2. Замените INTERSECT на UNION и сравните, как изменится смысл результата
4. Найдем одноименцев и однофамильцев среди клиентов и сотрудников
Ищем совпадающие имя и фамилию среди клиентов и сотрудников. Первая часть возвращает пары first_name + last_name из таблицы users, вторая — такие же пары из таблицы workers. После INTERSECT останутся только те ФИ, которые встретились в обеих таблицах. В этом примере мы нашли совпадающие строки, состоящие из двух столбцов.
🔄 Попробуйте изменить запрос:
- • Уберите второй столбец last_name из одного из запросов и посмотрите на ошибку
- • Оставьте в запросах только first_name и сравните, как изменится результат
5. Приводим типы, если сравниваем значения разной природы
В этом примере обе части приводят идентификаторы к text. Такой приём необходим, когда без явного приведения PostgreSQL не сможет сопоставить разные типы и выполнить пересечение.
🔄 Попробуйте изменить запрос:
- • Уберите приведение ::text у одного из столбцов и проверьте, выполнится ли запрос
6. Если LIMIT нужен внутри каждой части, используем скобки
Здесь ORDER BY и LIMIT относятся не ко всему пересечению сразу, а к каждой части отдельно. Без скобок такая запись приведёт к ошибке.
🔄 Попробуйте изменить запрос:
- • Уберите скобки и выполните запрос, чтобы увидеть ошибку PostgreSQL
- • Оставьте скобки, но уберите внутренние LIMIT и сравните результат
Типичные ошибки
Путают INTERSECT и UNION
INTERSECT не складывает все строки вместе, а оставляет только общее пересечение. Если нужен общий список всех строк, это уже задача для UNION или UNION ALL.
Ждут, что строка попадёт в результат, если совпал только один столбец
Если в запросе участвуют два или три столбца, PostgreSQL сравнивает всю строку целиком по позициям. Совпадения только по одному полю недостаточно.
Возвращают разное количество столбцов
Если в одной части запроса два столбца, а в другой один, INTERSECT не выполнится. Структура результата должна совпадать.
Не приводят типы, когда это нужно
Если одна часть возвращает число, а другая текст, PostgreSQL не всегда сможет автоматически сопоставить разные типы. В таких случаях помогает явное приведение, например worker_id::text.
Ставят ORDER BY внутри каждой части без скобок
По умолчанию сортировка ставится в конце всей конструкции. Если нужно сортировать и ограничивать каждую часть до пересечения, её нужно обернуть в скобки.
Путают INTERSECT и INTERSECT ALL
Обычный INTERSECT убирает полные дубликаты строк. INTERSECT ALL тоже ищет пересечение, но может сохранить повторы, если они есть.
Практика
Проверь себя
Ответьте на вопросы, чтобы закрепить материал:
1
Что делает INTERSECT?
2
Чем INTERSECT отличается от INTERSECT ALL?
3
Что произойдёт, если в первом запросе вернуть два столбца, а во втором только один?
4
Как PostgreSQL сравнивает строки, если в обеих частях запроса выбраны два столбца?
5
Первый запрос вернул один столбец со значениями: Москва, Казань, Самара, Казань, Омск. Второй запрос вернул один столбец со значениями: Казань, Омск, Омск, Пермь. Сколько строк вернёт результат после применения INTERSECT? Введите только число.
6
Первый запрос вернул один столбец со значениями: 1, 1, 2, 2, 2, 5, 7. Второй запрос вернул один столбец со значениями: 1, 2, 2, 3, 5, 5. Сколько строк вернёт результат после применения INTERSECT ALL? Введите только число.
7
Используя таблицу products, выведите категории, в которых есть и товары дороже 70000, и товары не дороже 50000. Выведите только category и отсортируйте результат по возрастанию.
8
Используя таблицу order_items, выведите идентификаторы заказов, в которых покупали и товар с product_id = 1, и товар с product_id = 31. Выведите только order_id и отсортируйте результат по возрастанию.