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

# Kusto Query Language (KQL) в ClickHouse

> Что поддерживает экспериментальный диалект KQL и что он намеренно не поддерживает

ClickHouse может разбирать подмножество запросов на [Kusto Query Language](https://learn.microsoft.com/en-us/kusto/query/)
вместо SQL. Этот диалект **экспериментальный** и по умолчанию отключён:

```sql theme={null}
SET allow_experimental_kusto_dialect = 1;
SET dialect = 'kusto';

StormEvents
| where State == 'FLORIDA' and DamageProperty > 0
| summarize Total = sum(DamageProperty) by EventType
| top 5 by Total
```

`SET dialect = 'clickhouse'` переключает обратно. `SET` — единственный SQL-оператор, распознаваемый при активном диалекте KQL, поэтому из него всегда можно выйти в рамках сеанса.

<h2 id="what-is-supported">
  Что поддерживается
</h2>

Это намеренно ограниченное подмножество. Конструкция KQL либо преобразуется с семантикой,
задокументированной Kusto, либо отклоняется с ошибкой разбора — никакие приближения не выполняются молча.
Если запрос успешно разбирается, его результат должен соответствовать результату в Kusto.

**Источники**: имя таблицы, `print`, `datatable`, `range`, конвейер в скобках и `union`.
`range` может задаваться от числа с числовым шагом, от даты и времени с шагом в виде временного интервала или от временного интервала с шагом в виде
временного интервала.

**Операторы**: `where` / `filter`, `extend`, `project`, `project-away`, `project-keep`,
`project-rename`, `summarize`, `sort by` / `order by`, `take` / `limit`, `top`, `distinct`,
`count`, `mv-expand`, `join`, `union`, `as`, `render`.

**Скалярные операторы**: `==`, `!=`, `<`, `<=`, `>`, `>=`, `=~`, `!~`, `in`, `in~`, `between`,
`contains`, `startswith`, `endswith`, `has`, `hasprefix`, `hassuffix`, их варианты с `_cs`
(с учётом регистра) и `!` (отрицанием), `has_any`, `has_all` и `matches regex`.
`in` и `!in` также принимают табличное выражение, значения берутся из первого столбца
(`x in (T | project key)`); `in~` принимает только список. Одиночное имя внутри `in (...)`, не
связанное через `let`, интерпретируется как столбец, поскольку у анализатора нет схемы, позволяющей отличить столбец от таблицы;
свяжите таблицу с помощью `let`, укажите её полное имя (`db.table`) или добавьте конвейер, чтобы получить табличную форму.

**Команды**: `let`, связывающий скалярное значение, целое табличное выражение или **функцию**:

```sql theme={null}
let MultiplyByN = (val: long, n: long = 2) { val * n };
let RecentErrors = (since: timespan) { Logs | where Level == 'Error' and Timestamp > ago(since) };

RecentErrors(1h) | summarize Count = count() by Component
```

Функции принимают скалярные параметры с необязательными литеральными значениями по умолчанию, а также табличные параметры,
объявленные как `T: (*)` или `T: (col: type, ...)`; табличные параметры должны идти первыми. Табличный параметр с
явно указанными столбцами предоставляет телу функции только эти столбцы аргумента, поэтому тело, обращающееся к
необъявленному столбцу, отклоняется, даже если этот столбец есть в конкретном аргументе; `T: (*)`
передаёт аргумент без изменений. Объявленные типы проверяются на границе вызова:
аргумент (или объявленный столбец табличного аргумента), тип которого не относится к
объявленному типу KQL, отклоняется; применяется преобразование без потери данных, например из `long` в `real`, а
значение, не помещающееся в объявленный тип — например, при переполнении `int`, — вызывает ошибку,
а не молча усекается. Аргументы можно передавать по
имени в любом порядке (`f(c = 7, a = 12)`). Тело состоит из произвольного числа операторов `let`, за которыми следует
одно выражение, и имеет доступ к охватывающим его привязкам. Функция, тело которой представляет собой
конвейер, возвращает таблицу, а не значение, поэтому она отклоняется там, где ожидается выражение —
в `extend`, `where` или `print`. Функцию без параметров можно
вызывать как со скобками, так и без них. `view ()` допускается и, поскольку здесь не разрешаются
подстановочные шаблоны `union *`, означает то же самое, что и `()`. Рекурсия отклоняется, как и в Kusto.

`let` создаёт привязку только для следующего за ним оператора, поскольку один оператор KQL — это один
запрос к ClickHouse; это также предотвращает утечку привязки в параллельный запрос. Имя, необходимое в двух операторах,
нужно привязать дважды.

**Литералы**: строки (включая дословные `@'...'`), числа, `datetime(...)`, `guid(...)`,
интервалы времени, например `1d` / `2.5h` / `500ms`, и массивы `dynamic([...])`.

Поддерживается около 130 скалярных и агрегатных функций. Как и в Kusto, агрегатные
функции можно вызывать только в списке агрегации `summarize`; `print count()` отклоняется,
а не передаётся одноимённой агрегатной функции ClickHouse.

**Функции ClickHouse** также доступны. Имя, неизвестное реестру KQL, передаётся
в ClickHouse в написанном вами виде, поэтому в запросе можно использовать всё, что предоставляет сервер:

```sql theme={null}
SET dialect = 'kusto';

StormEvents
| extend Bucket = toStartOfHour(StartTime), Fingerprint = cityHash64(EventType)
| summarize Events = count() by Bucket
```

Исключение — имена, определённые в самом Kusto, но не реализованные в этом диалекте: они
отклоняются, а не передаются дальше, поэтому имя Kusto не может незаметно приобрести другое значение.
Наиболее наглядный пример — `range`: `range(1, 3, 1)` в Kusto возвращает `[1, 2, 3]`, а в
ClickHouse — `[1, 2]`, поэтому его использование в KQL приводит к ошибке, а не к неверному результату.

<h2 id="coverage-against-the-kusto-reference">
  Покрытие относительно справочника Kusto
</h2>

Согласно собственным справочникам Microsoft
([табличные операторы](https://learn.microsoft.com/en-us/kusto/query/queries),
[скалярные функции](https://learn.microsoft.com/en-us/kusto/query/scalar-functions),
[агрегатные функции](https://learn.microsoft.com/en-us/kusto/query/aggregation-functions)):

| | В документации Kusto | поддерживается здесь |
| - | - | - |
| Табличные операторы | 52 | 20 |
| Скалярные и агрегатные функции | 307 | 162 |
| Пользовательские функции | да | да |

Поддерживаются операторы из руководства Microsoft *Learn common operators*,
а также `datatable`, `range`, `print`, `union` и `join` — этого достаточно для типов запросов, рассматриваемых в этом руководстве и KQL Quick Reference.

<h2 id="what-is-not-supported">
  Что не поддерживается
</h2>

Вместо неточного перевода отклоняется с ошибкой разбора:

* Операторы: `search`, `parse`, `mv-apply`, `lookup`, `evaluate`, `invoke`, `facet`,
  `top-nested`, `make-series`, `sample`, `serialize`, `partition`, `range` в качестве оператора.
* Функции: семейство `series_*`, `bag_*` / `pack_*`, `parse_url`, `parse_csv`,
  `parse_json`, `todynamic`, `toscalar`, `format_timespan`, `format_datetime`, `extract_all`,
  `range`, семейство `percentiles*` и оконные функции `row_*`.
  (`format_datetime` и `extract_all` отклоняются, а не аппроксимируются: спецификаторы формата
  `yyyy-MM-dd` в Kusto отличаются от используемых в ClickHouse, а `extract_all` в Kusto возвращает по одному
  массиву на каждую группу захвата.)
* Имена Kusto, совпадающие с функциями ClickHouse, но имеющие другое значение: `range` (указана
  выше), `repeat`, `replace`, `translate` и `materialize`. Их прямое преобразование незаметно
  дало бы другой результат: `repeat(1, 3)` в Kusto создаёт массив `[1, 1, 1]`, тогда как
  `repeat` в ClickHouse повторяет строку, поэтому все эти имена отклоняются.
* Геопространственные функции, принимающие или возвращающие GeoJSON: все `geo_*_to_central_point` и
  всё, что работает с полигонами и линиями. Функции для точек, Geohash и H3, работающие с
  обычными значениями долготы и широты, *поддерживаются*. `geo_point_to_s2cell` не поддерживается:
  в ClickHouse нет представления S2 в виде токена.
* **Объекты** `dynamic` (`dynamic({"a": 1})`), доступ к членам (`x.y`) и поиск по ключу
  (`x['k']`). Сопоставляются только массивы `dynamic` — с `Array` в ClickHouse. `dynamic` как
  **объявленный тип** — в схеме `datatable`, `typeof(...)` или параметре функции —
  также отклоняется: аннотация не содержит тип элемента, поэтому его невозможно сопоставить без потери
  точности.
* Межкластерные и межбазовые ссылки, такие как `cluster(...)` и `database(...)`.
* Подсказки для запросов и `join` (`hint.strategy`, `hint.shufflekey`, ...).
* Параметры операторов: `mv-expand ... to typeof(T)` / `limit N` / `bagexpansion`, подсказки
  `summarize`, `union kind=` / `withsource=` / `isfuzzy=`, `join hint.*`.
* Шаблоны столбцов с подстановочными знаками в `project-away` и `project-keep` (`project-away Tmp*`):
  для раскрытия такого шаблона нужна схема, которая недоступна при разборе. Перечислите столбцы явно.
* Механизм плагинов `evaluate` целиком, а вместе с ним `bag_unpack`, `pivot`, `narrow`,
  `python`, `R` и остальные.
* Операторы приложения: `alias database`, `declare pattern`,
  `declare query_parameters`, `restrict access to`.
* Обфусцированные строковые литералы (`h"..."`) и многострочные литералы (тройные обратные апострофы).

<h2 id="behaviour-worth-knowing">
  Важные особенности поведения
</h2>

* **Временные интервалы — это значения `Interval`.** `1d` преобразуется в `toIntervalNanosecond(86400000000000)`.
  Чтобы отображать их в формате Kusto (`1.00:00:00`), а не в виде числа, задайте
  `interval_output_format = 'kusto'`.
* **Деление выполняется как в Kusto**: `7 / 2` равно `3`, поскольку оба операнда — целые числа, а
  результат деления временного интервала на временной интервал — вещественное отношение (`15ms / 10ms` равно `1.5`). Это
  реализовано в `kqlDivide`, которая определяет поведение по типам аргументов.
* **Вычитание двух значений даты и времени даёт число секунд**, тогда как Kusto возвращает временной интервал.
  Сложение и вычитание временных интервалов работают ожидаемым образом.
* **`sort` по умолчанию сортирует по убыванию**, в отличие от SQL, и помещает null-значения в начало.
* **`project-rename` перемещает переименованный столбец в конец** строки. Kusto сохраняет его
  исходную позицию; для воспроизведения этого потребовалось бы знать схему при разборе.
* **`union` требует, чтобы операнды имели совместимые схемы.** Kusto расширяет схему до объединения
  всех столбцов и дополняет отсутствующие значения null; `UNION ALL` в ClickHouse этого не делает.
* **Строковые операторы — это функции сопоставления, а не шаблоны `LIKE`.** `contains '50%'` ищет
  буквальный знак процента.
* **`geo_*` принимает долготу перед широтой**, как и Kusto. `geo_distance_2points` использует
  `greatCircleDistance` ClickHouse — быстрое приближение, которое отличается от Kusto в
  четвёртой значащей цифре — примерно на 600 м при расстоянии 1500 км, — а `use_spheroid = true` выбирает
  `geoDistance`, формулу эллипсоида, как в Kusto. Точное совпадение не является целью:
  эти функции обычно используются для фильтрации, а не для отчётности. Обратите внимание, что координата вне
  диапазона \[-180, 180] или \[-90, 90] даёт бессмысленное число, а не null, который возвращает Kusto:
  ни одна из функций ClickHouse не проверяет диапазон аргументов, а такая проверка потребовала бы восьми
  сравнений на строку.
* **`dayofweek()` возвращает временной интервал**, а не число: понедельник — это `1.00:00:00`.
* **`tohex()` выводит отрицательное значение с разрядностью 64 бита.** Kusto выводит его с разрядностью,
  соответствующей типу самого аргумента, который не виден при разборе.
* **Значения даты и времени — это настоящие значения `DateTime64`**, поэтому они выводятся в формате ClickHouse
  (`2017-01-01 00:00:00`), а не в формате Kusto (`2017-01-01T00:00:00.0000000`). Предыдущая
  реализация создавала форматированную *строку*, которая выглядела как в Kusto, но не сравнивалась
  и не сортировалась как дата и время.
* **Параметрические агрегатные функции ClickHouse** (`quantileExact(0.5)(x)`) не имеют аналога в KQL.
  Используйте именованную альтернативу, например `medianExact(x)`.

<h2 id="reporting-a-problem">
  Сообщение о проблеме
</h2>

Если запрос разбирается, но возвращает результат, который Kusto не вернул бы, это ошибка — сообщите о ней, приложив
оба результата. Если нужный вам запрос отклоняется, это запрос на новую возможность; приведённые
выше списки лишь очерчивают границу, а не перечисляют все отклоняемые имена — окончательный ответ для конкретного запроса даёт сама ошибка разбора, и ничто из этого не является окончательным.
