Чтобы вывести уникальные значения в SQL, допишите DISTINCT сразу после SELECT: SELECT DISTINCT city FROM orders;. База уберёт из результата повторяющиеся строки и оставит по одной из каждой группы одинаковых. Сама таблица при этом не меняется, DISTINCT работает только с выводом запроса.
Если столбцов в списке несколько, уникальной считается их комбинация, а не каждый столбец отдельно. Базовый синтаксис SELECT с WHERE и сортировкой мы разбирали в статье о команде SELECT в MySQL, а с чего вообще начинать язык, рассказали в обзоре языка SQL.
Все запросы ниже мы запускали на MySQL 8.0 и PostgreSQL 16, спорные места ещё и на SQLite. Вывод скопирован из консоли, там, где базы отвечают по-разному, показаны оба варианта.
Синтаксис 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 при сортировке по возрастанию ставит первым.
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.
Частые ошибки с 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 |
|---|---|---|---|---|---|
| Нейросети для рабочих задач Перейти на сайт курса | 31 290 ₽ | - | 1 месяц | Обзор курса | |
| SQL-разработчик: тариф PRO Перейти на сайт курса | 89 900 ₽ | 7491 ₽/мес. | 2 месяца | Обзор курса | |
| SQL-разработчик: тариф Базовый Перейти на сайт курса | 79 900 ₽ | 3329 ₽/мес. | 3 месяца | Обзор курса | |
| SQL для анализа данных Перейти на сайт курса | 50 828 ₽ | 6203 ₽/мес. | 2 месяца | Обзор курса | |
| SQL с нуля | 134 640 ₽ | 5500 ₽/мес. | 12 месяцев | Обзор курса |
Больше программ — в полном каталоге курсов по SQL




