> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-parallel-read-in-order-multi-part.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Страница с описанием анализатора запросов ClickHouse

# Анализатор

В ClickHouse версии `24.3` анализатор включен по умолчанию.
Подробнее о принципах его работы можно прочитать [здесь](/ru/guides/clickhouse/performance-and-monitoring/understanding-query-execution-with-the-analyzer#analyzer).

Начиная с версии `26.9` анализатор обязателен: настройка `enable_analyzer` устарела, попытка задать ей значение `0` отклоняется, а анализ запросов, который ClickHouse использовал до версии `24.3`, больше не поддерживается. Перечисленные ниже несовместимости описывают, чем отличался прежний анализ, чтобы можно было обновить написанный под него запрос; чтобы увидеть его поведение, выполните запрос на версии ClickHouse старше `26.9`.

<h2 id="known-incompatibilities">
  Известные несовместимости
</h2>

Несмотря на исправление большого количества ошибок и внедрение новых оптимизаций, это также приводит к некоторым несовместимым изменениям в поведении ClickHouse. Ознакомьтесь со следующими изменениями, чтобы понять, как переписать ваши запросы для анализатора.

<h3 id="invalid-queries-are-no-longer-optimized">
  Некорректные запросы больше не оптимизируются
</h3>

Прежняя инфраструктура планирования запросов применяла оптимизации на уровне AST до этапа проверки запроса.
Оптимизации могли переписать исходный запрос так, чтобы он стал корректным и исполнимым.

В анализаторе проверка запроса выполняется до этапа оптимизации.
Это означает, что некорректные запросы, которые раньше можно было выполнить, теперь не поддерживаются.
В таких случаях запрос необходимо исправить вручную.

<h4 id="example-1">
  Пример 1
</h4>

Следующий запрос использует столбец `number` в списке проекций, хотя после агрегации доступно только `toString(number)`.
В старом анализаторе `GROUP BY toString(number)` оптимизировалось до `GROUP BY number,`, что делало запрос корректным.

```sql theme={null}
SELECT number
FROM numbers(1)
GROUP BY toString(number)
```

<h4 id="example-2">
  Пример 2
</h4>

Та же проблема возникает и в этом запросе. Столбец `number` используется после агрегации с другим ключом.
Прежний анализатор запросов исправлял этот запрос, перемещая фильтр `number > 5` из условия `HAVING` в условие `WHERE`.

```sql theme={null}
SELECT
    number % 2 AS n,
    sum(number)
FROM numbers(10)
GROUP BY n
HAVING number > 5
```

Чтобы исправить запрос, перенесите все условия, относящиеся к неагрегированным столбцам, в раздел `WHERE`, чтобы привести его в соответствие со стандартным синтаксисом SQL:

```sql theme={null}
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`, что соответствует прежнему поведению.

<h3 id="create-view-with-invalid-query">
  `CREATE VIEW` с некорректным запросом
</h3>

Анализатор всегда выполняет проверку типов.
Ранее можно было создать `VIEW` с некорректным запросом `SELECT`.
В этом случае ошибка возникала при первом `SELECT` или `INSERT` (в случае `MATERIALIZED VIEW`).

Теперь создать `VIEW` таким способом нельзя.

<h4 id="example-view">
  Пример
</h4>

```sql theme={null}
CREATE TABLE source (data String)
ENGINE=MergeTree
ORDER BY tuple();

CREATE VIEW some_view
AS SELECT JSONExtract(data, 'test', 'DateTime64(3)')
FROM source;
```

<h3 id="known-incompatibilities-of-the-join-clause">
  Известные несовместимости предложения `JOIN`
</h3>

<h4 id="join-using-column-from-projection">
  `JOIN` с использованием столбца из проекции
</h4>

По умолчанию псевдоним из списка `SELECT` нельзя использовать в качестве ключа `JOIN USING`.

Новая настройка `analyzer_compatibility_join_using_top_level_identifier`, если она включена, меняет поведение `JOIN USING`: при разрешении идентификаторов предпочтение отдается выражениям из списка проекций запроса `SELECT`, а не столбцам из левой таблицы напрямую.

Например:

```sql theme={null}
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`, запрос завершится ошибкой.

<h4 id="changes-in-behavior-with-join-using-and-aliasmaterialized-columns">
  Изменения в поведении `JOIN USING` со столбцами `ALIAS`/`MATERIALIZED`
</h4>

В анализаторе использование `*` в запросе `JOIN USING` со столбцами `ALIAS` или `MATERIALIZED` по умолчанию включает эти столбцы в результирующий набор.

Например:

```sql theme={null}
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`, а не использовать `*`.

<h4 id="handling-of-type-modifiers-for-columns-in-using-clause">
  Обработка модификаторов типов столбцов в предложении `USING`
</h4>

В анализаторе правила определения общего супертипа для столбцов, указанных в предложении `USING`, были унифицированы, чтобы результаты стали более предсказуемыми,
особенно при работе с такими модификаторами типов, как `LowCardinality` и `Nullable`.

* `LowCardinality(T)` и `T`: если столбец типа `LowCardinality(T)` участвует в JOIN со столбцом типа `T`, результирующим общим супертипом будет `T`, то есть модификатор `LowCardinality` фактически отбрасывается.
* `Nullable(T)` и `T`: если столбец типа `Nullable(T)` участвует в JOIN со столбцом типа `T`, результирующим общим супертипом будет `Nullable(T)`, что гарантирует сохранение свойства nullable.

Например:

```sql theme={null}
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` отбрасывается.

<h3 id="projection-column-names-changes">
  Изменения в именах столбцов проекции
</h3>

При вычислении имён проекции псевдонимы не подставляются.

```sql theme={null}
SELECT
    1 + 1 AS x,
    x + 1
FORMAT PrettyCompact
```

До версии `24.3` второй столбец получал имя по подставленному псевдониму:

```text theme={null}
   ┌─x─┬─plus(plus(1, 1), 1)─┐
1. │ 2 │                   3 │
   └───┴─────────────────────┘
```

Анализатор сохраняет псевдоним в имени:

```text theme={null}
   ┌─x─┬─plus(x, 1)─┐
1. │ 2 │          3 │
   └───┴────────────┘
```

<h3 id="incompatible-function-arguments-types">
  Несовместимые типы аргументов функции
</h3>

В анализатор вывод типов происходит на этапе начального анализа запроса.
Это означает, что проверка типов выполняется до укороченного вычисления, поэтому аргументы функции `if` всегда должны иметь общий супертип.

Например, следующий запрос завершается ошибкой `There is no supertype for types Array(UInt8), String because some of them are Array and some of them are not`:

```sql theme={null}
SELECT toTypeName(if(0, [2, 3, 4], 'String'))
```

<h3 id="heterogeneous-clusters">
  Неоднородные кластеры
</h3>

Анализатор существенно меняет протокол обмена данными между серверами в кластере. Поэтому невозможно выполнять распределённые запросы на серверах, которые расходятся в том, используется ли анализатор, — а для кластера серверов версий старше `26.9` это означает серверы с разными значениями настройки `enable_analyzer`.

В сервере версии `26.10` и новее другого механизма анализа запросов не осталось, поэтому он игнорирует значение, присылаемое более старым initiator, и в любом случае анализирует запрос с помощью анализатора. Два варианта анализа именуют результирующие столбцы по-разному, а initiator сопоставляет блок, возвращаемый сегментом, по имени столбца, поэтому такой запрос может завершиться на initiator ошибкой `NOT_FOUND_COLUMN_IN_BLOCK` — например, когда в нём выбирается функция, записанная в неканоническом регистре (`hostname()`), которую анализатор приводит к каноническому имени (`hostName()`). Поэтому в кластере, всё ещё работающем со старым механизмом анализа запросов, необходимо установить `enable_analyzer = 1` на каждом сервере до того, как любой из них будет обновлён до `26.10`.

<h3 id="unsupported-features">
  Неподдерживаемые возможности
</h3>

Ниже приведен список возможностей, которые анализатор пока не поддерживает:

* Индекс Annoy.
* Индекс Hypothesis. Работа над ним ведется [здесь](https://github.com/ClickHouse/ClickHouse/pull/48381).

<h2 id="cloud-migration">
  Миграция в Cloud
</h2>

Мы включаем анализатор на всех экземплярах, где он сейчас отключен, чтобы обеспечить новые функциональные возможности и оптимизации производительности. Это изменение ужесточает правила области видимости в SQL, поэтому клиентам потребуется вручную обновить запросы, которые им не соответствуют.

<h3 id="migration-workflow">
  Процесс миграции
</h3>

1. Определите запрос, отфильтровав записи в `system.query_log` по `normalized_query_hash`:

```sql theme={null}
SELECT query 
FROM clusterAllReplicas(default, system.query_log)
WHERE normalized_query_hash='{hash}' 
LIMIT 1 
SETTINGS skip_unavailable_shards=1
```

2. Выполните запрос с анализатором, добавив настройку совместимости (compatibility setting), которая восстанавливает разрешение идентификаторов прежнего анализа, если запрос на это опирается.

```sql theme={null}
SETTINGS
    analyzer_compatibility_join_using_top_level_identifier=1
```

3. Доработайте запрос и проверьте, что его результаты совпадают с выводом, который запрос давал до миграции.

Ознакомьтесь с наиболее частыми несовместимостями, обнаруженными в ходе внутреннего тестирования.

<h3 id="unknown-expression-identifier">
  Неизвестный идентификатор выражения
</h3>

Ошибка: `Unknown expression identifier ... in scope ... (UNKNOWN_IDENTIFIER)`. Код исключения: 47

Причина: запросы, зависящие от нестандартного устаревшего поведения, допускающего неоднозначности, — например, обращения к вычисляемым псевдонимам в фильтрах, неоднозначных проекций подзапросов или «динамической» области видимости CTE, — теперь корректно определяются как недопустимые и сразу отклоняются.

Решение: обновите SQL-шаблоны следующим образом:

* Логика фильтрации: перенесите условие из WHERE в HAVING, если фильтрация идёт по результатам, или продублируйте выражение в WHERE, если фильтрация идёт по исходным данным.
* Область видимости подзапроса: явно выберите все столбцы, необходимые внешнему запросу.
* Ключи JOIN: используйте ON с полными выражениями вместо USING, если ключ — это псевдоним.
* Во внешних запросах обращайтесь к псевдониму самого подзапроса/CTE, а не к таблицам внутри него.

<h3 id="non-aggregated-columns-in-group-by">
  Неагрегированные столбцы в GROUP BY
</h3>

Ошибка: `Column ... is not under aggregate function and not in GROUP BY keys (NOT_AN_AGGREGATE)`. Код исключения: 215

Причина: Старый анализатор позволял выбирать столбцы, которых нет в GROUP BY (часто подставляя произвольное значение). Анализатор следует стандарту SQL: каждый выбранный столбец должен быть либо агрегатом, либо ключом группировки.

Решение: Оберните столбец в `any()`, `argMax()` или добавьте его в GROUP BY.

```sql theme={null}
/* ИСХОДНЫЙ ЗАПРОС */
-- 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
```

<h3 id="non-aggregated-columns-in-having">
  Неагрегированные столбцы в HAVING
</h3>

Ошибка: `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`, как и в прежнем поведении.

```sql theme={null}
/* 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;
```

<h3 id="duplicate-cte-names">
  Повторяющиеся имена CTE
</h3>

Ошибка: `CTE with name ... already exists (MULTIPLE_EXPRESSIONS_FOR_ALIAS)`. Код исключения: 179

Причина: старый анализатор позволял определять несколько общих табличных выражений (WITH ...) с одинаковым именем, при этом более позднее определение перекрывало предыдущее. Новый анализатор по умолчанию не допускает такой неоднозначности.

Решение: переименуйте повторяющиеся CTE так, чтобы их имена стали уникальными. В качестве временной меры на период миграции можно включить `analyzer_compatibility_allow_cte_redefinition = 1` (доступно начиная с ClickHouse `26.10`) и тем самым восстановить прежнее поведение: ссылка привязывается к последнему определению имени, которое не разрешается в данный момент. Благодаря этому переопределение может обращаться к предыдущему определению, а тело запроса — к последнему.

Ограничения: CTE, объявленное как `MATERIALIZED`, и CTE в предложении `WITH RECURSIVE` нельзя переопределить даже при включенной настройке. Кроме того, одна конструкция запроса обрабатывается иначе, чем в старом анализаторе: CTE, объявленное между двумя определениями одного и того же имени, тоже привязывается к последнему определению, тогда как старый анализатор привязывал его к определению, видимому в месте его объявления.

```sql theme={null}
/* ORIGINAL QUERY */
WITH
  data AS (SELECT 1 AS id),
  data AS (SELECT id + 1 AS id FROM data) -- Redefined, reads the previous definition
SELECT * FROM data;

/* FIXED QUERY */
WITH
  raw_data AS (SELECT 1 AS id),
  processed_data AS (SELECT id + 1 AS id FROM raw_data)
SELECT * FROM processed_data;

/* LEGACY BEHAVIOR AS A MIGRATION AID */
WITH
  data AS (SELECT 1 AS id),
  data AS (SELECT id + 1 AS id FROM data)
SELECT * FROM data
SETTINGS analyzer_compatibility_allow_cte_redefinition = 1;
```

<h3 id="ambiguous-column-identifiers">
  Неоднозначные идентификаторы столбцов
</h3>

Ошибка: `JOIN [JOIN TYPE] ambiguous identifier ... (AMBIGUOUS_IDENTIFIER)` Код исключения: 207

Причина: В запросе используется имя столбца, которое присутствует в нескольких таблицах в JOIN, без указания исходной таблицы. Старый анализатор часто определял нужный столбец на основе внутренней логики, тогда как анализатор требует явного указания имени.

Решение: Полностью указывайте столбец в виде table\_alias.column\_name.

```sql theme={null}
/* ИСХОДНЫЙ ЗАПРОС */
SELECT table1.ID AS ID FROM table1, table2 WHERE ID...

/* ИСПРАВЛЕННЫЙ ЗАПРОС */
SELECT table1.ID AS ID_RENAMED FROM table1, table2 WHERE ID_RENAMED...
```

<h3 id="invalid-usage-of-final">
  Недопустимое использование FINAL
</h3>

Ошибка: `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 только к исходной таблице внутри подзапроса или уберите его, если движок его не поддерживает.

```sql theme={null}
/* ИСХОДНЫЙ ЗАПРОС */
SELECT * FROM (SELECT * FROM my_table) AS subquery FINAL ...

/* ИСПРАВЛЕННЫЙ ЗАПРОС */
SELECT * FROM (SELECT * FROM my_table FINAL) AS subquery ...
```

<h3 id="countdistinct-case-insensitivity">
  Чувствительность функции `countDistinct()` к регистру
</h3>

Ошибка: `Function with name countdistinct does not exist (UNKNOWN_FUNCTION)`. Код исключения: 46

Причина: имена функций чувствительны к регистру, либо в анализаторе для них используется строгое сопоставление. `countdistinct` (полностью в нижнем регистре) больше не распознаётся автоматически.

Решение: используйте стандартную `countDistinct` (camelCase) или специфичную для ClickHouse функцию uniq.
