Клауза WITH
ClickHouse поддерживает общие табличные выражения (CTE, Common Table Expressions), общие скалярные выражения и рекурсивные запросы.
Общие табличные выражения
Общие табличные выражения (CTE) представляют собой именованные подзапросы.
К ним можно обращаться по имени в любом месте SELECT-запроса, где допускается табличное выражение.
Именованные подзапросы могут использоваться по имени в области видимости текущего запроса или во внутренних областях видимости дочерних подзапросов.
Каждое обращение к общему табличному выражению в SELECT-запросах всегда подменяется подзапросом из его определения.
Рекурсия предотвращается за счёт исключения текущего CTE на этапе разрешения идентификаторов.
Обратите внимание, что CTE не гарантируют одинаковые результаты во всех местах, где они используются, поскольку запрос будет выполняться повторно для каждого использования.
Синтаксис
Пример
Пример повторного выполнения подзапроса:
Если бы CTE возвращали именно результаты, а не просто фрагмент кода, то вы бы всегда видели 1000000.
Однако из-за того, что мы обращаемся к cte_numbers дважды, случайные числа генерируются каждый раз заново и, соответственно, мы видим разные случайные результаты: 280501, 392454, 261636, 196227 и так далее...
Общие скалярные выражения
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-запрос