← назад к разделу

Девять из десяти запросов, которые вы напишете в жизни, — это чтение данных: найти заказ, посмотреть статус, проверить, что запись появилась. Всё чтение делается одной командой — SELECT. Она безопасна: сколько ни выполняй, данные не изменятся. Продолжаем на базе магазина с таблицами customer и orders.

SELECT title, price FROM products WHERE price < 5000 Колонка · 12990 · audio Кофемолка · 4990 · kitchen Наушники · 8990 · audio Ланчбокс · 890 · kitchen WHERE отбирает строки Кофемолка · 4990Ланчбокс · 890SELECT оставляет колонки без ORDER BY порядок строк не гарантирован — «вчера было иначе» не баг базы

WHERE решает, какие строки останутся, SELECT — какие колонки показать. Это два разных сита.

SELECT и FROM: что и откуда

Минимальный запрос — «покажи всё из таблицы»:

живой пример

SELECT * FROM orders;
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

FROM называет таблицу, SELECT — какие столбцы показать; звёздочка значит «все». На практике чаще перечисляют нужные столбцы явно:

живой пример

SELECT id, status, total_amount FROM orders;
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

Так результат читается легче, а на широких таблицах (по 40 столбцов) это единственный способ не утонуть. Точка с запятой завершает запрос; регистр не важен (select = SELECT), но команды принято писать заглавными — глазу проще отделять их от имён.

Звёздочка удобна, когда смотрят глазами, и вредна в коде приложения. Колонки в таблице меняются: добавили internal_comment — и он поехал в ответ ручки вместе с остальным; добавили длинное description — и запрос стал тащить мегабайты, которых никто не просил; переставили колонки местами — и код, читающий их по номеру, стал читать не то. Плюс по SELECT * не видно, что коду действительно нужно, а значит, нельзя понять, можно ли удалить колонку. Правило простое: звёздочка в ручном запросе, явный список — везде, где запрос сохраняют.

А чтобы узнать, из чего вообще выбирать, спрашивают базу. В psql это \d orders — колонки, типы и обязательность одним экраном. Из любого клиента то же показывает запрос к служебному каталогу:

живой пример

SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = 'orders'
ORDER BY ordinal_position;
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

is_nullable = NO означает «поле всегда заполнено», YES — «бывает пустым», и это первое, что стоит знать про незнакомую таблицу: пустые значения ведут себя иначе, чем все ожидают.

WHERE: оставить только нужное

Вся сила запросов — в фильтре WHERE: он оставляет строки, для которых условие истинно.

живой пример

SELECT id, total_amount FROM orders WHERE status = 'PAID';
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

Сравнения: =, <> (не равно), >, <, >=, <=. Текстовые значения — в одинарных кавычках: 'PAID'. Числа — без кавычек: total_amount > 1000.

Условия соединяются через AND и OR:

живой пример

SELECT id FROM orders
WHERE status = 'PAID' AND total_amount > 1000;
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

С OR внимательнее: AND склеивает сильнее, чем OR, и без скобок смешанное условие читается не так, как написано:

-- ожидают оплаты ИЛИ (оплачены И дороже 500): дешёвые неоплаченные тоже попадут
WHERE status = 'PENDING_PAYMENT' OR status = 'PAID' AND total_amount > 500
-- то, что обычно имели в виду: любой из двух статусов, и все дороже 500
WHERE (status = 'PENDING_PAYMENT' OR status = 'PAID') AND total_amount > 500

Смешали AND и OR — ставьте скобки, даже если знаете приоритет: их поставят и за читателя.

Три полезных фильтра сверх сравнений:

  • IN — значение из списка: WHERE status IN ('PENDING_PAYMENT', 'PAID') — короче, чем цепочка OR.
  • BETWEEN — диапазон включительно: WHERE total_amount BETWEEN 500 AND 1500. С колонками времени так писать нельзя: created_at BETWEEN '2026-08-01' AND '2026-08-31' обрежет весь последний день, кроме его полуночи, потому что верхняя граница читается как 2026-08-31 00:00:00. Для времени берут пару сравнений: created_at >= '2026-08-01' AND created_at < '2026-09-01'.
  • IS NULL — пустое значение: WHERE paid_at IS NULL находит неоплаченные заказы. Через = NULL пустоту не ищут: такой запрос всегда возвращает ноль строк и молчит об этом; почему — в статье NULL и типы данных.
  • LIKE — поиск по шаблону в строках: WHERE email LIKE '%@gmail.com'. Процент означает «любые символы»: 'Anna%' — начинается с «Anna», '%ova%' — содержит «ova» (имена в учебной базе записаны латиницей, поэтому и шаблоны такие). В PostgreSQL есть регистронезависимый вариант ILIKE — для поиска по именам и почтам обычно нужен именно он. Оговорка про нашу песочницу: почта там объявлена особым типом CITEXT, который сам не различает регистр, и LIKE '%@EXAMPLE.COM' находит те же девять строк, что и запись строчными. С обычным текстовым столбцом так не будет — вот там ILIKE и понадобится.

Отработать здесь: Неоплаченные заказы · Заказы Анны в двух статусах · Товары в диапазоне цен · Покупатели с фамилией на «-ova»

ORDER BY и LIMIT: порядок и порция

Без явной сортировки база возвращает строки в непредсказуемом порядке — сегодня так, завтра иначе. Нужен порядок — скажите об этом:

живой пример

SELECT id, total_amount FROM orders
ORDER BY total_amount DESC
LIMIT 10;
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

ORDER BY total_amount DESC — по убыванию суммы (ASC — по возрастанию, он по умолчанию). LIMIT 10 — только первые десять строк. Вместе это «топ-10 самых дорогих заказов» — и одновременно защита: если в таблице миллионы строк, запрос без LIMIT будет тащить их все.

Сортировать можно по нескольким столбцам: ORDER BY status, total_amount DESC — сначала по статусу, а внутри каждого статуса — по убыванию суммы. Слово «группировка» здесь не к месту: строки только раскладываются по порядку, ни одна из них никуда не схлопывается.

Рядом с LIMIT живёт OFFSET — «пропусти первые N строк». Так листают страницы:

живой пример

SELECT id, total_amount FROM orders
ORDER BY total_amount DESC, id
LIMIT 10 OFFSET 20;
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

Это третья страница по десять строк. Две оговорки, из-за которых постраничный вывод ломается чаще всего. Первая: без ORDER BY страницы бессмысленны, а при сортировке по неуникальной колонке — почти бессмысленны. Если у двух заказов одинаковая сумма, их взаимный порядок база не обещает, и одна и та же строка может приехать и на второй странице, и на третьей, а другая не приехать вовсе. Поэтому вторым ключом сортировки ставят что-то уникальное, как id в примере. Вторая: OFFSET не бесплатен. Чтобы пропустить двадцать тысяч строк, база их всё равно прочитает и отсортирует, поэтому глубокие страницы тормозят тем сильнее, чем дальше листаешь. В приложениях вместо номера страницы запоминают последнюю показанную строку и просят «следующие десять после неё» — условие вида WHERE (total_amount, id) < (последняя_сумма, последний_id) читает ровно нужное.

Отработать здесь: Витрина от дешёвого к дорогому · Лента заказов покупателя: по статусу, свежие сверху

DISTINCT: без повторов

Вопрос «какие вообще бывают статусы заказов» кажется вопросом про справочник, но справочника может не быть, а статусы лежат прямо в колонке orders.status, по одному на строку. SELECT status FROM orders вернёт столько строк, сколько заказов, и большинство повторяются. DISTINCT оставляет каждую комбинацию значений один раз:

живой пример

SELECT DISTINCT status
FROM orders
ORDER BY status;
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

DISTINCT относится ко всем колонкам в SELECT сразу: SELECT DISTINCT customer_id, status уберёт повторы пар, а не отдельных значений. Выполняется он после WHERE и до ORDER BY, поэтому сортировать можно только по тому, что осталось в выдаче. Считать уникальные, а не показывать, помогает его пара из следующей статьи: COUNT(DISTINCT customer_id). Одно предупреждение: если DISTINCT понадобился, чтобы убрать задвоение после JOIN, это лечит симптом, а причина в соединении, о ней в статье про JOIN.

В каком порядке на самом деле выполняется запросспросят на собеседовании

Псевдоним из SELECT в WHERE не работает, а в ORDER BY работает; COUNT в WHERE написать нельзя, а в HAVING можно. Это не капризы базы: запрос пишется в порядке SELECT … FROM … WHERE … ORDER BY, а выполняется в другом. Сначала FROM собирает строки, потом WHERE их фильтрует, и только потом SELECT вычисляет колонки и псевдонимы: на момент фильтрации псевдонима ещё нет, а ORDER BY идёт после SELECT, и там он уже есть. Агрегаты считаются по группам, а группы появляются после WHERE, поэтому фильтр по COUNT живёт в HAVING, который стоит после группировки.

Полный порядок такой: FROM/JOIN → WHERE → GROUP BY → HAVING → оконные функции → SELECT → DISTINCT → ORDER BY → LIMIT. Группы, окна и вынесенные вперёд запросы через WITH встретятся дальше в цикле, начиная со статьи про подзапросы. И LIMIT не спасает от тяжёлой сортировки: чтобы отдать первые 20 строк по ORDER BY, база обязана сначала упорядочить всё, если нет подходящего индекса.

FROM собирает строки WHERE отбирает строки GROUP BY складывает в группы HAVING отбирает группы оконные считают по соседям SELECT колонки и псевдонимы DISTINCT убирает повторы ORDER BY задаёт порядок LIMIT отрезает порцию

Смотрите, на каком месте стоит SELECT: он считает псевдонимы уже после WHERE, поэтому в фильтре псевдонима ещё нет, а в ORDER BY он есть.

Где это применяется

SELECT … FROM … WHERE … — рабочая лошадка на каждый день: проверить, что заказ из интерфейса действительно создался; убедиться, что после отмены статус сменился; найти пользователя по кусочку почты через LIKE; глянуть последние записи через ORDER BY created_at DESC LIMIT 20. Как только вы перестаёте просить разработчика «посмотри в базе, а?» и смотрите сами — скорость вашей работы меняется скачком.

Где спотыкаются начинающие:

  • Двойные кавычки вместо одинарных. WHERE status = "PAID" во многих СУБД — ошибка: двойные кавычки — для имён столбцов, одинарные — для значений.
  • Сравнение без учёта регистра и пробелов. 'Paid', 'PAID' и 'PAID ' — три разных значения. Не находится очевидное — проверьте регистр и хвостовые пробелы.
  • Полагаются на «естественный» порядок строк. Без ORDER BY порядок не гарантирован — «вчера первая строка была другой» не баг базы, а отсутствие сортировки в запросе.

Коротко

  • SELECT называет колонки, FROM таблицу, WHERE оставляет строки по условию (=, LIKE, IN, BETWEEN), ORDER BY задаёт порядок, LIMIT порцию.
  • База выполняет запрос не в порядке написания: FROM → WHERE → SELECT → DISTINCT → ORDER BY → LIMIT; поэтому псевдоним из SELECT в WHERE не виден.
  • DISTINCT убирает повторы по всем колонкам выдачи сразу; для подсчёта уникальных есть COUNT(DISTINCT …).
  • LIMIT без ORDER BY отдаёт случайную порцию: порядок строк без сортировки база не обещает.
  • OFFSET листает страницы, но читает и пропущенное; сортировать надо с уникальным ключом, иначе строки перескакивают между страницами.
  • BETWEEN включает обе границы: для колонок времени берут >= и <, иначе последний день диапазона теряется.

Что почитать дальше