Программирование SQLDML и запросыРазработчик серверных приложений

Можно ли считать, что SQL вычисляет одинаковое выражение в списке SELECT ровно один раз для каждой строки?

Можно ли считать, что SQL вычисляет одинаковое выражение в списке SELECT ровно один раз для каждой строки?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

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

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

Исторический контекст

SQL использует декларативную модель: запрос описывает требуемый результат, а не пошаговый алгоритм его получения. Это позволяет СУБД выбирать план выполнения, применять упрощения, использовать индексы, менять порядок операций и оптимизировать вычисления.

Такой подход решает проблему переносимой формулировки результата без привязки к конкретному алгоритму исполнения. Обратная сторона — текстовое расположение выражений не является надёжной инструкцией о количестве и порядке их вычислений.

Постановка проблемы

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

Например, в таком запросе SQL не обязан вычислять арифметическое выражение один раз и затем повторно использовать результат:

SELECT price * (1 - discount) AS net_price, price * (1 - discount) * tax_rate AS gross_price FROM products;

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

Подробное решение

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

Для детерминированного выражения это обычно не меняет наблюдаемый результат. Для недетерминированных или изменяемых функций поведение зависит от конкретной СУБД, категории функции и её объявления; переносить такие ожидания между СУБД нельзя.

Вынесение выражения в подзапрос или обычное CTE тоже не всегда означает материализацию. Современные оптимизаторы часто трактуют их как часть общего запроса и встраивают обратно. Если требуется именно сохранить вычисленный промежуточный результат, используют механизм материализации, временную таблицу или другой явно поддерживаемый СУБД способ — с учётом стоимости записи и чтения данных.

Важно различать логический результат и физическое исполнение. SQL задаёт, какие значения должны быть получены, но обычно не обещает, сколько раз будет вызвано выражение и в каком физическом порядке это произойдёт.

Ситуация из практики

В отчёте дважды использовали сложное вычисление над миллионами строк: один раз для отображения, второй — для расчёта категории. Команда вынесла вычисление в CTE и ожидала, что оно будет выполнено единожды, но СУБД встроила CTE в общий план, поэтому ожидаемого выигрыша не произошло.

Рассматривались три варианта. Повторить выражение в SELECT было проще, но не давало контроля над стоимостью; обычный CTE улучшал читаемость, но не гарантировал материализацию; временная таблица требовала дополнительной операции записи, зато явно фиксировала промежуточный результат.

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

Что кандидаты часто упускают

  1. Гарантирует ли алиас вычисляемого столбца повторное использование значения в том же списке SELECT?

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

  2. Можно ли использовать повторное выражение для получения одинакового значения функции текущего времени или случайной функции?

    Нет, без отдельной гарантии конкретной СУБД и конкретной функции. Для таких функций важны правила стабильности: функция может возвращать одно значение на запрос, одно на строку или новое значение при каждом вызове. Это поведение нельзя выводить только из того, что текст функции написан дважды.

  3. Как надёжно проверить, повторяется ли вычисление в конкретной СУБД?

    План выполнения помогает увидеть операции и их стоимость, но не всегда доказывает точное число вызовов пользовательской функции. Для практической проверки используют план, профилирование и контролируемый тест, а для требуемой семантики — официальные гарантии СУБД, материализацию или предварительное сохранение результата. Оптимизацию нельзя считать контрактом, если документация её прямо не гарантирует.