DISTINCT: jätämme jäljelle uniikit
What you'll learn
- poistamaan toistot tulosteesta
SELECT DISTINCT-rakenteella ja selittämään, että uniikkius lasketaan koko sarakeyhdistelmän mukaan - erottamaan
DISTINCT-tehtävät (”mitä arvoja esiintyy”)GROUP BY-tehtävistä (”mitä laskea ryhmittäin”) - tunnistamaan antikuvion ”
DISTINCTkaksoiskappaleiden peittämiseksiJOIN:n jälkeen” ja selittämään sen hinnan
Vain uniikit arvot
Varaston suuressa salissa jokaisella äänellä on kaiku: sama kategoria, sama tila toistuvat tuhansilla riveillä. Kun et tarvitse virtaa vaan lyhyen sanakirjan — ”mitä arvoja täällä ylipäätään esiintyy” — kaiku on vaimennettava.
SELECT DISTINCT sarakkeet poistaa toistuvat rivit tuloksesta. Tämä on hyödyllistä, kun et tarvitse koko tilausvirtaa vaan arvojen sanakirjan: mitä kategorioita products-taulussa on, mitä tiloja orders-taulussa esiintyy, mitä kaupunkeja users-taulussa on täytetty.
Tärkeää: uniikkius lasketaan koko valitun sarakeyhdistelmän mukaan. Yksi sarake — tuon sarakkeen uniikit arvot; kaksi saraketta — uniikit parit.

DISTINCT vaimentaa arkiston kaiun: tuhannet toistot luhistuvat lyhyeksi uniikkien arvojen sanakirjaksi.Где пишется 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 vertaa rivejä koko valitun sarakeyhdistelmän mukaan: jokainen yhdistelmä jää tulokseen tasan kerran.orders-taulun tilat kukin kerran. Niiden joukossa on juuri se pending, joka jähmettyi ikuisiksi ajoiksi.| 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
Haastattelukysymys: miten SELECT DISTINCT x FROM t eroaa kyselystä SELECT x FROM t GROUP BY x? Ja milloin kyselyssä oleva DISTINCT on merkki virheestä?
Vahva vastaus: ilman aggregaatteja tulos on sama — molemmat poistavat toistot x:n mukaan, ja tietokannanhallintajärjestelmä rakentaa niille usein saman suunnitelman. GROUP BY tarvitaan, kun ryhmittäin lasketaan jotain. Hälyttävä merkki on DISTINCT, joka on lisätty ”korjaamaan” kaksoiskappaleet JOIN:n jälkeen: se peittää rivien monistumisen liitoksessa ja pakottaa tietokannan poistamaan kaksoiskappaleet koko tuloksesta. Oikea hoito on selvittää itse liitos, ei vaimentaa sen seurauksia.
DISTINCT в SELECT?SELECT DISTINCT category, status FROM products?DISTINCT и ORDER BY?КВЕРИ:
DISTINCTнужен, когда ты осознанно гасишь эхо. Если не знаешь, откуда эхо взялось, сначала найди источник.