DISTINCT в SQL: как вывести уникальные значения без дубликатов

Чтобы вывести уникальные значения в SQL, допишите DISTINCT сразу после SELECT.

Разобрали на MySQL 8.0 и PostgreSQL 16, как оператор работает с несколькими столбцами и NULL, как посчитать уникальные значения через COUNT(DISTINCT) и чем заменить DISTINCT ON в MySQL.

Статью написал:
Ваня Буявец, продюсер, основатель Checkroi
Ваня Буявец
Основатель Checkroi, продюсер, эксперт в выборе онлайн-курсов
Все 2994 статьи автора Подписаться на Телеграм-канал
Одобрено экспертом:
Наташа Буявец, основатель Checkroi, эксперт по онлайн-курсам
Наташа Буявец
Основательница Checkroi, продюсер Youtube-каналов, эксперт по онлайн-курсам
Все 3648 экспертных мнений Подписаться на Телеграм-канал
Обложка: DISTINCT в SQL: как вывести уникальные значения без дубликатов

Чтобы вывести уникальные значения в SQL, допишите DISTINCT сразу после SELECT: SELECT DISTINCT city FROM orders;. База уберёт из результата повторяющиеся строки и оставит по одной из каждой группы одинаковых. Сама таблица при этом не меняется, DISTINCT работает только с выводом запроса.

Если столбцов в списке несколько, уникальной считается их комбинация, а не каждый столбец отдельно. Базовый синтаксис SELECT с WHERE и сортировкой мы разбирали в статье о команде SELECT в MySQL, а с чего вообще начинать язык, рассказали в обзоре языка SQL.

Все запросы ниже мы запускали на MySQL 8.0 и PostgreSQL 16, спорные места ещё и на SQLite. Вывод скопирован из консоли, там, где базы отвечают по-разному, показаны оба варианта.

CheckroiCheckroiПодборка курсов по SQL419 курсов • 40 школСравните цены, школы, программу и найдите выгодные предложения по обучениюСравнить→

Синтаксис DISTINCT и учебная таблица

Рой разбирает таблицу заказов и убирает повторяющиеся строки

Оператор ставится между SELECT и списком столбцов. Писать его можно один раз на весь запрос:

SELECT DISTINCT столбец1, столбец2
FROM таблица
WHERE условие
ORDER BY столбец1;

Противоположность DISTINCT называется ALL, это поведение по умолчанию. SELECT ALL city и SELECT city вернут одно и то же: все строки вместе с повторами.

Примеры построены на маленькой таблице заказов orders. В ней семь строк, у двух клиентов город не указан:

id customer city product
1 Анна Москва Курс SQL
2 Борис Казань Курс Python
3 Анна Москва Курс Python
4 Вера NULL Курс SQL
5 Борис Казань Курс SQL
6 Глеб Москва Курс SQL
7 Дина NULL Курс Python

Как вывести уникальные значения одного столбца

Обычный SELECT по столбцу city вернёт все семь строк: Москва трижды, Казань дважды и два NULL. С DISTINCT повторы схлопываются:

SELECT DISTINCT city FROM orders ORDER BY city;

+--------------+
| city         |
+--------------+
| NULL         |
| Казань       |
| Москва       |
+--------------+
3 rows in set

Два пустых города дали одну строку, а не две: для DISTINCT все NULL одинаковы. Документация SQLite объясняет это прямо: при поиске дублей два значения NULL считаются равными. MySQL и PostgreSQL ведут себя так же, мы проверили на обеих базах.

Решение: если пустой город в списке не нужен, отсейте его до DISTINCT условием WHERE city IS NOT NULL. Почему здесь не работает city != NULL, подробно разобрано в статье о NULL в SQL.

Где NULL окажется в списке

В выводе выше NULL стоит первым, так сортирует MySQL. PostgreSQL при ORDER BY city ставит NULL в конец:

-- PostgreSQL 16, в psql включено \pset null '(NULL)', иначе NULL выводится пустой строкой
SELECT DISTINCT city FROM orders ORDER BY city;

  city
--------
 Казань
 Москва
 (NULL)
(3 rows)

В PostgreSQL порядок задаётся явно: ORDER BY city NULLS FIRST или NULLS LAST. В MySQL такой записи нет, на NULLS FIRST он отвечает синтаксической ошибкой 1064, а NULL при сортировке по возрастанию ставит первым.

Канал основателя Checkroi Вани Буявца3 700 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединиться

DISTINCT по нескольким столбцам

Частая ошибка новичков: ожидать, что DISTINCT применится только к первому столбцу. На деле он сравнивает строку результата целиком, то есть всю комбинацию значений:

SELECT DISTINCT customer, city FROM orders ORDER BY customer;

+----------+--------+
| customer | city   |
+----------+--------+
| Анна     | Москва |
| Борис    | Казань |
| Вера     | NULL   |
| Глеб     | Москва |
| Дина     | NULL   |
+----------+--------+
5 rows in set

Москва встречается дважды, потому что пары «Анна, Москва» и «Глеб, Москва» разные. А вот вторые заказы Анны и Бориса исчезли: у них совпали и клиент, и город.

Такой список пар часто отдают коллегам таблицей. Если вы пишете запросы в DBeaver, результат сохраняется в файл парой кликов, шаги мы показали в инструкции о том, как выгрузить результат запроса в Excel и CSV.

Скобки ничего не меняют. Запрос SELECT DISTINCT(city), customer выглядит так, будто уникальность наложена на город, но MySQL и PostgreSQL вернули те же пять пар. Скобки окружают только выражение city, а DISTINCT по-прежнему касается всего списка.

Отсюда второе следствие. SELECT DISTINCT * по таблице с уникальным id не уберёт ничего: в нашей таблице он вернул все 7 строк, ведь любые две строки отличаются хотя бы номером.

Как посчитать количество уникальных значений: COUNT(DISTINCT)

Когда нужно не само множество, а число в нём, DISTINCT ставят внутрь COUNT. Сравните три счётчика по одному столбцу:

SELECT COUNT(*)             AS all_rows,
       COUNT(city)          AS with_city,
       COUNT(DISTINCT city) AS cities
FROM orders;

+----------+-----------+--------+
| all_rows | with_city | cities |
+----------+-----------+--------+
|        7 |         5 |      2 |
+----------+-----------+--------+

Тут NULL ведёт себя иначе, чем в обычном DISTINCT. Список городов из прошлого раздела содержал три строки, вместе с NULL, а COUNT(DISTINCT city) вернул 2: функция пропускает пустые значения. Если две цифры «уникальных городов» в отчётах расходятся на единицу, первым делом проверьте, есть ли в столбце NULL.

С фильтром счётчик отвечает на вопрос «сколько разных клиентов купили курс SQL»:

SELECT COUNT(DISTINCT customer) AS buyers
FROM orders
WHERE product = 'Курс SQL';
-- buyers: 4

Из таких счётчиков собирают отчёты о продажах и воронках. Чем ещё на работе занят аналитик, который пишет их каждый день, мы разбирали в материале о профессии SQL-аналитика.

Уникальные пары: здесь базы расходятся

Посчитать уникальные комбинации двух столбцов каждая СУБД предлагает по-своему. Мы прогнали один и тот же подсчёт пар «клиент, город»:

Запрос MySQL 8.0 PostgreSQL 16 SQLite 3.51
COUNT(DISTINCT customer, city) 3 ошибка: function count(character varying, character varying) does not exist ошибка: wrong number of arguments to function COUNT()
COUNT(DISTINCT (customer, city)) ошибка 1241: Operand should contain 1 column(s) 5 ошибка: row value misused
SELECT COUNT(*) FROM (SELECT DISTINCT customer, city FROM orders) t 5 5 5

Почему MySQL насчитал три пары, а не пять? Его справка говорит, что список выражений внутри COUNT даёт число комбинаций, в которых нет NULL. Пары Веры и Дины с пустым городом выпали.

Решение, которое работает везде одинаково, стоит в последней строке таблицы: подзапрос с DISTINCT и внешний COUNT(*). Он учитывает и пары с NULL.

В таблицах та же задача решается без кода. Как убрать повторы в Excel и Google Таблицах, мы показали в инструкции по удалению дубликатов.

Чем DISTINCT отличается от GROUP BY

Два лотка с карточками: уникальный ряд и сгруппированные стопки

На простом списке разницы нет. Эти два запроса вернули одинаковые три строки и в MySQL, и в PostgreSQL:

SELECT DISTINCT city FROM orders;
SELECT city FROM orders GROUP BY city;

PostgreSQL даже строит для них похожий план: в EXPLAIN для DISTINCT видна та же операция HashAggregate, что и для группировки. MySQL для DISTINCT собирает временную таблицу с удалением дублей.

Разница появляется, когда к каждому уникальному значению нужна цифра. DISTINCT этого не умеет, а GROUP BY считает итог по каждой группе:

SELECT city, COUNT(*) AS orders_cnt
FROM orders
GROUP BY city
ORDER BY orders_cnt DESC;

+--------+------------+
| city   | orders_cnt |
+--------+------------+
| Москва |          3 |
| NULL   |          2 |
| Казань |          2 |
+--------+------------+
Задача Что брать
Список уникальных значений DISTINCT, он короче и понятнее
Число уникальных значений COUNT(DISTINCT …)
Уникальные значения и сумма, количество или среднее по каждому GROUP BY с агрегатной функцией
Отбор групп по условию на агрегат GROUP BY и HAVING

Разницу между DISTINCT и GROUP BY спрашивают и на техническом интервью, рядом с вопросами про JOIN и NULL. Какие ещё задачи по SQL дают кандидатам, видно по разбору собеседования на аналитика данных.

DISTINCT ON в PostgreSQL и замена для MySQL

Иногда нужна не уникальная пара, а одна целая строка на каждое значение: например, первый заказ каждого клиента. Для этого в PostgreSQL есть DISTINCT ON:

SELECT DISTINCT ON (customer) customer, product, id
FROM orders
ORDER BY customer, id;

 customer |   product   | id
----------+-------------+----
 Анна     | Курс SQL    |  1
 Борис    | Курс Python |  2
 Вера     | Курс SQL    |  4
 Глеб     | Курс SQL    |  6
 Дина     | Курс Python |  7

Без ORDER BY такой запрос тоже выполнится, но какая строка окажется «первой», заранее не известно. Документация PostgreSQL пишет, что первая строка набора непредсказуема, если только не применяется ORDER BY. Там же сказано, что выражения DISTINCT ON должны соответствовать самым левым выражениям в ORDER BY, поэтому customer стоит в сортировке первым.

В MySQL на DISTINCT ON будет синтаксическая ошибка 1064. Тот же результат там даёт оконная функция ROW_NUMBER, она есть с версии 8.0:

SELECT customer, product, id
FROM (
  SELECT o.*,
         ROW_NUMBER() OVER (PARTITION BY customer ORDER BY id) AS rn
  FROM orders o
) t
WHERE rn = 1
ORDER BY customer;

Мы прогнали этот вариант на обеих базах: вывод совпал с DISTINCT ON строка в строку. Если работаете с PostgreSQL через графический клиент, пригодится инструкция о том, как подключить DBeaver к PostgreSQL.

Ваня БуявецКанал основателя Checkroi Вани БуявцаЗабирайте промпты и обучение по нейросетям в моём Телеграм-каналеБольше 3 700 человек уже применяют Claude Code, ChatGPT и другие нейросети в работе, учёбе, бизнесе и жизниПерейти в канал

Частые ошибки с DISTINCT и что с ними делать

Рой с лупой находит лишние дубли в длинном списке строк

Сортировка по столбцу, которого нет в SELECT

Запрос SELECT DISTINCT city FROM orders ORDER BY id; обе базы отклоняют:

-- MySQL 8.0
ERROR 3065 (HY000): Expression #1 of ORDER BY clause is not in SELECT list,
references column 'orders.id' which is not in SELECT list;
this is incompatible with DISTINCT

-- PostgreSQL 16
ERROR:  for SELECT DISTINCT, ORDER BY expressions must appear in select list

У Москвы в таблице три разных id, и базе непонятно, по какому из них ставить строку. Решение: сортировать по выводимому столбцу или добавить нужный столбец в список, понимая, что уникальной станет уже пара.

DISTINCT как заплатка после JOIN

Допустим, нужен список клиентов, у которых есть хотя бы один заказ. Соединение с заказами размножит клиентов: Анна и Борис попали в результат дважды, всего 7 строк вместо 5. Самое быстрое решение: дописать DISTINCT. Он и правда вернёт пятерых.

Но дубли здесь появились из-за неверной формы запроса, а DISTINCT их только прячет. База сначала соберёт все строки соединения и только потом начнёт искать среди них повторы, на больших таблицах это лишняя работа. Точнее проверить существование заказа:

SELECT c.name
FROM clients c
WHERE EXISTS (
  SELECT 1 FROM orders o WHERE o.customer = c.name
)
ORDER BY c.name;
-- Анна, Борис, Вера, Глеб, Дина

Правило на практике: видите DISTINCT рядом с JOIN, спросите, откуда взялись дубли. Иногда это ошибка в условии соединения, и тогда DISTINCT скрывает неверные данные.

Расчёт на порядок строк без ORDER BY

DISTINCT порядок не гарантирует. Тот же SELECT DISTINCT city FROM orders; без сортировки MySQL вернул в порядке первого появления (Москва, Казань, NULL), а PostgreSQL выдал NULL, Казань, Москва. Нужна стабильная выдача, например для отчёта или выгрузки, пишите ORDER BY явно.

Если такие запросы тормозят на живой базе, их разбирают по плану выполнения. Этим в команде обычно занят тот, о ком мы писали в статье кто такой администратор баз данных.

Где освоить SQL системно

DISTINCT разбирается за полчаса. Уверенно писать запросы с группировками, оконными функциями и подзапросами учатся неделями на реальных данных, и курс с задачами и проверкой ускоряет этот путь.

Ниже программы из нашего каталога, все варианты собраны в разделе курсов по SQL. План обучения по месяцам расписан в статье как стать SQL-разработчиком.

КурсШколаСтоимость со скидкойВ рассрочкуДлитель­ностьОбзор курса от Checkroi
Нейросети для рабочих задач
Перейти на сайт курса
SkillboxSkillbox31 290 ₽ - 1 месяцОбзор курса
SQL-разработчик: тариф PRO
Перейти на сайт курса
Академия ЭдюсонЭдюсон89 900 ₽7491 ₽/мес.2 месяцаОбзор курса
SQL-разработчик: тариф Базовый
Перейти на сайт курса
Академия ЭдюсонЭдюсон79 900 ₽3329 ₽/мес.3 месяцаОбзор курса
SQL для анализа данных
Перейти на сайт курса
SkillboxSkillbox50 828 ₽6203 ₽/мес.2 месяцаОбзор курса
SQL с нуляSkyproSkypro134 640 ₽5500 ₽/мес.12 месяцевОбзор курса

Больше программ — в полном каталоге курсов по SQL

Часто задаваемые вопросы

Что делает DISTINCT в SQL?

DISTINCT убирает из результата SELECT повторяющиеся строки и оставляет по одной из каждой группы одинаковых. Данные в таблице не меняются: оператор работает только с выводом запроса.

Какой оператор SQL возвращает только различные значения?

Это DISTINCT, его пишут сразу после SELECT: SELECT DISTINCT city FROM orders;. Если в списке несколько столбцов, уникальной считается их комбинация, а не каждый столбец по отдельности.

Как посчитать количество уникальных значений в столбце?

Через COUNT(DISTINCT столбец). Учтите, что функция пропускает NULL: в нашем примере список городов дал три строки вместе с NULL, а COUNT(DISTINCT city) вернул 2.

Как DISTINCT обрабатывает NULL?

Все NULL в столбце DISTINCT считает одинаковыми и выводит одной строкой. Если пустое значение в списке не нужно, добавьте условие WHERE столбец IS NOT NULL. Подробнее о поведении пустых значений мы писали в статье о NULL в SQL.

Чем DISTINCT отличается от GROUP BY?

Для простого списка уникальных значений они дают одинаковый результат. GROUP BY нужен, когда к каждому значению надо посчитать сумму, количество или среднее через агрегатную функцию, DISTINCT так не умеет.

Как вывести одну строку на каждое значение, например первый заказ клиента?

В PostgreSQL для этого есть DISTINCT ON (столбец) вместе с ORDER BY, который задаёт, какая строка будет первой. В MySQL 8.0 тот же результат даёт оконная функция ROW_NUMBER() с отбором rn = 1.

Читайте Checkroi первым в Google
Добавьте Checkroi в избранные источники — и наши разборы курсов и обзоры школ будут показываться выше в вашей выдаче Google.
Оставить комментарий
0 комментариев
Форма комментария

Оставьте комментарий

Напишите, что думаете. Нам важно ваше мнение!