SELECT: vi hämtar de data vi behöver

DISTINCT: vi behåller det unika

22 min
What you'll learn
  • ta bort dubbletter från utdata med SELECT DISTINCT och förklara att unikheten bedöms över hela kombinationen av kolumner
  • skilja på uppgifter för DISTINCT (”vilka värden förekommer”) och uppgifter för GROUP BY (”vad ska räknas per grupp”)
  • känna igen antimönstret ”DISTINCT för att maskera dubbletter efter en JOIN” och förklara dess pris

Bara unika värden

I förrådets stora sal har varje ljud ett eko: samma kategori, samma status upprepas i tusentals rader. När du behöver inte strömmen utan en kort ordlista — ”vilka värden förekommer här över huvud taget” — måste ekot tystas.

SELECT DISTINCT kolumner tar bort upprepade rader ur resultatet. Det är användbart när du behöver inte hela strömmen av ordrar utan ett ordförråd av värden: vilka kategorier som finns i products, vilka statusar som förekommer i orders, vilka städer som är ifyllda i users.

Viktigt: unikheten bedöms över hela den valda kombinationen av kolumner. En kolumn — de unika värdena i den kolumnen; två kolumner — de unika paren.

Tiotals halvgenomskinliga identiska upprepningshologram klappar ihop till en enda skarp post
DISTINCT tystar arkivets eko: tusentals upprepningar klappar ihop till en kort ordlista med unika värden.

Где пишется 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 jämför rader över hela den valda kombinationen av kolumner: varje kombination blir kvar i resultatet exakt en gång.
Ekot är tystat: alla statusar från orders, en gång var. Bland dem finns just det pending, fruset för evigt.
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

Intervjufråga: hur skiljer sig SELECT DISTINCT x FROM t från SELECT x FROM t GROUP BY x? Och när är ett DISTINCT i en fråga ett tecken på ett misstag?

Starkt svar: utan aggregat är resultatet detsamma — båda tar bort upprepningar efter x, och :et bygger ofta samma plan för dem. GROUP BY behövs när något räknas per grupp. Varningssignalen är ett DISTINCT som lagts till för att ”laga” dubbletter efter en JOIN: det maskerar radmångfaldigandet i sammanfogningen och tvingar databasen att deduplicera hela resultatet. Det rätta botemedlet är att reda ut själva sammanfogningen, inte att dämpa dess följder.

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

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