SELECT: достаём нужные данные

DISTINCT: оставляем уникальное

22 мин
Чему научишься
  • убирать повторы из выдачи через SELECT DISTINCT
  • понимать, что уникальность считается не по одному «главному» столбцу, а по всей выбранной комбинации столбцов
  • получать словарь значений: какие категории, статусы, города или другие варианты вообще встречаются в таблице
  • отличать задачи для DISTINCT от задач для GROUP BY
  • понимать, что DISTINCT не сортирует результат сам по себе
  • объяснять, почему DISTINCT может быть дорогим на больших таблицах
  • распознавать : DISTINCT, добавленный только для маскировки дублей после JOIN

Только уникальные значения

В большом зале хранилища у каждого звука есть эхо. Один и тот же статус повторяется в тысячах заказов. Одна и та же категория тянется через сотни товаров. Один и тот же город вспыхивает в профилях разных пользователей.

Когда тебе нужен весь поток данных, повторы нормальны.

Например, если ты смотришь заказы, то статус paid может встретиться много раз:

SELECT id, status
FROM orders;

Результат может быть таким:

idstatus
1paid
2paid
3pending
4cancelled
5paid
6pending

Здесь каждая строка — отдельный заказ. Повторы статусов не ошибка. Просто у нескольких заказов одинаковый статус.

Но иногда нужен не поток заказов, а короткий словарь:

Какие статусы вообще бывают в таблице?

Тогда нам не нужны все повторы 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уникальные6 значений → 3 уникальных
DISTINCT сравнивает строки по всей выбранной комбинации столбцов: каждая комбинация остаётся в результате ровно один раз.
Эхо погашено: все статусы из orders по одному разу. Среди них — то самое pending, застывшее навсегда.

DISTINCT по одному столбцу

Самый простой случай — выбрать уникальные значения одного столбца.

Например, таблица products:

idnamecategory
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

Например, есть таблица:

idcategorystatus
1Книгиactive
2Книгиactive
3Книгиarchived
4Игрушкиactive
5Игрушкиactive
6Игрушкиarchived

Запрос:

SELECT DISTINCT category
FROM products;

вернёт:

category
Книги
Игрушки

А запрос:

SELECT DISTINCT category, status
FROM products;

вернёт:

categorystatus
Книгиactive
Книгиarchived
Игрушкиactive
Игрушкиarchived

Почему?

Потому что теперь уникальность считается не только по category, а по паре:

category + status

Строки:

Книги + active
Книги + archived

для SQL разные, хотя категория у них одинаковая.

Поэтому DISTINCT не означает «сделай уникальным первый столбец». Он означает:

Убери повторяющиеся строки результата целиком.

Уникальность считается по всей комбинации выбранных столбцов. Одна страна может встретиться несколько раз — рядом с ней разные города.

Как DISTINCT относится к NULL

NULL означает отсутствие значения, но для DISTINCT несколько строк с NULL в одном и том же столбце считаются повтором.

Например, таблица users:

idnamecity
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;

Результат:

categoryproducts_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_idnameorder_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;

И внешне результат становится «красивым»:

idname
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;

Такой запрос можно читать так:

  1. разбей строки по category;
  2. внутри каждой категории отсортируй товары по цене от дорогих к дешёвым;
  3. оставь первую строку каждой категории.

Результат может быть таким:

categorynameprice
АксессуарыДомик-портал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 нужен, когда ты осознанно гасишь эхо. Если не знаешь, откуда эхо взялось, сначала найди источник.

Закрепление: реши задачи
Решено 0 из 3 · для зачёта достаточно 2