DISTINCT: оставляем уникальное
Чему научишься
- убирать повторы из выдачи через
SELECT DISTINCT - понимать, что уникальность считается не по одному «главному» столбцу, а по всей выбранной комбинации столбцов
- получать словарь значений: какие категории, статусы, города или другие варианты вообще встречаются в таблице
- отличать задачи для
DISTINCTот задач дляGROUP BY - понимать, что
DISTINCTне сортирует результат сам по себе - объяснять, почему
DISTINCTможет быть дорогим на больших таблицах - распознавать :
DISTINCT, добавленный только для маскировки дублей послеJOIN
Только уникальные значения
В большом зале хранилища у каждого звука есть эхо. Один и тот же статус повторяется в тысячах заказов. Одна и та же категория тянется через сотни товаров. Один и тот же город вспыхивает в профилях разных пользователей.
Когда тебе нужен весь поток данных, повторы нормальны.
Например, если ты смотришь заказы, то статус paid может встретиться много раз:
SELECT id, status
FROM orders;
Результат может быть таким:
| id | status |
|---|---|
| 1 | paid |
| 2 | paid |
| 3 | pending |
| 4 | cancelled |
| 5 | paid |
| 6 | pending |
Здесь каждая строка — отдельный заказ. Повторы статусов не ошибка. Просто у нескольких заказов одинаковый статус.
Но иногда нужен не поток заказов, а короткий словарь:
Какие статусы вообще бывают в таблице?
Тогда нам не нужны все повторы paid и pending. Нужно оставить каждое значение один раз.
Для этого используют DISTINCT:
SELECT DISTINCT status
FROM orders;
Результат:
| status |
|---|
| paid |
| pending |
| cancelled |
DISTINCT убирает повторяющиеся строки из результата запроса.
Он не меняет данные в таблице.
Он не удаляет строки из базы.
Он только очищает выдачу от дублей.
КВЕРИ: Эхо архива не всегда шум. Иногда оно показывает частоту событий. Но если тебе нужен словарь, а не хор повторов, включай глушитель —
DISTINCT.

DISTINCT гасит эхо архива: тысячи повторов схлопываются в короткий словарь уникальных значений.Где пишется DISTINCT
DISTINCT пишется сразу после SELECT:
SELECT DISTINCT status
FROM orders;
Это важно.
Не так:
SELECT status DISTINCT
FROM orders;
И не так:
SELECT status
FROM orders
DISTINCT;
Правильная форма:
SELECT DISTINCT столбцы
FROM таблица;
Например, словарь категорий товаров:
SELECT DISTINCT category
FROM products;
Словарь городов пользователей:
SELECT DISTINCT city
FROM users;
Словарь статусов заказов:
SELECT DISTINCT status
FROM orders;
Во всех этих запросах мысль одна:
Покажи, какие значения вообще встречаются, без повторов.
DISTINCT сравнивает строки по всей выбранной комбинации столбцов: каждая комбинация остаётся в результате ровно один раз.orders по одному разу. Среди них — то самое pending, застывшее навсегда.DISTINCT по одному столбцу
Самый простой случай — выбрать уникальные значения одного столбца.
Например, таблица products:
| id | name | category |
|---|---|---|
| 1 | Корм «Лунный тунец» | Корм |
| 2 | Игрушка «Лазерная мышь» | Игрушки |
| 3 | Книга «SQL для котонавтов» | Книги |
| 4 | Миска антигравитационная | Аксессуары |
| 5 | Книга «Память старой Земли» | Книги |
| 6 | Игрушка «Мышь-перехватчик» | Игрушки |
Если написать обычный запрос:
SELECT category
FROM products;
результат покажет категорию каждой строки:
| category |
|---|
| Корм |
| Игрушки |
| Книги |
| Аксессуары |
| Книги |
| Игрушки |
Но если тебе нужен список категорий без повторов:
SELECT DISTINCT category
FROM products;
получится:
| category |
|---|
| Корм |
| Игрушки |
| Книги |
| Аксессуары |
DISTINCT не выбирает «первый товар категории».
Он не группирует товары для подсчётов.
Он просто отвечает на вопрос:
Какие разные значения есть в этом столбце?
DISTINCT по нескольким столбцам
Главная деталь урока:
уникальность считается по всей выбранной строке результата.
Если выбрать один столбец, DISTINCT убирает повторы по одному столбцу.
SELECT DISTINCT category
FROM products;
Но если выбрать два столбца:
SELECT DISTINCT category, status
FROM products;
то DISTINCT будет искать уникальные пары:
category + status
Например, есть таблица:
| id | category | status |
|---|---|---|
| 1 | Книги | active |
| 2 | Книги | active |
| 3 | Книги | archived |
| 4 | Игрушки | active |
| 5 | Игрушки | active |
| 6 | Игрушки | archived |
Запрос:
SELECT DISTINCT category
FROM products;
вернёт:
| category |
|---|
| Книги |
| Игрушки |
А запрос:
SELECT DISTINCT category, status
FROM products;
вернёт:
| category | status |
|---|---|
| Книги | active |
| Книги | archived |
| Игрушки | active |
| Игрушки | archived |
Почему?
Потому что теперь уникальность считается не только по category, а по паре:
category + status
Строки:
Книги + active
Книги + archived
для SQL разные, хотя категория у них одинаковая.
Поэтому DISTINCT не означает «сделай уникальным первый столбец». Он означает:
Убери повторяющиеся строки результата целиком.
Как DISTINCT относится к NULL
NULL означает отсутствие значения, но для DISTINCT несколько строк с NULL в одном и том же столбце считаются повтором.
Например, таблица users:
| id | name | city |
|---|---|---|
| 1 | Анна | Москва |
| 2 | Борис | |
| 3 | Вера | Казань |
| 4 | Глеб | NULL |
| 5 | Дана | Москва |
Запрос:
SELECT DISTINCT city
FROM users;
вернёт примерно такой набор:
| city |
|---|
| Москва |
| Казань |
| NULL |
Две строки с NULL не появятся дважды. В словаре городов будет одна строка NULL.
Это не противоречит прошлому уроку про NULL.
В условиях WHERE сравнение с NULL через = даёт UNKNOWN. Но DISTINCT решает другую задачу: он убирает повторы в готовой выдаче. Для несколько отсутствующих значений в одном столбце схлопываются в одну строку.
Если не хочешь видеть NULL в словаре, добавь фильтр:
SELECT DISTINCT city
FROM users
WHERE city IS NOT NULL
ORDER BY city;
Так результат покажет только заполненные города.
DISTINCT не сортирует результат
DISTINCT убирает повторы, но не отвечает за порядок строк.
Например:
SELECT DISTINCT status
FROM orders;
может вернуть:
| status |
|---|
| pending |
| paid |
| cancelled |
А в другой ситуации порядок может оказаться другим.
Это нормально: SQL-таблица не обязана возвращать строки в «естественном» порядке, если ты явно не попросил сортировку.
Если нужен аккуратный список, добавляй ORDER BY:
SELECT DISTINCT status
FROM orders
ORDER BY status;
Теперь запрос говорит две отдельные вещи:
SELECT DISTINCT status
— убери повторы;
ORDER BY status
— отсортируй результат по статусу.
Не стоит думать, что DISTINCT сам «наводит порядок». Он гасит дубли, а не сортирует.
DISTINCT против GROUP BY
Для простого словаря значений эти два запроса могут дать одинаковый результат:
SELECT DISTINCT category
FROM products;
и:
SELECT category
FROM products
GROUP BY category;
Оба вернут список категорий без повторов.
Но смысл у них разный.
DISTINCT отвечает на вопрос:
Какие значения бывают?
GROUP BY отвечает на вопрос:
На какие группы разбить строки, чтобы потом что-то посчитать по каждой группе?
Например, если тебе нужен просто список категорий, достаточно DISTINCT:
SELECT DISTINCT category
FROM products;
А если нужно узнать, сколько товаров в каждой категории, нужен GROUP BY:
SELECT category, COUNT(*) AS products_count
FROM products
GROUP BY category;
Результат:
| category | products_count |
|---|---|
| Аксессуары | 5 |
| Игрушки | 8 |
| Книги | 4 |
| Корм | 12 |
Здесь GROUP BY уже не просто убирает повторы. Он собирает строки в группы, чтобы COUNT(*) посчитала количество строк внутри каждой группы.
Поэтому практическое правило такое:
Если нужен словарь значений — используй DISTINCT.
SELECT DISTINCT category
FROM products;
Если нужно что-то посчитать по группам — используй GROUP BY.
SELECT category, COUNT(*)
FROM products
GROUP BY category;
Цена тишины
DISTINCT выглядит маленьким словом, но база выполняет за ним реальную работу.
Чтобы убрать повторы, должна понять, какие строки результата одинаковые. Для этого ей нужно сравнить строки между собой. В зависимости от ситуации база может:
- отсортировать результат и убрать соседние повторы;
- построить уникальных значений;
- использовать уже подходящий , если он помогает.
На маленькой таблице разницу почти невозможно заметить.
Например, если в products всего 20 строк, запрос:
SELECT DISTINCT category
FROM products;
будет работать быстро.
Но если в таблице миллионы строк, а выбранных столбцов много, может стать заметной частью стоимости запроса.
Особенно дорого выглядит такой запрос:
SELECT DISTINCT *
FROM orders;
Здесь база должна сравнить целые строки по всем выбранным столбцам. Если строк много и столбцов много, работы становится больше.
Поэтому DISTINCT не стоит добавлять «на всякий случай».
Хороший вопрос перед использованием:
Какие именно повторы я хочу убрать и почему они появились?
Если ответ такой:
Мне нужен словарь статусов.
DISTINCT уместен.
SELECT DISTINCT status
FROM orders;
Если ответ такой:
У меня после появились странные дубли, поэтому я добавил DISTINCT.
Это уже тревожный сигнал. Возможно, проблема не в выдаче, а в логике соединения.
DISTINCT после JOIN: когда он маскирует проблему
Самый опасный сценарий — добавлять DISTINCT, чтобы «починить» дубли после JOIN.
Пока мы ещё не разбирали соединения подробно, но идею стоит запомнить заранее.
Допустим, есть пользователи и заказы.
Один пользователь может сделать много заказов.
Если соединить пользователей с заказами, строка пользователя размножится: по одной строке на каждый его заказ.
Например:
| user_id | name | order_id |
|---|---|---|
| 1 | Анна | 101 |
| 1 | Анна | 102 |
| 1 | Анна | 103 |
| 2 | Борис | 104 |
Если тебе нужны только пользователи, можно увидеть Анну три раза.
Иногда в такой ситуации человек пишет:
SELECT DISTINCT users.id, users.name
FROM users
JOIN orders ON orders.user_id = users.id;
И внешне результат становится «красивым»:
| id | name |
|---|---|
| 1 | Анна |
| 2 | Борис |
Но важно понимать: DISTINCT здесь не объяснил, почему Анна размножилась. Он просто схлопнул одинаковые строки после соединения.
Иногда это допустимо, если ты осознанно хочешь получить список пользователей, у которых есть хотя бы один заказ.
Но если DISTINCT добавлен только потому, что «без него почему-то дубли», это плохой запах запроса.
Правильный подход — понять природу данных:
- какая связь между таблицами: один к одному или один ко многим;
- почему одна строка слева находит несколько строк справа;
- нужен ли тебе действительно список уникальных пользователей;
- или нужно считать заказы;
- или нужно выбрать один конкретный заказ;
- или нужно изменить условие соединения.
DISTINCT после JOIN может быть нормальным инструментом, но не должен быть пластырем на непонятную проблему.
КВЕРИ: Если эхо появилось после открытия соседнего зала, не спеши глушить весь архив. Сначала проверь, какую дверь ты открыл.
Бонус PostgreSQL: DISTINCT ON
В PostgreSQL есть особая конструкция:
SELECT DISTINCT ON (выражение) ...
Она отличается от обычного DISTINCT.
Обычный DISTINCT убирает полностью одинаковые строки результата.
Например:
SELECT DISTINCT category, status
FROM products;
оставляет уникальные комбинации category + status.
А DISTINCT ON говорит:
Оставь только одну строку на каждое значение вот этого выражения.
Например, нужно выбрать по одному товару из каждой категории:
SELECT DISTINCT ON (category)
category, name, price
FROM products
ORDER BY category, price DESC;
Такой запрос можно читать так:
- разбей строки по
category; - внутри каждой категории отсортируй товары по цене от дорогих к дешёвым;
- оставь первую строку каждой категории.
Результат может быть таким:
| category | name | price |
|---|---|---|
| Аксессуары | Домик-портал | 3000 |
| Игрушки | Игрушка «Лазерная мышь» | 1700 |
| Книги | Книга «SQL для котонавтов» | 2500 |
| Корм | Корм «Лунный тунец» | 1900 |
Здесь DISTINCT ON (category) оставляет одну строку на категорию, а ORDER BY category, price DESC определяет, какая именно строка будет первой внутри категории.
Важно:
DISTINCT ON — . В стандартном SQL и во многих других его нет.
Для базового уровня главное не запоминать его как обязательный инструмент, а понять отличие:
DISTINCTоставляет уникальные строки результата;DISTINCT ONв PostgreSQL оставляет первую строку для каждой группы по указанному выражению.
Вопрос с собеседования
Вопрос с собеседования:
Что делает DISTINCT в SELECT?
Сильный ответ:
DISTINCT удаляет повторяющиеся строки из результата запроса. Он не меняет данные в таблице и не удаляет дубли из самой базы. Он работает только на уровне выдачи. Если выбрано несколько столбцов, уникальность считается по всей комбинации выбранных столбцов.
Вопрос с собеседования:
Что вернёт SELECT DISTINCT category, status FROM products?
Сильный ответ:
Он вернёт уникальные пары category + status. Это не список уникальных категорий и не список уникальных статусов по отдельности. Если одна категория встречается с разными статусами, она появится в результате несколько раз — по одному разу для каждой уникальной пары.
Вопрос с собеседования:
Чем SELECT DISTINCT x FROM t отличается от SELECT x FROM t GROUP BY x?
Сильный ответ:
Без агрегатных функций результат часто будет одинаковым: оба запроса вернут уникальные значения x. Но назначение разное. DISTINCT используют, когда нужен словарь значений: какие значения вообще встречаются. GROUP BY используют, когда нужно разбить строки на группы и что-то посчитать по каждой группе, например COUNT(*), SUM(...), AVG(...).
Вопрос с собеседования:
Гарантирует ли DISTINCT сортировку результата?
Сильный ответ:
Нет. DISTINCT убирает повторы, но не задаёт порядок строк. Если нужен предсказуемый порядок, нужно использовать ORDER BY.
Вопрос с собеседования:
Когда DISTINCT в запросе может быть признаком ошибки?
Сильный ответ:
Тревожный сигнал — DISTINCT, добавленный только для того, чтобы убрать дубли после JOIN. Часто такие дубли появляются из-за при соединении один-ко-многим или из-за неправильного условия соединения. В такой ситуации DISTINCT маскирует симптом и добавляет базе работу по . Правильнее разобраться, почему строки размножились, и исправить логику запроса.
DISTINCT в SELECT?SELECT DISTINCT category, status FROM products?DISTINCT и ORDER BY?КВЕРИ:
DISTINCTнужен, когда ты осознанно гасишь эхо. Если не знаешь, откуда эхо взялось, сначала найди источник.
- Уникальные пары «категория — поставщик»EASY
- Авторы, читавшие собственные статьиEASY
- Города с объектами недвижимостиEASY