В ClickHouse версии 24.3 анализатор включен по умолчанию.
Подробнее о принципах его работы можно прочитать здесь.
Известные несовместимости
Несмотря на исправление большого количества ошибок и внедрение новых оптимизаций, это также приводит к некоторым несовместимым изменениям в поведении ClickHouse. Ознакомьтесь со следующими изменениями, чтобы понять, как переписать ваши запросы для анализатора.
Некорректные запросы больше не оптимизируются
Прежняя инфраструктура планирования запросов применяла оптимизации на уровне AST до этапа проверки запроса. Оптимизации могли переписать исходный запрос так, чтобы он стал корректным и исполнимым.
В анализаторе проверка запроса выполняется до этапа оптимизации. Это означает, что некорректные запросы, которые раньше можно было выполнить, теперь не поддерживаются. В таких случаях запрос необходимо исправить вручную.
Пример 1
Следующий запрос использует столбец number в списке проекций, хотя после агрегации доступно только toString(number).
В старом анализаторе GROUP BY toString(number) оптимизировалось до GROUP BY number,, что делало запрос корректным.
SELECT number
FROM numbers(1)
GROUP BY toString(number)Пример 2
Та же проблема возникает и в этом запросе. Столбец number используется после агрегации с другим ключом.
Прежний анализатор запросов исправлял этот запрос, перемещая фильтр number > 5 из условия HAVING в условие WHERE.
SELECT
number % 2 AS n,
sum(number)
FROM numbers(10)
GROUP BY n
HAVING number > 5Чтобы исправить запрос, перенесите все условия, относящиеся к неагрегированным столбцам, в раздел WHERE, чтобы привести его в соответствие со стандартным синтаксисом SQL:
SELECT
number % 2 AS n,
sum(number)
FROM numbers(10)
WHERE number > 5
GROUP BY nДля упрощения миграции анализатор может воспроизводить прежнее преобразование HAVING в WHERE для неагрегатных AND-конъюнктов. Чтобы включить это поведение, задайте analyzer_compatibility_allow_non_aggregate_in_having = 1. Этот параметр доступен начиная с ClickHouse 26.7. Параметр игнорируется для WITH CUBE, WITH ROLLUP, WITH TOTALS и GROUPING SETS. Конъюнкты, содержащие агрегатные функции, grouping или недетерминированные функции, остаются в HAVING; если какой-либо конъюнкт содержит оконную функцию или функцию с сохранением состояния (например, rowNumberInBlock), преобразование отключается для всего HAVING, что соответствует прежнему поведению.
CREATE VIEW с некорректным запросом
Анализатор всегда выполняет проверку типов.
Ранее можно было создать VIEW с некорректным запросом SELECT.
В этом случае ошибка возникала при первом SELECT или INSERT (в случае MATERIALIZED VIEW).
Теперь создать VIEW таким способом нельзя.
Пример
CREATE TABLE source (data String)
ENGINE=MergeTree
ORDER BY tuple();
CREATE VIEW some_view
AS SELECT JSONExtract(data, 'test', 'DateTime64(3)')
FROM source;Известные несовместимости предложения JOIN
JOIN с использованием столбца из проекции
По умолчанию псевдоним из списка SELECT нельзя использовать в качестве ключа JOIN USING.
Новая настройка analyzer_compatibility_join_using_top_level_identifier, если она включена, меняет поведение JOIN USING: при разрешении идентификаторов предпочтение отдается выражениям из списка проекций запроса SELECT, а не столбцам из левой таблицы напрямую.
Например:
SELECT a + 1 AS b, t2.s
FROM VALUES('a UInt64, b UInt64', (1, 1)) AS t1
JOIN VALUES('b UInt64, s String', (1, 'one'), (2, 'two')) t2
USING (b);Если analyzer_compatibility_join_using_top_level_identifier установлено в true, условие JOIN интерпретируется как t1.a + 1 = t2.b, что соответствует поведению в более ранних версиях.
Результат будет 2, 'two'.
Если эта настройка имеет значение false, по умолчанию используется условие JOIN t1.b = t2.b, и запрос вернёт 2, 'one'.
Если b отсутствует в t1, запрос завершится ошибкой.
Изменения в поведении JOIN USING со столбцами ALIAS/MATERIALIZED
В анализаторе использование * в запросе JOIN USING со столбцами ALIAS или MATERIALIZED по умолчанию включает эти столбцы в результирующий набор.
Например:
CREATE TABLE t1 (id UInt64, payload ALIAS sipHash64(id)) ENGINE = MergeTree ORDER BY id;
INSERT INTO t1 VALUES (1), (2);
CREATE TABLE t2 (id UInt64, payload ALIAS sipHash64(id)) ENGINE = MergeTree ORDER BY id;
INSERT INTO t2 VALUES (2), (3);
SELECT * FROM t1
FULL JOIN t2 USING (payload);В анализаторе результат этого запроса будет включать столбец payload вместе с id из обеих таблиц.
В отличие от него, предыдущий анализатор включал эти столбцы ALIAS только при включении определённых настроек (asterisk_include_alias_columns или asterisk_include_materialized_columns),
при этом столбцы могли отображаться в другом порядке.
Чтобы результаты были предсказуемыми и согласованными, особенно при миграции старых запросов на анализатор, рекомендуется явно указывать столбцы в секции SELECT, а не использовать *.
Обработка модификаторов типов столбцов в предложении USING
В анализаторе правила определения общего супертипа для столбцов, указанных в предложении USING, были унифицированы, чтобы результаты стали более предсказуемыми,
особенно при работе с такими модификаторами типов, как LowCardinality и Nullable.
LowCardinality(T)иT: если столбец типаLowCardinality(T)участвует в JOIN со столбцом типаT, результирующим общим супертипом будетT, то есть модификаторLowCardinalityфактически отбрасывается.Nullable(T)иT: если столбец типаNullable(T)участвует в JOIN со столбцом типаT, результирующим общим супертипом будетNullable(T), что гарантирует сохранение свойства nullable.
Например:
SELECT id, toTypeName(id)
FROM VALUES('id LowCardinality(String)', ('a')) AS t1
FULL OUTER JOIN VALUES('id String', ('b')) AS t2
USING (id);В этом запросе общий супертип для id определяется как String, а модификатор LowCardinality из t1 отбрасывается.
Изменения в именах столбцов проекции
При вычислении имён проекции псевдонимы не подставляются.
SELECT
1 + 1 AS x,
x + 1
SETTINGS enable_analyzer = 0
FORMAT PrettyCompact
┌Несовместимые типы аргументов функции
В анализатор вывод типов происходит на этапе начального анализа запроса.
Это означает, что проверка типов выполняется до укороченного вычисления, поэтому аргументы функции if всегда должны иметь общий супертип.
Например, следующий запрос завершается ошибкой There is no supertype for types Array(UInt8), String because some of them are Array and some of them are not:
SELECT toTypeName(if(0, [2, 3, 4], 'String'))Неоднородные кластеры
Анализатор существенно меняет протокол обмена данными между серверами в кластере. Поэтому выполнять распределённые запросы на серверах с разными значениями настройки enable_analyzer невозможно.
Мутации обрабатываются предыдущим анализатором
Мутации по-прежнему используют старый анализатор.
Это означает, что некоторые новые возможности ClickHouse SQL нельзя использовать в мутациях. Например, оператор QUALIFY.
Текущий статус можно проверить здесь.
Неподдерживаемые возможности
Ниже приведен список возможностей, которые анализатор пока не поддерживает:
- Индекс Annoy.
- Индекс Hypothesis. Работа над ним ведется здесь.
- Оконное представление не поддерживается. Его поддержка в будущем не планируется.
Миграция в Cloud
Мы включаем анализатор на всех экземплярах, где он сейчас отключен, чтобы обеспечить новые функциональные возможности и оптимизации производительности. Это изменение ужесточает правила области видимости в SQL, поэтому клиентам потребуется вручную обновить запросы, которые им не соответствуют.
Процесс миграции
- Определите запрос, отфильтровав записи в
system.query_logпоnormalized_query_hash:
SELECT query
FROM clusterAllReplicas(default, system.query_log)
WHERE normalized_query_hash='{hash}'
LIMIT 1
SETTINGS skip_unavailable_shards=1- Выполните запрос при включенном анализаторе, добавив следующие настройки.
SETTINGS
enable_analyzer=1,
analyzer_compatibility_join_using_top_level_identifier=1- Доработайте запрос и проверьте, что его результаты совпадают с выводом при отключённом анализаторе.
Ознакомьтесь с наиболее частыми несовместимостями, обнаруженными в ходе внутреннего тестирования.
Неизвестный идентификатор выражения
Ошибка: Unknown expression identifier ... in scope ... (UNKNOWN_IDENTIFIER). Код исключения: 47
Причина: запросы, зависящие от нестандартного устаревшего поведения, допускающего неоднозначности, — например, обращения к вычисляемым псевдонимам в фильтрах, неоднозначных проекций подзапросов или «динамической» области видимости CTE, — теперь корректно определяются как недопустимые и сразу отклоняются.
Решение: обновите SQL-шаблоны следующим образом:
- Логика фильтрации: перенесите условие из WHERE в HAVING, если фильтрация идёт по результатам, или продублируйте выражение в WHERE, если фильтрация идёт по исходным данным.
- Область видимости подзапроса: явно выберите все столбцы, необходимые внешнему запросу.
- Ключи JOIN: используйте ON с полными выражениями вместо USING, если ключ — это псевдоним.
- Во внешних запросах обращайтесь к псевдониму самого подзапроса/CTE, а не к таблицам внутри него.
Неагрегированные столбцы в GROUP BY
Ошибка: Column ... is not under aggregate function and not in GROUP BY keys (NOT_AN_AGGREGATE). Код исключения: 215
Причина: Старый анализатор позволял выбирать столбцы, которых нет в GROUP BY (часто подставляя произвольное значение). Анализатор следует стандарту SQL: каждый выбранный столбец должен быть либо агрегатом, либо ключом группировки.
Решение: Оберните столбец в any(), argMax() или добавьте его в GROUP BY.
/* ИСХОДНЫЙ ЗАПРОС */
-- device_id неоднозначен
SELECT user_id, device_id FROM table GROUP BY user_id
/* ИСПРАВЛЕННЫЙ ЗАПРОС */
SELECT user_id, any(device_id) FROM table GROUP BY user_id
-- ИЛИ
SELECT user_id, device_id FROM table GROUP BY user_id, device_idНеагрегированные столбцы в HAVING
Ошибка: Column ... is not under aggregate function and not in GROUP BY keys (NOT_AN_AGGREGATE). Код исключения: 215
Причина: Старый анализатор без предупреждения переносил неагрегирующие AND-конъюнкты из HAVING в WHERE, рассматривая их как фильтры предварительной агрегации. Анализатор соответствует standard SQL: HAVING может ссылаться только на ключи агрегации и агрегатные функции.
Решение: Вручную перенесите предикат из HAVING в WHERE или включите analyzer_compatibility_allow_non_aggregate_in_having = 1 (доступно начиная с ClickHouse 26.7), чтобы вернуть прежнее преобразование как вспомогательное средство при миграции. Настройка совместимости не применяется к WITH CUBE, WITH ROLLUP, WITH TOTALS и GROUPING SETS. Конъюнкты, содержащие агрегатные, grouping или недетерминированные функции, остаются в HAVING; если хотя бы один конъюнкт содержит оконную функцию или функцию с сохранением состояния (например, rowNumberInBlock), преобразование отключается для всего HAVING, как и в прежнем поведении.
/* ORIGINAL QUERY */
SELECT category, sum(value) FROM t GROUP BY category HAVING service = 'svc1';
/* FIXED QUERY */
SELECT category, sum(value) FROM t WHERE service = 'svc1' GROUP BY category;Дублирующиеся имена CTE
Ошибка: CTE with name ... already exists (MULTIPLE_EXPRESSIONS_FOR_ALIAS). Код исключения: 179
Причина: Старый анализатор допускал определение нескольких общих табличных выражений (WITH …) с одним и тем же именем, когда более позднее выражение перекрывало предыдущее. Анализатор не допускает такой неоднозначности.
Решение: Переименуйте повторяющиеся CTE, чтобы их имена были уникальными.
/* ИСХОДНЫЙ ЗАПРОС */
WITH
data AS (SELECT 1 AS id),
data AS (SELECT 2 AS id) -- Переопределено
SELECT * FROM data;
/* ИСПРАВЛЕННЫЙ ЗАПРОС */
WITH
raw_data AS (SELECT 1 AS id),
processed_data AS (SELECT 2 AS id)
SELECT * FROM processed_data;Неоднозначные идентификаторы столбцов
Ошибка: JOIN [JOIN TYPE] ambiguous identifier ... (AMBIGUOUS_IDENTIFIER) Код исключения: 207
Причина: В запросе используется имя столбца, которое присутствует в нескольких таблицах в JOIN, без указания исходной таблицы. Старый анализатор часто определял нужный столбец на основе внутренней логики, тогда как анализатор требует явного указания имени.
Решение: Полностью указывайте столбец в виде table_alias.column_name.
/* ИСХОДНЫЙ ЗАПРОС */
SELECT table1.ID AS ID FROM table1, table2 WHERE ID...
/* ИСПРАВЛЕННЫЙ ЗАПРОС */
SELECT table1.ID AS ID_RENAMED FROM table1, table2 WHERE ID_RENAMED...Недопустимое использование FINAL
Ошибка: Table expression modifiers FINAL are not supported for subquery... или Storage ... doesn't support FINAL (UNSUPPORTED_METHOD). Коды исключений: 1, 181
Причина: FINAL — это модификатор хранения таблицы (в частности, для [Shared]ReplacingMergeTree). Анализатор отклоняет FINAL, если он применяется к:
- Подзапросам или производным таблицам (например, FROM (SELECT …) FINAL).
- Движкам таблиц, которые его не поддерживают (например, SharedMergeTree).
Решение: Применяйте FINAL только к исходной таблице внутри подзапроса или уберите его, если движок его не поддерживает.
/* ИСХОДНЫЙ ЗАПРОС */
SELECT * FROM (SELECT * FROM my_table) AS subquery FINAL ...
/* ИСПРАВЛЕННЫЙ ЗАПРОС */
SELECT * FROM (SELECT * FROM my_table FINAL) AS subquery ...Чувствительность функции countDistinct() к регистру
Ошибка: Function with name countdistinct does not exist (UNKNOWN_FUNCTION). Код исключения: 46
Причина: имена функций чувствительны к регистру, либо в анализаторе для них используется строгое сопоставление. countdistinct (полностью в нижнем регистре) больше не распознаётся автоматически.
Решение: используйте стандартную countDistinct (camelCase) или специфичную для ClickHouse функцию uniq.