SELECT: die richtigen Daten abrufen

DISTINCT: nur Eindeutiges behalten

22 min
What you'll learn
  • Wiederholungen in der Ausgabe über SELECT DISTINCT entfernen und erklären, dass die Eindeutigkeit über die gesamte Spaltenkombination beurteilt wird
  • Aufgaben für DISTINCT („welche Werte vorkommen“) von Aufgaben für GROUP BY („was pro Gruppe berechnet werden soll“) unterscheiden
  • das Anti-Muster „DISTINCT zum Kaschieren von Duplikaten nach einem JOIN“ erkennen und seinen Preis erklären

Nur eindeutige Werte

Im großen Saal des Lagers hat jeder Laut ein Echo: dieselbe Kategorie, derselbe Status wiederholen sich über Tausende Zeilen. Wenn du nicht die ganze Datenflut brauchst, sondern ein kurzes Wörterbuch — „welche Werte kommen hier überhaupt vor?“ —, muss das Echo verstummen.

SELECT DISTINCT Spalten entfernt wiederholte Zeilen aus dem Ergebnis. Das ist nützlich, wenn du nicht alle Bestellungen sehen willst, sondern ein Wörterbuch der Werte: welche Kategorien es in products gibt, welche Status in orders vorkommen, welche Städte in users ausgefüllt sind.

Wichtig: Die Eindeutigkeit wird über die gesamte ausgewählte Spaltenkombination beurteilt. Eine Spalte — die eindeutigen Werte dieser Spalte; zwei Spalten — die eindeutigen Paare.

Dutzende halbtransparente, identische Duplikat-Hologramme klappen zu einer einzigen, klaren Aufzeichnung zusammen
DISTINCT bringt das Echo des Archivs zum Verstummen: Tausende Wiederholungen werden zu einem kurzen Wörterbuch eindeutiger Werte.

Где пишется 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 vergleicht Zeilen über die gesamte ausgewählte Spaltenkombination: Jede Kombination bleibt genau einmal erhalten.
Das Echo ist verstummt: alle Status aus orders, jeder genau einmal. Darunter jenes pending, das für immer stehen geblieben ist.
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

Frage aus dem Vorstellungsgespräch: Wodurch unterscheidet sich SELECT DISTINCT x FROM t von SELECT x FROM t GROUP BY x? Und wann ist ein DISTINCT in einer Abfrage ein Zeichen für einen Fehler?

Starke Antwort: Ohne Aggregate ist das Ergebnis gleich — beide entfernen Wiederholungen nach x, und das baut dafür oft denselben Plan. GROUP BY braucht man, wenn pro Gruppe etwas berechnet wird. Ein Alarmsignal ist ein DISTINCT, das hinzugefügt wurde, um Duplikate nach einem JOIN zu „reparieren“: Es kaschiert die Zeilenvervielfachung in der Verbindung und zwingt die Datenbank, das ganze Ergebnis zu deduplizieren. Die richtige Lösung ist, die Verbindung selbst in Ordnung zu bringen, nicht ihre Folgen zu dämpfen.

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

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