Клауза WITH
ClickHouse поддерживает общие табличные выражения (CTE, Common Table Expressions), общие скалярные выражения и рекурсивные запросы.
Общие табличные выражения
Общие табличные выражения представляют собой именованные подзапросы.
На них можно ссылаться по имени в любом месте SELECT-запроса, где допускается табличное выражение.
На именованные подзапросы можно ссылаться по имени в области видимости текущего запроса или в областях видимости дочерних подзапросов.
Каждое обращение к общему табличному выражению в SELECT-запросах всегда заменяется подзапросом из его определения, если CTE явно не определено как материализованное (см. Материализованные общие табличные выражения).
Рекурсия предотвращается за счёт скрытия текущего CTE в процессе разрешения идентификаторов.
Обратите внимание, что CTE не гарантируют одинаковые результаты во всех местах, где они вызываются, поскольку запрос будет выполняться повторно для каждого случая использования.
Синтаксис
Пример
Пример повторного выполнения подзапроса:
Если бы CTE возвращали именно результаты, а не просто фрагмент кода, то вы бы всегда видели 1000000.
Однако из-за того, что мы обращаемся к cte_numbers дважды, случайные числа генерируются каждый раз заново и, соответственно, мы видим разные случайные результаты: 280501, 392454, 261636, 196227 и так далее...
Материализованные общие табличные выражения
По умолчанию ClickHouse подставляет подзапрос CTE в каждое место, где на него есть ссылка, и каждый раз выполняет его заново.
Добавление ключевого слова MATERIALIZED указывает ClickHouse выполнить подзапрос CTE ровно один раз, сохранить результат во временной таблице и использовать эту таблицу для всех ссылок.
Это особенно полезно, когда один и тот же CTE используется в запросе несколько раз (например, в самосоединениях или нескольких подзапросах IN), поскольку лежащее в основе вычисление выполняется только один раз.
Материализованные CTE — экспериментальная функция.
Для их использования должны быть включены analyzer и SETTING enable_materialized_cte.
Синтаксис
Когда использовать
Материализованные CTE наиболее полезны в следующих случаях:
- Один и тот же CTE используется в запросе более одного раза.
Без
MATERIALIZEDкаждая ссылка заново и независимо выполняет подзапрос. - CTE содержит недетерминированные функции, такие как
generateRandom. Материализация гарантирует, что все ссылки будут видеть одни и те же данные. - CTE включает ресурсоёмкие вычисления (агрегации, joins, большие сканирования), которые не следует повторять.
Если материализованный CTE используется только один раз, ClickHouse автоматически подставляет его обратно как обычный подзапрос, чтобы избежать лишних накладных расходов.
Примеры
Пример 1: Самосоединение по материализованному CTE
Без MATERIALIZED обе стороны join будут выполнять подзапрос независимо.
С MATERIALIZED таблица сканируется один раз, и обе стороны join читают из одной и той же временной таблицы.
Пример 2: Детерминированные результаты с недетерминированными функциями
Обычные CTE с generateRandom выдают разные результаты при каждом обращении.
Материализация CTE обеспечивает согласованность:
Поскольку обе ссылки читают из одних и тех же материализованных данных, результат всегда равен 1000000.
Пример 3: Цепочка материализованных CTE
Материализованные CTE могут ссылаться на другие материализованные CTE. ClickHouse определяет зависимости и материализует их в правильном порядке:
Порядок определений CTE не имеет значения — допускаются ссылки на определения, идущие ниже:
Ограничения
- Требуется экспериментальная SETTING:
enable_materialized_cteдолжна быть включена. - Требуется analyzer: материализованные CTE работают только при включенном analyzer (
enable_analyzer = 1). - Не поддерживается с
RECURSIVE: использование ключевых словMATERIALIZEDиRECURSIVEвместе не допускается и приводит к исключениюUNSUPPORTED_METHOD. - Коррелированные CTE запрещены: материализованный CTE не может ссылаться на столбцы из внешних областей видимости запроса.
Общие скалярные выражения
ClickHouse позволяет объявлять псевдонимы для произвольных скалярных выражений в предложении WITH.
Общие скалярные выражения могут использоваться в любой части запроса.
Если общее скалярное выражение ссылается на что‑то отличное от константного литерала, выражение может привести к появлению свободных переменных. ClickHouse разрешает любой идентификатор в ближайшей возможной области видимости, поэтому свободные переменные могут ссылаться на неожиданные сущности в случае конфликтов имён или привести к коррелированному подзапросу. Рекомендуется определять CSE как лямбда‑функцию (возможно только при включённом analyzer), связывающую все используемые идентификаторы, чтобы добиться более предсказуемого поведения при разрешении идентификаторов в выражениях.
Синтаксис
Примеры
Пример 1: Использование константного выражения в роли "переменной"
Пример 2: Использование функций высшего порядка для ограничения идентификаторов
Пример 3: Использование функций высшего порядка со свободными переменными
Следующие примеры запросов показывают, что несвязанные (unbound) идентификаторы разрешаются в сущности из ближайшей области видимости.
Здесь идентификатор extension не привязан в теле лямбда-функции gen_name.
Хотя extension определён как '.txt' в виде общего скалярного выражения в области определения и использования generated_names, он разрешается в столбец таблицы extension_list, потому что этот столбец доступен в подзапросе generated_names.
Пример 4: Удаление результата выражения sum(bytes) из списка столбцов в предложении SELECT
Пример 5: Использование результатов скалярного подзапроса
Пример 6: Повторное использование выражения в подзапросе
Рекурсивные запросы
Необязательный модификатор RECURSIVE позволяет запросу WITH ссылаться на результат собственного выполнения. Пример:
Пример: Сумма целых чисел от 1 до 100
Рекурсивные CTE зависят от анализатора запросов, представленного в версии 24.3. Если вы используете версию 24.3+ и сталкиваетесь с исключением (UNKNOWN_TABLE) или (UNSUPPORTED_METHOD), это означает, что анализатор отключён для вашего экземпляра, роли или профиля. Чтобы активировать анализатор, включите настройку allow_experimental_analyzer или обновите настройку compatibility до более новой версии.
Начиная с версии 24.8 анализатор полностью переведён в продуктивный режим, а настройка allow_experimental_analyzer была переименована в enable_analyzer.
Общая форма рекурсивного запроса WITH всегда состоит из нерекурсивного выражения, затем UNION ALL, затем рекурсивного выражения, при этом только рекурсивное выражение может содержать ссылку на собственный результат запроса. Рекурсивный CTE-запрос выполняется следующим образом:
- Выполнить нерекурсивное выражение. Поместить результат выполнения нерекурсивного выражения во временную рабочую таблицу.
- Пока рабочая таблица не пуста, повторять следующие шаги:
- Выполнить рекурсивное выражение, подставив текущее содержимое рабочей таблицы вместо рекурсивной самоссылки. Поместить результат выполнения рекурсивного выражения во временную промежуточную таблицу.
- Заменить содержимое рабочей таблицы содержимым промежуточной таблицы, затем очистить промежуточную таблицу.
Рекурсивные запросы обычно используются для работы с иерархическими или древовидными данными. Например, мы можем написать запрос, который выполняет обход дерева:
Пример: Обход дерева
Сначала создадим таблицу для дерева:
Мы можем обойти это дерево с помощью следующего запроса:
Пример: Обход дерева
Порядок обхода
Чтобы получить порядок обхода в глубину, для каждой результирующей строки мы вычисляем массив строк, которые уже были посещены:
Пример: Обход дерева в глубину
Чтобы создать порядок обхода в ширину, стандартный подход — добавить столбец, который хранит глубину поиска:
Пример: Обход дерева в порядке обхода в ширину
Обнаружение циклов
Сначала создадим таблицу графа:
Мы можем обойти этот граф с помощью такого запроса:
Пример: Обход графа без проверки на циклы
Но если мы добавим цикл в этом графе, предыдущий запрос приведёт к ошибке Maximum recursive CTE evaluation depth:
Стандартный способ обработки циклов состоит в том, чтобы вычислить массив уже посещённых узлов:
Пример: Обход графа с обнаружением циклов
Бесконечные запросы
Также можно использовать бесконечные рекурсивные CTE-запросы, если во внешнем запросе используется LIMIT:
Пример: Бесконечный рекурсивный CTE-запрос