Программирование SQLDML и запросыРазработчик SQL и серверной логики

На каком этапе логического выполнения SELECT устраняются дубликаты при использовании DISTINCT?

На каком этапе логического выполнения SELECT устраняются дубликаты при использовании DISTINCT?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

DISTINCT логически применяется после формирования значений, указанных в списке SELECT, и после фильтрации WHERE. Поэтому он удаляет дубликаты уже среди прошедших фильтр результирующих строк, но не влияет на то, какие исходные строки были отфильтрованы.

Исторический контекст

Реляционная модель описывает данные как множество, где одинаковые строки не повторяются. Однако SQL обычно использует модель мультимножеств: дубликаты сохраняются, потому что это удобнее для запросов и часто дешевле вычислительно.

DISTINCT появился как явный способ перейти к результату без повторяющихся строк. Это позволяет не выполнять устранение дубликатов там, где оно не требуется.

Постановка проблемы

При соединениях таблиц одна логическая сущность может появиться в результате несколько раз. Например, один клиент с несколькими заказами даст несколько строк при соединении клиентов с заказами.

Если добавить DISTINCT, повторяющиеся значения выбранных столбцов исчезнут. Но фильтр WHERE всё равно будет применён раньше: DISTINCT не может вернуть строки, которые фильтр уже исключил.

Подробное решение

Логический порядок для типичного запроса можно представить так: FROM и соединения, WHERE, группировка и агрегирование, HAVING, формирование списка SELECT, DISTINCT, ORDER BY, ограничение результата.

Следовательно, WHERE работает с исходными строками после формирования источника данных, а DISTINCT сравнивает уже сформированные строки результата. Сравниваются значения всех выражений из списка SELECT, а не все столбцы исходных таблиц.

Например:

SELECT DISTINCT c.id, c.name FROM customers AS c JOIN orders AS o ON o.customer_id = c.id WHERE o.status = 'paid';

Сначала выбираются только оплаченные заказы. Затем для соответствующих клиентов формируются пары id, name, и одинаковые пары объединяются в одну строку.

Важно, что DISTINCT не гарантирует порядок строк. Для предсказуемого порядка нужен отдельный ORDER BY.

Также DISTINCT может быть дорогим: СУБД приходится сортировать результат или строить структуру для проверки уникальности. Если задача состоит в проверке существования связанной строки, EXISTS часто точнее выражает намерение и может избежать размножения строк, хотя конкретный план зависит от СУБД и индексов.

Ситуация из практики

В отчёте требовался список клиентов, у которых есть хотя бы один оплаченный заказ. Разработчик соединил таблицы клиентов и заказов, а затем добавил DISTINCT, потому что клиенты с несколькими заказами повторялись.

Вариант с DISTINCT прост и корректен, если нужны именно выбранные уникальные значения. Его минус — возможная сортировка или хеширование большого промежуточного результата.

Вариант с проверкой существования лучше отражает бизнес-условие «есть хотя бы один заказ» и не создаёт по одной результирующей строке на каждый заказ. В таком случае был выбран EXISTS; после добавления индекса по клиенту и статусу план обработки стал работать с меньшим объёмом данных.

Что кандидаты часто упускают

  1. Удаляет ли DISTINCT дубликаты до применения WHERE?

Нет. Логически сначала применяется WHERE, и только затем из прошедших строк формируется набор значений SELECT, в котором устраняются дубликаты. Поэтому DISTINCT не может восстановить строку, исключённую условием фильтра.

  1. По каким столбцам определяется дубликат?

По всей совокупности выражений в списке SELECT. Если выбран идентификатор клиента, имя и регион, то строки с одинаковым именем, но разными идентификатором или регионом дубликатами не считаются.

Выражение в списке SELECT также учитывается по своему результату. Поэтому добавление дополнительного столбца или вычисляемого значения может увеличить количество строк после DISTINCT.

  1. Гарантирует ли DISTINCT одну строку на объект исходной таблицы?

Нет. Он гарантирует уникальность только выбранных значений, а не уникальность сущностей по их первичному ключу. Если в результат включены столбцы заказа, даты или другого связанного объекта, строки одного клиента могут оставаться различными.

Чтобы получить одну строку на сущность, нужно явно определить правило выбора: использовать группировку, агрегирование, проверку существования или оконную функцию с последующей фильтрацией. Одного DISTINCT для такого требования недостаточно.