ПрограммированиеSQL аналитик

Каким образом реализовать выборку только уникальных записей из сложной структуры данных с несколькими дубликатами по разным столбцам, и какова специфика работы DISTINCT vs GROUP BY vs ROW_NUMBER()?

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

Ответ.

Извлечение уникальных записей в SQL стало критично важной задачей с массовым переходом организаций на хранение мультиизмеряемых данных. Иногда требуется вывести неповторяющиеся строки по комбинации нескольких столбцов, иногда — лишь по одному ключевому.

История вопроса:

Первые версии SQL предлагали только DISTINCT для фильтрации дубликатов. Затем появились структурные приёмы, в том числе GROUP BY для агрегаций по уникальным наборам значений и оконные функции типа ROW_NUMBER() для более гибких сценариев работы с дубликатами, например: выборка по "последней" или "первой" записи.

Проблема:

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

Решение:

  • Используйте DISTINCT для получения уникальных строк по необходимым полям.
  • GROUP BY — когда нужны агрегаты (например, сумма или дата по уникальным парам).
  • Оконные функции (ROW_NUMBER()) — для задач типа отбора "одной строки из группы дубликатов по какому-то критерию".

Пример кода:

Получить одну последнюю запись о заказах по каждому клиенту:

WITH OrdersRank AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY OrderDate DESC) as rn FROM Orders ) SELECT * FROM OrdersRank WHERE rn = 1;

Ключевые особенности:

  • DISTINCT — возвращает уникальные строки только по тем полям, что указаны в SELECT.
  • GROUP BY — обязателен, если нужна агрегация.
  • ROW_NUMBER() — максимально гибок для отбора строк с нужным приоритетом/датой/версионностью.

Вопросы с подвохом.

Можно ли использовать DISTINCT вместе с агрегатными функциями без GROUP BY?

Нет, агрегатные функции требуют группировки, иначе будет ошибка синтаксиса.

SELECT COUNT(DISTINCT CustomerID) -- корректно SELECT SUM(Amount), DISTINCT CustomerID -- ошибка!

Что произойдет, если в GROUP BY не указывать все неагрегируемые поля из SELECT?

Это вызовет ошибку в большинстве СУБД: все поля в SELECT, кроме агрегатных, должны быть перечислены в GROUP BY.

Можно ли "убрать" дубликаты с помощью оконных функций без подзапроса?

Нет: использование ROW_NUMBER() внутри одного SELECT не фильтрует автоматически «повторы», необходим внешний запрос для выбора нужных строк.

Типовые ошибки и анти-паттерны

  • Использование DISTINCT при большом числе столбцов и строк — резкое падение производительности.
  • GROUP BY без нужного агрегата — бессмысленно и ресурсоёмко.
  • Оконные функции без последующего фильтра — данные возвращаются с повторами.

Пример из жизни

Негативный кейс

Выбрали DISTINCT по всем столбцам для таблицы в 20 млн строк: запрос работал часами, Итог — тайм-аут или падение производительности БД.

Плюсы:

  • Просто написать.

Минусы:

  • Крайне неэффективно на больших данных.

Позитивный кейс

Использовали оконные функции: получили только нужную последнюю запись на клиента за миллисекунды; предыдущие и повторяющиеся не грузились.

Плюсы:

  • Высочайшая производительность.
  • Гибкость.

Минусы:

  • Требует грамотной архитектуры запроса и знания оконных функций.