DISTINCT убирает дубликаты строк
По умолчанию SELECT возвращает все подходящие строки — со всеми повторами. Ключевое слово DISTINCT говорит SQLite схлопнуть строки, у которых значения во всех выбранных столбцах совпадают, чтобы каждая уникальная комбинация попала в результат лишь один раз.
Пять строк на входе — три на выходе. SQLite посмотрел на колонку customer, выкинул повторы и вернул по одной строке на каждое уникальное значение. Порядок при этом не гарантируется — если он вам важен, добавляйте ORDER BY.
DISTINCT работает по всему списку SELECT
Вот тут многие спотыкаются. DISTINCT не выбирает одну колонку для удаления дубликатов — он убирает повторы по всей строке целиком, учитывая каждую колонку, которую вы перечислили в SELECT.
Каждая уникальная пара (customer, country) встречается ровно один раз. Если один и тот же клиент попадётся с двумя разными странами, вы увидите обе строки — для SQLite это не дубликаты.
Синтаксиса DISTINCT(customer), который бы игнорировал остальные колонки, не существует. Скобки выглядят заманчиво, но SELECT DISTINCT(customer), country парсится точно так же, как SELECT DISTINCT customer, country — скобки здесь просто группируют выражение. Если вам действительно нужна одна строка на клиента с какой-то выбранной страной, это уже задача для GROUP BY вместе с агрегатной функцией.
COUNT(DISTINCT col) — считаем уникальные значения
Частая задача: сколько уникальных значений в колонке? COUNT(*) считает строки, COUNT(col) — значения, отличные от NULL, а COUNT(DISTINCT col) — уникальные значения, не считая NULL.
Пять заказов, три уникальных клиента, три уникальные страны. COUNT(DISTINCT ...) — самая полезная агрегатная форма DISTINCT. Её достают всякий раз, когда нужно посчитать, «сколько разных штук вообще встретилось».
Учтите: в SQLite внутрь COUNT(DISTINCT ...) можно передать только один столбец. Если хочется посчитать уникальные комбинации по нескольким колонкам, оберните это в подзапрос: SELECT COUNT(*) FROM (SELECT DISTINCT a, b FROM t).
Как DISTINCT обходится с NULL
У NULL в SQL репутация чудаковатая: выражение NULL = NULL возвращает не TRUE, а NULL. Но для DISTINCT сделано особое исключение — при удалении дубликатов все NULL считаются равными друг другу.
Возвращается три строки: 'ada@example.com', 'dan@example.com' и одна-единственная NULL. Три NULL-овых адреса схлопнулись в один. То же правило работает и для GROUP BY, и для операций над множествами вроде UNION — полезно держать в голове, когда ловишь баг в духе «почему NULL-строка приходит один раз вместо трёх?».
DISTINCT отрабатывает до ORDER BY и LIMIT
Логический порядок выполнения клауз в SELECT такой: FROM → WHERE → GROUP BY → HAVING → SELECT/DISTINCT → ORDER BY → LIMIT. То есть сначала DISTINCT убирает дубликаты, потом ORDER BY сортирует то, что осталось, и только в конце LIMIT обрезает результат.
WHERE оставляет четыре строки, DISTINCT схлопывает дубликаты Бориса, ORDER BY сортирует по алфавиту, а LIMIT забирает первые две. Стоит хотя бы раз пройтись по этой цепочке мысленно — путаница с порядком результата чаще всего возникает именно из-за того, что забывают, какой шаг когда выполняется.
DISTINCT или GROUP BY: в чём разница
Если задача — просто убрать дубликаты, оба запроса вернут одно и то же:
Результат тот же. Разница — в том, что можно делать дальше:
DISTINCTнужен ровно для одного: «дай мне уникальные строки».GROUP BY— это уже «сгруппируй строки и посчитай что-нибудь по каждой группе»:COUNT(*),SUM(amount),MAX(created_at)и так далее.
Если вы тянетесь за DISTINCT, а потом понимаете, что заодно хочется ещё и сумму по каждому клиенту, — это явный сигнал переходить на GROUP BY:
По одной строке на клиента и нужные нам агрегаты в придачу. С DISTINCT так не получится — у него просто нет способа выразить «одна строка на группу плюс сумма».
На что обратить внимание
- Производительность. Чтобы найти дубликаты, SQLite обычно сортирует или хеширует строки. На больших выборках выручает индекс по тем колонкам, по которым идёт дедупликация. Если вы пишете
SELECT DISTINCTсразу по всем колонкам широкой таблицы — стоит задуматься, точно ли вам нужны все они. DISTINCT *— редкий случай. Сама конструкция допустима:SELECT DISTINCT * FROM tубирает повторяющиеся строки целиком. Но если у таблицы есть первичный ключ, каждая строка и так уникальна, так что толку от такого запроса никакого.- Не путайте с
UNIQUE.UNIQUE— это ограничение на уровне таблицы, которое в принципе не даёт вставить дубликаты. АDISTINCT— фильтр на этапе запроса, который просто прячет дубликаты в результате. Инструменты разные, задачи разные.
Дальше: выражения CASE
Когда вы уже умеете формировать строки результата с помощью SELECT, WHERE, ORDER BY и DISTINCT, логичный следующий шаг — условная логика внутри самого запроса. Выражения CASE позволяют возвращать разные значения в зависимости от условий — это SQL-аналог цепочки if/else, и как раз о них следующая страница.
Часто задаваемые вопросы
Как работает SELECT DISTINCT в SQLite?
SELECT DISTINCT убирает повторяющиеся строки из результата. SQLite сравнивает все колонки из списка SELECT и оставляет одну строку на каждую уникальную комбинацию значений. Применяется после WHERE и JOIN, но до ORDER BY и LIMIT.
Можно ли применить DISTINCT сразу к нескольким колонкам?
Да, причём DISTINCT всегда работает по всему списку колонок в SELECT, а не по одной из них. Запрос SELECT DISTINCT city, country FROM users вернёт уникальные пары (city, country). Синтаксиса вроде DISTINCT(city), который игнорировал бы остальные колонки, в SQL нет — если нужно именно так, используйте GROUP BY с агрегатной функцией.
Как DISTINCT обрабатывает NULL в SQLite?
Для целей дедупликации DISTINCT считает все NULL равными между собой, поэтому несколько строк с NULL схлопнутся в одну. Это отличается от поведения = в WHERE, где сравнение NULL = NULL даёт неопределённый результат. Такое особое правило действует только для DISTINCT, GROUP BY и UNION.
В чём разница между DISTINCT и GROUP BY в SQLite?
Если задача — просто убрать дубли, то SELECT DISTINCT col и SELECT col FROM t GROUP BY col дадут одинаковый результат. Разница в смысле: DISTINCT пишут, когда нужны только уникальные строки, а GROUP BY — когда вместе с группировкой нужны агрегаты вроде COUNT(*) или SUM(amount) по каждой группе.