SELECT: wybieramy potrzebne dane

DISTINCT: zostawiamy unikalne wartości

22 min
What you'll learn
  • usuwać powtórzenia z wyniku przez SELECT DISTINCT i objaśniać, że unikalność liczy się według całej kombinacji kolumn
  • odróżniać zadania dla DISTINCT („jakie wartości występują”) od zadań dla GROUP BY („co policzyć w grupach”)
  • rozpoznawać antywzorzec „DISTINCT do maskowania duplikatów po JOIN” i wyjaśniać jego koszt

Tylko unikalne wartości

W wielkiej sali magazynu każdy dźwięk ma echo: ta sama kategoria, ten sam status powtarzają się w tysiącach wierszy. Kiedy potrzebujesz nie strumienia danych, tylko krótkiego słownika — „jakie wartości w ogóle tu występują” — echo trzeba wyciszyć.

SELECT DISTINCT kolumny usuwa powtarzające się wiersze z wyniku. Przydaje się wtedy, gdy potrzebujesz nie całego strumienia zamówień, lecz słownika wartości: jakie są kategorie w products, jakie statusy występują w orders, które miasta są wypełnione w users.

Ważne: unikalność liczy się według całej wybranej kombinacji kolumn. Jedna kolumna oznacza unikalne wartości tej kolumny; dwie kolumny — unikalne pary.

Dziesiątki półprzezroczystych jednakowych hologramów-powtórzeń składają się w jeden wyraźny rekord
DISTINCT wycisza echo archiwum: tysiące powtórzeń składają się w krótki słownik unikalnych wartości.

Где пишется 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;

Во всех этих запросах мысль одна:

Покажи, какие значения вообще встречаются, без повторов.

with dupesDISTINCTunique6 values → 3 unique
DISTINCT porównuje wiersze według całej wybranej kombinacji kolumn: każda kombinacja zostaje w wyniku dokładnie raz.
Echo wyciszone: wszystkie statusy z orders po jednym razie. Wśród nich — to samo pending, zastygłe na zawsze.
Query result
status
cancelled
paid
pending
refunded

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 не означает «сделай уникальным первый столбец». Он означает:

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

Уникальность считается по всей комбинации выбранных столбцов. Одна категория может встретиться несколько раз, если рядом с ней разные статусы.
Query result
countrycity
БеларусьМинск
КазахстанАлматы
РоссияЕкатеринбург
РоссияКазань
РоссияМосква
РоссияНижний Новгород
РоссияНовосибирск
РоссияСанкт-Петербург

Как 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 оставляет первую строку для каждой группы по указанному выражению.
Interview question

Pytanie z rozmowy kwalifikacyjnej: czym SELECT DISTINCT x FROM t różni się od SELECT x FROM t GROUP BY x? I kiedy DISTINCT w zapytaniu jest oznaką błędu?

Mocna odpowiedź: bez agregatów wynik jest taki sam — oba zapytania usuwają powtórzenia według x, a często buduje dla nich ten sam plan. GROUP BY jest potrzebny wtedy, gdy w grupach coś się liczy. Niepokojący sygnał to DISTINCT dodany po to, żeby „naprawić” duplikaty po JOIN: maskuje rozmnażanie wierszy w połączeniu i zmusza bazę do deduplikacji całego wyniku. Właściwe leczenie polega na uporządkowaniu samego połączenia, a nie na tłumieniu jego skutków.

Check yourself
Что делает DISTINCT в SELECT?
Check yourself
Как считается уникальность в запросе SELECT DISTINCT category, status FROM products?
Check yourself
Какой запрос лучше выражает мысль «покажи, какие категории вообще есть в товарах»?
Check yourself
Что верно про DISTINCT и ORDER BY?

КВЕРИ: DISTINCT нужен, когда ты осознанно гасишь эхо. Если не знаешь, откуда эхо взялось, сначала найди источник.