Одна из самых полезных тем в SQL — это порядок выполнения запроса. Именно она помогает понять:

  • почему WHERE не видит агрегаты
  • почему HAVING работает после GROUP BY
  • почему алиас из SELECT нельзя использовать везде
  • почему итог запроса иногда не совпадает с тем, что ты ожидал

И это же одна из любимых тем на собеседованиях.

Почему это важно

Когда мы читаем SQL, мы видим его сверху вниз:

SELECT ...
FROM ...
WHERE ...
GROUP BY ...
HAVING ...
ORDER BY ...

Но выполняется он не в этом порядке.

SQL сначала собирает и фильтрует данные, потом группирует, потом считает агрегаты, потом формирует итоговый вывод.

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

Если упростить, то чаще всего порядок такой:

  1. FROM
  2. JOIN
  3. WHERE
  4. GROUP BY
  5. агрегатные функции
  6. HAVING
  7. оконные функции
  8. SELECT
  9. DISTINCT
  10. ORDER BY
  11. LIMIT

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

1. FROM — откуда берем данные

На этом этапе SQL определяет, из какой таблицы или подзапроса будет строиться результат.

FROM orders

Если таблиц несколько, то дальше подключаются JOIN.

2. JOIN — как соединяются таблицы

После FROM SQL начинает соединять таблицы.

FROM orders o
LEFT JOIN customers c
    ON o.customer_id = c.customer_id

Именно здесь часто возникают:

  • дубли строк
  • неожиданное увеличение результата
  • потеря строк при неправильном типе join

Поэтому если после JOIN у тебя “внезапно все умножилось”, искать проблему нужно именно здесь.

3. WHERE — фильтрация строк до группировки

WHERE работает по обычным строкам, до агрегирования.

WHERE status = 'paid'

Именно поэтому в WHERE нельзя писать:

WHERE SUM(amount) > 1000

На этом этапе SUM(amount) еще не существует.

4. GROUP BY — группировка

После фильтрации SQL собирает строки в группы.

GROUP BY customer_id

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

5. Агрегатные функции — SUM, COUNT, AVG и другие

На этом этапе вычисляются агрегаты:

  • SUM()
  • COUNT()
  • AVG()
  • MIN()
  • MAX()

Например:

SELECT
    customer_id,
    SUM(amount) AS total_amount
FROM orders
GROUP BY customer_id

SUM(amount) появляется только после того, как группы уже сформированы.

6. HAVING — фильтрация уже сгруппированного результата

HAVING работает после GROUP BY и после расчета агрегатов.

HAVING SUM(amount) > 1000

Вот почему SUM(amount) можно использовать в HAVING, но нельзя в WHERE.

Запоминалка:

  • WHERE — фильтр строк
  • HAVING — фильтр групп

7. Оконные функции

После агрегации и HAVING обычно уже можно применять оконные функции.

Например:

SELECT
    customer_id,
    total_amount,
    RANK() OVER (ORDER BY total_amount DESC) AS rank_num
FROM (
    SELECT
        customer_id,
        SUM(amount) AS total_amount
    FROM orders
    GROUP BY customer_id
) t

Оконные функции работают над уже подготовленным набором строк.

Поэтому они логически идут позже, чем GROUP BY и агрегаты.

8. SELECT — формирование итогового набора столбцов

Только теперь SQL окончательно собирает то, что попадет в вывод.

SELECT
    customer_id,
    SUM(amount) AS total_amount

Вот почему алиас, который создается в SELECT, не всегда доступен раньше.

Например, нельзя ожидать, что алиас уже существует в WHERE.

9. DISTINCT — удаление дублей

Если используется DISTINCT, он применяется уже к итоговому набору строк.

SELECT DISTINCT customer_id
FROM orders

То есть сначала формируется результат, потом из него убираются повторяющиеся строки.

10. ORDER BY — сортировка

После этого SQL сортирует итоговый результат.

ORDER BY total_amount DESC

Вот здесь алиасы из SELECT обычно уже можно использовать.

11. LIMIT — ограничение числа строк

И только в самом конце SQL обрезает результат по количеству строк.

LIMIT 10

Сначала сортировка, потом ограничение.

Именно поэтому:

ORDER BY revenue DESC
LIMIT 10

означает “сначала найти самых больших, потом взять 10”.

Частая ошибка №1: путать порядок написания и порядок выполнения

Мы видим запрос сверху вниз, но SQL работает не так.

Из-за этого и появляются вопросы:

  • почему WHERE не видит SUM()
  • почему HAVING работает
  • почему после JOIN результат уже изменился, хотя SELECT еще даже не сформировался

Частая ошибка №2: не понимать, где появляются дубликаты

Если число строк выросло, это почти никогда не из-за SELECT или ORDER BY.

Обычно причины:

  • JOIN
  • неправильный ключ соединения
  • many-to-many логика

Порядок выполнения помогает быстро локализовать проблему.

Частая ошибка №3: алиасы из SELECT

Например:

SELECT
    amount * 1.2 AS amount_with_tax
FROM orders
WHERE amount_with_tax > 100

Во многих СУБД это не сработает, потому что WHERE выполняется раньше SELECT.

Нужно писать либо выражение целиком:

WHERE amount * 1.2 > 100

либо выносить это в подзапрос / CTE.

Удобная схема для запоминания

Можно запомнить так:

  1. собрать строки
  2. соединить таблицы
  3. отфильтровать строки
  4. сгруппировать
  5. посчитать агрегаты
  6. отфильтровать группы
  7. посчитать окна
  8. показать нужные столбцы
  9. убрать дубли
  10. отсортировать
  11. обрезать результат

Это уже достаточно, чтобы уверенно отвечать на большую часть вопросов по теме.

Итог

Порядок выполнения SQL-запроса — это одна из тех тем, которые резко повышают качество понимания SQL.

Если ты держишь в голове эту логику, тебе проще:

  • писать запросы без ошибок
  • понимать поведение WHERE и HAVING
  • объяснять результат на собеседованиях
  • находить причины дублей после JOIN

Именно поэтому эту тему стоит не просто “знать”, а реально понимать.