В запросе объявлены несколько CTE, один из которых использует другой. Что определяет допустимый порядок их разрешения?
Допустимый порядок определяется зависимостями между CTE: блок, который ссылается на другой блок, должен видеть его имя по правилам области видимости конкретного диалекта. В обычном нерекурсивном WITH это обычно означает, что зависимый CTE объявляется после используемого.
Это не означает последовательное физическое выполнение. После проверки зависимостей оптимизатор может встроить CTE, переставить вычисления, материализовать результат или вообще не вычислять неиспользуемый блок.
CTE появился как способ дать промежуточному результату запроса имя и разложить сложную логику на читаемые этапы. До этого разработчикам приходилось глубже вкладывать подзапросы в FROM, из-за чего зависимости между частями запроса было труднее анализировать.
Именованные блоки также сделали удобнее рекурсивные запросы и повторное использование одного логического результата. При этом CTE остаётся частью одного оператора, а не универсальным временным объектом базы данных.
Если CTE ссылается на имя, которое ещё недоступно по правилам диалекта, запрос не выполнится. Простая перестановка блоков может исправить синтаксическую или семантическую ошибку, но она не задаёт порядок фактического чтения таблиц.
Неверное предположение о последовательном выполнении приводит к ошибкам оптимизации: например, разработчик может ожидать, что тяжёлый CTE будет рассчитан один раз, хотя оптимизатор встроит его в несколько мест, либо считать, что независимый CTE обязательно будет вычислен, хотя он не используется в итоговом результате.
Каждый CTE образует именованный запрос. Его зависимости можно представить как ориентированный граф: если второй обращается к первому, между ними есть направленная связь от второго к первому. Такой граф не должен содержать недопустимых циклов, кроме явно поддерживаемой рекурсии.
В обычном WITH распространённое правило таково: CTE может ссылаться на ранее объявленные CTE, но не на последующие. Рекурсивный режим разрешает самоссылку рекурсивного CTE, однако взаимные ссылки и точные ограничения зависят от СУБД.
Здесь active зависит от base, поэтому base объявлен раньше. Но этот текст не гарантирует, что СУБД сначала полностью построит base, затем active: оптимизатор может протолкнуть фильтр, объединить этапы или выбрать материализацию.
Независимые CTE не образуют зависимости друг от друга, поэтому их порядок обычно влияет только на читаемость и соответствие правилам видимости. Для корректного запроса следует сначала объявлять базовые блоки, затем блоки, использующие их, а рекурсивные зависимости оформлять специальным синтаксисом поддерживаемой СУБД.
В отчёте были выделены три этапа: отбор заказов, агрегация по клиентам и присоединение итогов к справочнику клиентов. Разработчик разместил агрегирующий CTE перед CTE с отбором и получил ошибку неизвестного имени.
Рассматривались два варианта. Можно было продублировать исходный подзапрос внутри агрегации, но это ухудшило бы читаемость и повысило риск расхождения логики. Можно было переставить блоки в соответствии с зависимостями; этот вариант сохранил структуру запроса и не навязал лишнюю материализацию.
Выбрали второй вариант: сначала объявили CTE отбора, затем CTE агрегации, затем итоговый запрос. После проверки плана выполнения убедились, что порядок деклараций не стал предположением о физическом порядке вычислений, а оптимизатор выбрал подходящую стратегию для конкретного запроса.
Гарантирует ли порядок CTE порядок выполнения?
Нет. Порядок объявления определяет допустимость ссылок и читаемую структуру зависимостей, но не обязан быть планом исполнения. Физический порядок выбирает оптимизатор с учётом стоимости, ограничений семантики и настроек материализации.
Можно ли ссылаться на CTE, объявленный ниже?
В обычном нерекурсивном WITH обычно нельзя: имя последующего CTE ещё не находится в области видимости текущего определения. Перестановка блоков часто решает проблему, но для рекурсивных и взаимно зависимых конструкций нужно учитывать правила конкретной СУБД.
Вычисляется ли CTE, который не используется в финальном запросе?
Нельзя считать это гарантированным. Оптимизатор может удалить неиспользуемую часть, а при наличии побочных эффектов или специальных конструкций действуют дополнительные правила диалекта. Поэтому CTE не следует использовать как способ принудительно выполнить отдельный этап; для этого нужны подходящие постоянные или временные объекты и явно поддерживаемые механизмы.