
Команда DISTINCT в SQL часто применяется для удаления повторяющихся строк, но в больших таблицах с миллионами записей её использование может приводить к значительным задержкам. Вместо этого можно применять GROUP BY, что позволяет одновременно агрегировать данные и получать уникальные значения без полной сортировки всей таблицы.
Оконные функции, такие как ROW_NUMBER() OVER (PARTITION BY …), дают возможность выбрать уникальные строки по заданным критериям, сохраняя при этом доступ ко всем столбцам. Такой подход особенно полезен при работе с аналитическими запросами, где важно сохранить контекст каждой записи.
Подзапросы с EXISTS позволяют фильтровать дублирующиеся данные на этапе проверки наличия записи, что снижает нагрузку на базу при больших объемах данных. Аналогично, объединение выборок через UNION обеспечивает получение уникальных значений без использования DISTINCT, а UNION ALL может применяться вместе с фильтрацией для ускорения запроса.
Агрегатные функции, такие как MIN, MAX или COUNT, в сочетании с группировкой позволяют формировать списки уникальных записей по ключевым полям без полного удаления дубликатов на уровне всей строки. Создание временных таблиц с уникальными значениями дополнительно снижает повторные вычисления и ускоряет многократное использование данных в сложных запросах.
Использование GROUP BY для удаления дубликатов
Команда GROUP BY группирует строки по указанным столбцам, что позволяет получать уникальные комбинации значений без применения DISTINCT. Например, чтобы получить список уникальных клиентов по городам, достаточно написать:
SELECT city, customer_name FROM customers GROUP BY city, customer_name;
При использовании GROUP BY можно одновременно применять агрегатные функции, такие как COUNT, MAX или MIN, что позволяет не только удалить дубликаты, но и подсчитать количество записей или выбрать крайние значения для каждой группы. Это снижает необходимость в дополнительных подзапросах.
Важно учитывать, что GROUP BY формирует уникальные группы по совокупности указанных полей. Если необходимо исключить дубликаты только по одному столбцу, остальные поля должны быть агрегированы или включены в группу, иначе SQL выдаст ошибку. Такой подход улучшает контроль над структурой данных и ускоряет выполнение запросов на больших таблицах.
Применение оконных функций ROW_NUMBER и PARTITION

Оконная функция ROW_NUMBER() в сочетании с PARTITION BY позволяет присвоить уникальный номер каждой строке внутри заданной группы. Это помогает отбирать только первую или последнюю запись из дубликатов без использования DISTINCT. Например, чтобы выбрать уникальные заказы по клиенту по дате создания:
SELECT * FROM (SELECT order_id, customer_id, order_date, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS rn FROM orders) sub WHERE rn = 1;
Такой подход удобен, когда требуется сохранить все столбцы записи, а не только те, что участвуют в группировке. ROW_NUMBER позволяет гибко фильтровать дубли по критериям сортировки, например выбирать самый новый или самый старый элемент для каждой группы.
При работе с большими таблицами рекомендуется создавать индексы на столбцах, участвующих в PARTITION BY и ORDER BY. Это ускоряет выполнение запроса и снижает нагрузку на сервер при выборке уникальных записей.
Фильтрация через EXISTS и подзапросы
Оператор EXISTS используется для проверки наличия записей в подзапросе и позволяет отбирать уникальные строки без применения DISTINCT. Например, чтобы выбрать клиентов, которые сделали хотя бы один заказ, можно написать:
SELECT * FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);
Подзапросы также позволяют фильтровать дублирующиеся данные на основе сложных условий. Например, можно выбрать только последние заказы каждого клиента:
SELECT * FROM orders o WHERE o.order_date = (SELECT MAX(order_date) FROM orders WHERE customer_id = o.customer_id);
EXISTS выполняется до возврата основной строки, что делает его особенно полезным для больших таблиц. Подзапросы можно оптимизировать, используя индексы на ключевых столбцах, участвующих в соединении, чтобы снизить нагрузку на базу и ускорить выполнение запроса.
Объединение результатов с UNION и UNION ALL

Оператор UNION объединяет результаты нескольких запросов и автоматически удаляет дубликаты, обеспечивая уникальность строк без использования DISTINCT. Например, чтобы получить список всех клиентов из двух таблиц:
SELECT customer_name FROM customers_us UNION SELECT customer_name FROM customers_eu;
UNION ALL возвращает все строки, включая дубликаты, но его можно сочетать с подзапросом или оконными функциями для последующей фильтрации повторов. Это ускоряет выполнение на больших объемах данных, так как не происходит сортировка и удаления повторов на этапе объединения.
При использовании UNION и UNION ALL важно, чтобы количество и тип столбцов в объединяемых запросах совпадали. Для оптимизации можно создавать индексы на столбцах, участвующих в объединении, что снижает нагрузку на сервер и ускоряет выборку уникальных значений.
Удаление повторов с помощью агрегатных функций

Агрегатные функции позволяют получать уникальные значения по ключевым столбцам без применения DISTINCT. Наиболее часто используются MIN, MAX, COUNT и SUM в сочетании с GROUP BY.
Примеры применения:
- Выбор самой ранней даты заказа для каждого клиента:
SELECT customer_id, MIN(order_date) FROM orders GROUP BY customer_id; - Определение максимальной суммы транзакции для каждого пользователя:
SELECT user_id, MAX(amount) FROM transactions GROUP BY user_id; - Подсчет количества уникальных товаров в заказе:
SELECT order_id, COUNT(product_id) FROM order_items GROUP BY order_id;
Использование агрегатных функций помогает одновременно удалить дубликаты и получить аналитические данные. Для повышения производительности рекомендуется создавать индексы на столбцах, участвующих в группировке, особенно при работе с большими таблицами.
Создание временных таблиц для уникальных записей

Временные таблицы позволяют сохранять уникальные записи отдельно от основной таблицы, что ускоряет последующие запросы и упрощает работу с дублирующимися данными. Для создания временной таблицы с уникальными клиентами можно использовать следующий синтаксис:
CREATE TEMPORARY TABLE unique_customers AS SELECT customer_id, customer_name FROM customers GROUP BY customer_id, customer_name;
После создания временной таблицы можно выполнять любые выборки или соединения, не затрагивая исходные данные, и использовать агрегатные функции для аналитики без повторной фильтрации дубликатов.
Рекомендуется создавать индексы на ключевых столбцах временной таблицы, если планируется многократное использование. Временные таблицы автоматически удаляются по завершении сессии, что обеспечивает чистоту базы и снижает нагрузку на сервер при больших объёмах данных.
Вопрос-ответ:
Почему использование DISTINCT может замедлять запросы на больших таблицах?
Команда DISTINCT требует сортировки или хэширования всех строк для удаления повторов. В таблицах с миллионами записей это создает значительную нагрузку на память и процессор. Альтернативы, такие как GROUP BY, оконные функции или временные таблицы, позволяют выбирать уникальные значения без полной сортировки всей таблицы, что снижает время выполнения.
В каких случаях имеет смысл использовать UNION вместо DISTINCT?
Оператор UNION объединяет результаты нескольких запросов и автоматически удаляет дубликаты. Это полезно, если уникальные данные нужно собрать из разных таблиц или выборок. При этом можно использовать UNION ALL для ускорения запроса, а дубликаты фильтровать на следующем этапе с помощью подзапросов или оконных функций.
Как временные таблицы помогают работать с уникальными данными?
Временные таблицы позволяют сохранить уникальные записи отдельно от основной базы, что ускоряет повторные выборки и сложные соединения. Например, можно создать временную таблицу с уникальными клиентами и потом использовать её для аналитических запросов или объединений с другими таблицами. Индексация ключевых столбцов временной таблицы дополнительно снижает нагрузку на сервер.
