На практике CTE особенно полезен там, где запрос уже перестал быть одной простой выборкой. Например, сначала нужно отобрать активных пользователей, потом посчитать по ним события за окно, потом агрегировать покупки, потом собрать retention-признаки и только после этого вывести финальную витрину. Всё это можно попытаться написать вложенными подзапросами в одну огромную конструкцию. Формально это тоже будет SQL. Но читать и проверять такую конструкцию станет тяжело уже через неделю.
SQL лучше учить как систему, в которой каждая новая конструкция помогает делать аналитическое мышление более управляемым. Именно в этом смысле CTE важен не сам по себе, а как шаг к более взрослой работе с длинными витринами, признаками и промежуточными расчётами.
Именно поэтому в 2026 году CTE стоит воспринимать не как синтаксическую игрушку, а как технику структурирования аналитической логики. Особенно в Data Science, где SQL всё чаще становится первым этапом feature engineering, построения витрин, расчёта labels и подготовки экспериментальных срезов.
Главная интуиция: CTE — это именованный шаг вычисления
Если говорить совсем просто, CTE позволяет дать имя промежуточному результату и использовать это имя дальше в запросе. Это похоже на переменные в программировании. Вместо того чтобы повторять длинное выражение или прятать его глубоко внутрь вложенного запроса, мы делаем отдельный блок: сначала считаем одно, потом на его основе следующее, потом финальный результат.
Именно это и делает аналитику чище. Запрос начинает читаться как маршрут рассуждения. Не как хаотичная стена SQL, а как последовательность логических этапов. Для командной работы это огромный плюс. Другой человек может быстро понять, где именно отбираются пользователи, где считаются агрегаты, где фильтруется дата, а где строится итоговая метрика.

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

Как формально устроен CTE
Математически на CTE удобно смотреть как на композицию отображений. Один промежуточный шаг берёт таблицу или набор таблиц и преобразует их в новую таблицу. Следующий шаг работает уже с этим новым объектом. Именно так и строится большинство хороших аналитических запросов.
(T_k = f_k(T_{k-1}))
Раздел математики: теория функций и дискретная математика.
Что означает каждый символ:
(T_{k-1}) — промежуточная таблица или результат предыдущего шага.
(f_k) — очередное преобразование: фильтрация, join, агрегация, window function или другая SQL-операция.
(T_k) — результат текущего шага после применения преобразования.
Формула нужна не для абстрактной красоты, а чтобы увидеть главную идею CTE: длинный запрос естественно раскладывается на последовательность функций над таблицами.
Численный пример: пусть (T_0) — исходная таблица событий. Тогда (f_1) может оставить только события логина, а (f_2) посчитать по ним число активных дней на пользователя. В результате (T_2=f_2(f_1(T_0))) уже становится осмысленной feature-таблицей, а не сырым журналом событий.
Такой взгляд очень полезен для ML-пайплайнов. Он помогает не смешивать сырые данные, фильтрацию, агрегации и финальную выборку в один непрозрачный блок. Каждый CTE становится понятным этапом преобразования.
Именно поэтому CTE естественно читать рядом с материалом про feature lineage: как только промежуточные шаги становятся именованными и прозрачными, происхождение признака перестаёт быть скрытой магией внутри длинного SQL.

CTE как средство проверки логики
Одна из самых сильных сторон CTE в том, что каждый промежуточный шаг можно мысленно и практически проверить отдельно. Мы можем посмотреть, сколько строк даёт блок active_users, какие поля выходят из orders_agg, не раздувает ли joined_features количество строк. Это резко упрощает дебаг. В монолитном вложенном запросе такие ошибки прячутся намного глубже.
Именно поэтому CTE особенно хорош в задачах, где важна правильность каждой стадии: cohort analysis, retention, unit economics, churn features, labels для классификации, временные признаки и сложные joins с бизнес-ограничениями.
А в задачах со временем это ещё и прямая защита от логических ошибок: когда шаги расчёта хорошо разделены, становится легче увидеть, не просочилось ли будущее в train. Поэтому эту статью особенно полезно держать рядом с разбором data leakage в фичах времени.
Когда WITH делает аналитику действительно чище
WITH полезен тогда, когда каждый блок отвечает на отдельный понятный вопрос. Например: кто является базовой популяцией? какие события берём? как агрегируем по пользователю? как джойним признаки? какой финальный фильтр? Если блоки named и логически чистые, запрос начинает читаться как рассказ. Это и есть признак хорошего CTE-стиля.
Если же WITH используется просто для того, чтобы разбить хаос на ещё несколько кусков хаоса, пользы становится меньше. CTE не заменяет хорошее мышление. Он лишь помогает выразить его в понятной форме.
Почему CTE особенно важен в Data Science
В аналитике запрос часто живёт не один день. Он переходит из notebook в dashboard, потом в production-скрипт, потом в расчёт признаков для модели. И на каждом этапе в него вносят изменения. Если запрос изначально написан как гигантская конструкция без смысловых блоков, поддержка становится дорогой и рискованной. Каждое исправление может сломать что-то в другом месте.
CTE делает SQL ближе к инженерному коду. Мы получаем именованные этапы, локальные единицы рассуждения и более понятную структуру. Для ML это особенно важно, потому что ошибки в подготовке данных часто опаснее, чем ошибки в самой модели. Неправильный SQL может тихо испортить таргет, утянуть leakage или нарушить one-row-per-entity структуру.
CTE и производительность: помогает ли WITH ускорять запрос
Здесь важно быть честным. CTE сам по себе не является ускорителем. Его главная сила — структурирование. В одних движках CTE может инлайниться в общий план, в других — материализоваться, в третьих поведение зависит от версии и контекста. Поэтому воспринимать WITH как магическую кнопку ускорения — ошибка.
Но есть интересный практический эффект. Понятный запрос легче оптимизировать. Если мы видим, что дорогой именно шаг с join или именно блок с сортировкой, то можем точечно переписать его. То есть CTE не обязательно ускоряет напрямую, но почти всегда улучшает управляемость performance-работы.
Поэтому логичным продолжением здесь становится и материал про SQL performance: сначала мы делаем запрос читаемым и проверяемым, а потом уже оптимизируем действительно дорогие места, а не гадаем по всему SQL целиком.
(C_{query} \approx \sum_{k=1}^{m} C_k)
Раздел математики: дискретная математика и асимптотический анализ.
Что означает каждый символ:
(C_{query}) — общая вычислительная стоимость запроса.
(m) — число смысловых этапов или блоков запроса.
(C_k) — стоимость отдельного шага.
Формула отражает инженерную интуицию: производительность длинного SQL часто складывается из стоимости нескольких отдельных этапов. Если мы хорошо видим эти этапы, мы лучше понимаем, что именно оптимизировать.
Численный пример: если фильтрация по дате дешёвая, агрегация средняя по стоимости, а join с широкой таблицей дорогой, то узкое место сидит именно в join-блоке. CTE позволяет быстрее локализовать эту часть плана и работать именно с ней, а не гадать по всему запросу целиком.
Геометрический смысл CTE
Если смотреть геометрически, CTE позволяет двигаться по пространству данных этапами. Сначала мы выделяем нужную подобласть пользователей, потом проектируем события в пространство агрегатов, потом соединяем это с другой системой координат, например с покупками или подписками. Каждый шаг имеет собственный смысл. В результате аналитика перестаёт быть неразличимой массой преобразований и становится маршрутом через пространство данных.
Это очень похоже на feature engineering в ML. Мы редко хотим одним махом превратить сырую таблицу в идеальный набор признаков. Обычно мы идём по шагам: фильтрация, очистка, агрегация, нормализация, объединение. CTE — это естественный SQL-язык для такой поэтапной геометрии данных.
Когда CTE особенно полезен
Он особенно полезен в cohort analysis, retention-логике, временных окнах, расчёте unit economics, подготовке labels, склейке нескольких бизнес-источников и построении feature tables. Полезен и в том случае, когда один и тот же промежуточный результат логически нужен в нескольких следующих шагах. Даже если движок под капотом оптимизирует всё по-своему, для человека структура запроса становится намного чище.
В командной работе это почти всегда окупается. Новый аналитик или ML-инженер быстрее читает такой запрос и реже вносит поломки в незнакомую логику.
А если этот SQL потом становится частью production-контура, понятная структура помогает не только читать запрос, но и безопаснее выпускать изменения. В этом смысле CTE хорошо рифмуется и с темой model deployment, где цена непрозрачной подготовки данных уже становится по-настоящему инженерной.
Какие ошибки здесь делают чаще всего
Первая ошибка — использовать CTE без явных смысловых названий. Вторая — дробить запрос на блоки, которые ничего концептуально не проясняют. Третья — думать, что WITH автоматически ускоряет запрос. Четвёртая — не проверять промежуточные CTE на кардинальность строк и получать тихие дубли после join. Пятая — превращать CTE в кладбище временных алиасов без бизнес-смысла.
Есть и более тонкая ошибка: бояться CTE ради “производительности”, хотя на самом деле проблема не в WITH, а в логике данных, join, сортировках или лишнем сканировании. В таких случаях отказ от CTE не лечит запрос. Он просто делает его менее читаемым.

Python: как мыслить CTE-подобно при подготовке данных
import pandas as pd # Подключаем pandas для работы с табличными данными.
events = pd.read_csv("events.csv") # Загружаем таблицу событий.
orders = pd.read_csv("orders.csv") # Загружаем таблицу заказов.
active_users = events[events["event_type"] == "login"][["user_id", "event_date"]] # Выделяем пользователей с логинами как отдельный осмысленный шаг.
active_users = active_users.drop_duplicates() # Убираем дубли пользователей по дате.
orders_agg = orders.groupby("user_id", as_index=False)["amount"].sum() # Считаем суммарные покупки на пользователя как второй шаг.
orders_agg = orders_agg.rename(columns={"amount": "total_amount"}) # Даем агрегату понятное имя.
feature_table = active_users.merge(orders_agg, on="user_id", how="left") # Соединяем два промежуточных результата в итоговую feature-таблицу.
feature_table["total_amount"] = feature_table["total_amount"].fillna(0) # Заполняем пропуски нулем для пользователей без заказов.
print(feature_table.head()) # Смотрим результат после всех шагов.Этот пример написан на Python, но логика у него полностью CTE-подобная. Мы не пытаемся сделать всё одной строкой. Мы строим именованные промежуточные этапы, каждый из которых можно проверить отдельно. Именно так и должен мыслиться хороший SQL с WITH.
Что важно вынести из темы
SQL CTE в Data Science нужен прежде всего для того, чтобы длинная аналитическая логика становилась управляемой. WITH помогает разложить запрос на осмысленные этапы, сделать его читаемым, упростить проверку промежуточных результатов и снизить вероятность логических ошибок в аналитике и ML-пайплайнах.
Если сформулировать совсем коротко, CTE не делает SQL “умнее”, но делает мышление над SQL чище. А в зрелой аналитике именно это чаще всего и даёт наибольшую пользу: не магический синтаксис, а понятная структура рассуждения над данными.
А дальше уже проще переходить и к системам, где та же логика дробления на этапы проявляется на уровне эксплуатации модели. Поэтому после CTE естественно читать и материал про batch inference и real-time inference, где вопрос «как разбить вычисление на осмысленные шаги» появляется уже на уровне production-режима.
Kaggle notebook по теме:
https://www.kaggle.com/code/jaspreetlall/ex-3-of-intro-to-sql-with-working-setup-code/code