> SmartGate

Порядок выполнения SQL-запроса

Разница между порядком написания запроса и его логической обработкой

Теория

Несмотря на то, что мы изучили еще не все операторы, из которых складывается SQL-запрос, данную тему лучше изучить и понять сейчас. Когда вы составляете ваш SQL-запрос, то сначала пишете SELECT, затем FROM, потом ORDER BY и LIMIT. И естественно вам кажется, что эти части запроса работают в том же порядке, в котором они написаны сверху вниз. Но на самом деле это не так. Эти операторы работают не в том же порядке, в котором вы их написали.

Из-за этого могут начать появляться вопросы, которые сначала вам могут показаться странными. Почему псевдоним из SELECT нельзя использовать в операторе WHERE, но можно использовать в ORDER BY? Почему агрегатная функция не работает в WHERE, а в HAVING уже работает? (Изучение операторов WHERE и HAVING будет рассматриваться в следующих темах).

Ответ один: в SQL существует логический порядок выполнения операторов, который отличается от порядка написания этих операторов в SQL-запросе.

Синтаксический порядок написания операторов в запросе такой:

• SELECT
• FROM
• WHERE
• GROUP BY
• HAVING
• ORDER BY
• LIMIT

А если смотреть глазами PostgreSQL, то такой:

• сначала FROM
• потом WHERE
• потом GROUP BY
• потом HAVING
• потом SELECT
• потом ORDER BY
• и только в конце LIMIT

Давайте представим это как конвейер. Вначале на ленту кладут целую таблицу FROM. Потом с неё убирают лишние строки WHERE. Потом при необходимости собирают оставшиеся строки в группы GROUP BY. Затем отбрасывают лишние группы HAVING. Потом уже решают, какие именно столбцы показать в результате SELECT. И только в самом конце этот результат сортируют ORDER BY и, если нужно, обрезают по количеству строк LIMIT.

Если разобраться как этот конвейер работает, половина SQL загадок перестанет быть таковыми. Псевдоним из SELECT не работает в WHERE просто потому, что до шага SELECT СУБД ещё даже не дошла. А в ORDER BY он уже виден, потому что итоговый набор столбцов к этому моменту существует.

Это одна из самых важных тем. Понимание этой темы позволит взглянуть на SQL-запрос как на единую логичную конструкцию, состоящую из логических операций, где каждая текущая операция передаёт результат своей работы следующей операции.

Важно: хотя логический порядок выполнения операторов внутри запроса и отличается от синтаксического их написания, писать эти операторы нужно именно в порядке синтаксического правила, начиная с оператора SELECT. Если в запросе перепутать операторы местами, например LIMIT написать перед ORDER BY, запрос не будет работать, и вы получите ошибку.

Синтаксис

SELECT column_list         -- 5. формируется итоговый список столбцов
FROM table_name            -- 1. берутся строки из указанной таблицы
WHERE condition            -- 2. остаются только строки, которые прошли условие
GROUP BY group_columns     -- 3. оставшиеся строки собираются в группы
HAVING group_condition     -- 4. остаются только группы, которые прошли условие
ORDER BY sort_columns      -- 6. итоговый результат сортируется
LIMIT row_count;           -- 7. количество строк в результате ограничивается

-- порядок выполнения запроса
FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT
SQL

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

Часть запроса Что делает Когда реально срабатывает
FROM Определяет источник данных Самый ранний шаг
WHERE Фильтрует строки Сразу после FROM
GROUP BY Собирает строки в группы После фильтрации строк
HAVING Фильтрует уже готовые группы После GROUP BY
SELECT Формирует итоговые столбцы результата После строк и групп
ORDER BY Сортирует готовый результат После SELECT
LIMIT Оставляет нужное количество строк Самый последний шаг

1. Писать сверху вниз и исполнять сверху вниз — не одно и то же
Запрос на экране вашего монитора начинается с SELECT, потому что человеку так удобнее читать. Но PostgreSQL не может сначала выбрать столбцы, пока ещё не знает, из каких строк вообще будет строиться результат. Поэтому логический запуск начинается с FROM и дальше движется по конвейеру.


2. WHERE работает со строками, а HAVING — уже с группами
WHERE отбрасывает строки до группировки. HAVING включается позже, когда группы уже собраны. Поэтому агрегат вроде COUNT(*) в WHERE неуместен, а в HAVING — это как раз его место (эти темы будут рассмотрены дальше).


3. Псевдоним из SELECT не живёт в прошлом
Если столбец получил имя через AS внутри SELECT, это имя появляется только на шаге формирования результата. Именно поэтому псевдоним уже может использоваться в ORDER BY, но WHERE его ещё не видит.


4. С GROUP BY есть тонкий нюанс
В PostgreSQL группировку иногда можно записать по псевдониму или по номеру столбца. Это удобство синтаксиса.


5. LIMIT ограничивает готовый результат
Если перед LIMIT стоит сортировка, сначала PostgreSQL отсортирует строки, а потом возьмёт нужное количество строк сверху. Поэтому ORDER BY salary DESC LIMIT 3 и означает «три самые большие зарплаты», а не просто «какие-то три строки».

Примеры

1. Сначала фильтруем строки, потом уже показываем столбцы

Запрос написан с SELECT, но внутри СУБД сначала берёт строки из workers, потом оставляет только зарплаты от 90000 и выше, потом формирует итоговые столбцы, затем сортирует их и в самом конце обрезает до пяти строк. Это хороший пример, чтобы увидеть конвейер.

🔄 Попробуйте изменить запрос:

  • • Уберите LIMIT 5 и посмотрите, что изменится
  • • Замените порог 90000 на 80000

2. Псевдоним работает в ORDER BY

Здесь псевдоним discounted_price используется в сортировке. Это работает потому, что ORDER BY логически идёт после шага SELECT, а значит итоговый набор столбцов к этому моменту уже понятен.

🔄 Попробуйте изменить запрос:

  • • Попробуйте операцию сортировки написать перед операцией фильтрации WHERE. Будет ли запрос работать?

3. В WHERE выражение приходится повторять

Давайте посмотрим, почему псевдоним не работает в WHERE. Чтобы отфильтровать будущую повышенную зарплату, выражение salary * 1.1 приходится писать прямо в фильтре, так как псевдоним raised_salary появляется позже, чем отработает WHERE.

🔄 Попробуйте изменить запрос:

  • • Попробуйте в уме представить, что будет, если написать в WHERE псевдоним raised_salary, а затем напишите и проверьте

4. WHERE работает до группировки, HAVING — после

Фильтрация в WHERE сначала отбрасывает товары, дешевле 10000. Потом PostgreSQL собирает оставшиеся строки в группы по категории. И только после этого HAVING решает, какие группы вообще достойны остаться в результате. Поэтому COUNT(*) и пишется именно в HAVING. (Этот пример больше показательный, так как охватывает операторы, которые мы еще не изучали).

🔄 Попробуйте изменить запрос:

  • • Замените порог в WHERE с 10000 на 50000
  • • Поменяйте условие HAVING COUNT(*) >= 2 на псевдоним products_count >= 2

5. LIMIT ограничивает отсортированный результат

Последовательность здесь принципиальна. Сначала остаются только доставленные заказы. Потом они сортируются от самых поздних к самым ранним. После этого LIMIT 3 оставляет верхние три строки. В результате оказываются три самых свежих доставленных заказа, а не случайные три записи.

🔄 Попробуйте изменить запрос:

  • • Замените LIMIT 3 на LIMIT 5
  • • Попробуйте убрать сортировку и посмотрите как отличается результат

Типичные ошибки

Читают SQL как будто он исполняется сверху вниз На экране запрос действительно начинается с SELECT, но логически PostgreSQL сначала работает с FROM и WHERE. Если об этом забыть, появляются ошибочные ожидания от псевдонимов, агрегатов и сортировки. Пишут псевдоним из SELECT в WHERE Это одна из самых частых ловушек. До шага WHERE псевдоним ещё не появился, поэтому фильтр его не видит. Если нужно фильтровать по выражению, это выражение приходится повторять. Путают WHERE и HAVING WHERE фильтрует строки до группировки. HAVING фильтрует уже готовые группы. Можно прямо так просто это и запомнить. Думают, что LIMIT выбирает строки раньше сортировки Нет. Если перед LIMIT есть ORDER BY, сначала результат сортируется, а уже потом ограничивается. Делают выводы из удобства синтаксиса, а не из логики выполнения Например, PostgreSQL позволяет использовать псевдоним в GROUP BY. Это не значит, что группировка выполняется после SELECT. Это всего лишь удобство записи.

Практика

Проверь себя

Ответьте на вопросы, чтобы закрепить материал:

1
Какая часть запроса логически срабатывает раньше остальных в обычном SELECT-запросе?
2
Почему псевдоним из SELECT обычно не работает в WHERE, но работает в ORDER BY?
3
Где уместно писать условие с агрегатом вроде COUNT(*) >= 2?
4
Что логически произойдёт раньше в запросе с ORDER BY salary DESC LIMIT 3?
5
Какое утверждение про GROUP BY и псевдонимы из SELECT ближе всего к истине для PostgreSQL?
6
Напишите запрос к таблице workers, который покажет department_id и столбец workers_count. Сначала оставьте только работников с зарплатой не меньше 70000 и с указанной должностью. Потом сгруппируйте строки по department_id. В результат должны попасть только те отделы, где после такой фильтрации осталось минимум 2 сотрудника. Отсортируйте результат по workers_count от большего к меньшему, а затем по department_id.
7
Напишите запрос к таблице orders, который выведет order_id, user_id, order_date и столбец next_day. В next_day нужно показать order_date + INTERVAL '1 day'. Оставьте только заказы, у которых этот следующий день не раньше '2024-05-01 00:00:00'::timestamp, и одновременно исключите заказы со статусом 'отменён'. Отсортируйте результат по next_day от более поздней даты к более ранней, а затем по order_id.