DISTINCT: pustimo edinstveno
What you'll learn
- odstranjevati ponovitve iz izpisa prek
SELECT DISTINCTin razložiti, da se edinstvenost šteje po celotni kombinaciji stolpcev - ločiti naloge za
DISTINCT(»katere vrednosti se pojavljajo«) od nalog zaGROUP BY(»kaj izračunati po skupinah«) - prepoznati anti-vzorec »
DISTINCTza prikrivanje podvojitev poJOIN« in razložiti njegovo ceno
Samo edinstvene vrednosti
V veliki dvorani shrambe ima vsak zvok odmev: ista kategorija, isti status se ponovita v tisočih vrsticah. Kadar ne potrebuješ toka, ampak kratek slovar — »katere vrednosti se tu sploh pojavljajo« — je treba odmev utišati.
SELECT DISTINCT stolpci odstrani podvojene vrstice iz rezultata. To je koristno, kadar ne potrebuješ celega toka naročil, ampak slovar vrednosti: katere kategorije so v products, kateri statusi se pojavljajo v orders, katera mesta so izpolnjena v users.
Pomembno: edinstvenost se šteje po celotni izbrani kombinaciji stolpcev. En stolpec — edinstvene vrednosti tega stolpca; dva stolpca — edinstveni pari.

DISTINCT utiša odmev arhiva: tisoči ponovitev se sesedejo v kratek slovar edinstvenih vrednosti.Где пишется 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 primerja vrstice po celotni izbrani kombinaciji stolpcev: vsaka kombinacija ostane v rezultatu natanko enkrat.orders po enkrat. Med njimi je tisti pending, zamrznjen za vedno.| 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
Vprašanje z razgovora: v čem se SELECT DISTINCT x FROM t razlikuje od SELECT x FROM t GROUP BY x? In kdaj je DISTINCT v poizvedbi znak napake?
Močan odgovor: brez agregatov je rezultat enak — oba odstranita ponovitve po x, in zanju pogosto zgradi povsem isti načrt. GROUP BY je potreben, kadar se po skupinah kaj šteje. Vznemirljiv signal je DISTINCT, dodan zato, da bi »popravil« podvojitve po JOIN: prikriva razmnoževanje vrstic v združitvi in bazo prisili, da razdvoji ves rezultat. Pravo zdravljenje je razčistiti samo združitev, ne pa dušiti njenih posledic.
DISTINCT в SELECT?SELECT DISTINCT category, status FROM products?DISTINCT и ORDER BY?КВЕРИ:
DISTINCTнужен, когда ты осознанно гасишь эхо. Если не знаешь, откуда эхо взялось, сначала найди источник.