DISTINCT : ne garder que l'unique
What you'll learn
- supprimer les répétitions du résultat avec
SELECT DISTINCTet expliquer que l'unicité porte sur toute la combinaison de colonnes - distinguer les usages de
DISTINCT(« quelles valeurs existent ») de ceux deGROUP BY(« que calculer par groupe ») - reconnaître l'anti-modèle «
DISTINCTpour masquer les doublons après unJOIN» et en expliquer le coût
Seulement les valeurs uniques
Dans la grande salle du coffre, chaque son a son écho : une même catégorie, un même statut se répètent sur des milliers de lignes. Quand tu n'as pas besoin du flot complet, mais seulement d'un court dictionnaire — « quelles valeurs existent ici, au fond » — il faut couper l'écho.
SELECT DISTINCT colonnes supprime les lignes répétées du résultat. C'est utile quand tu n'as pas besoin de toutes les commandes, mais d'un vocabulaire de valeurs : quelles catégories existent dans products, quels statuts apparaissent dans orders, quelles villes sont renseignées dans users.
Point important : l'unicité se juge sur toute la combinaison de colonnes choisie. Une colonne donne les valeurs uniques de cette colonne ; deux colonnes donnent les paires uniques.

DISTINCT coupe l'écho de l'archive : des milliers de répétitions se réduisent à un court dictionnaire de valeurs uniques.Где пишется 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 compare les lignes sur toute la combinaison de colonnes choisie : chaque combinaison reste dans le résultat une seule fois.orders, une fois chacun. Parmi eux, ce fameux pending, figé pour toujours.| status |
|---|
| cancelled |
| paid |
| pending |
| refunded |
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 не означает «сделай уникальным первый столбец». Он означает:
Убери повторяющиеся строки результата целиком.
| country | city |
|---|---|
| Беларусь | Минск |
| Казахстан | Алматы |
| Россия | Екатеринбург |
| Россия | Казань |
| Россия | Москва |
| Россия | Нижний Новгород |
| Россия | Новосибирск |
| Россия | Санкт-Петербург |
Как 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 оставляет первую строку для каждой группы по указанному выражению.
Interview question
Question d'entretien : en quoi SELECT DISTINCT x FROM t diffère-t-il de SELECT x FROM t GROUP BY x ? Et quand un DISTINCT dans une requête est-il le signe d'une erreur ?
Bonne réponse : sans agrégats, le résultat est identique : les deux suppriment les répétitions sur x, et le SGBD leur construit souvent le même plan. GROUP BY devient nécessaire quand on calcule quelque chose par groupe. Le signal inquiétant, c'est un DISTINCT ajouté pour « réparer » les doublons après un JOIN : il masque la multiplication des lignes dans la jointure et force la base à dédupliquer tout le résultat. Le bon remède est de corriger la jointure elle-même, pas d'en étouffer les conséquences.
DISTINCT в SELECT?SELECT DISTINCT category, status FROM products?DISTINCT и ORDER BY?КВЕРИ:
DISTINCTнужен, когда ты осознанно гасишь эхо. Если не знаешь, откуда эхо взялось, сначала найди источник.
- Unique category-supplier pairsEASY
- Auteurs qui se lisent eux-mêmesEASY
- Villes accueillant des biensEASY