ClickHouse에서 Kusto Query Language

ClickHouse에서 Kusto Query Language (KQL)

ClickHouse는 SQL 대신 Kusto Query Language의 부분집합을 파싱할 수 있어요. 이 방언은 실험적이며 기본적으로 꺼져 있어요:

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'로 다시 되돌아갈 수 있어요. KQL 방언이 활성화된 상태에서 SET은 유일하게 인식되는 SQL 문이므로, 어떤 세션이든 항상 빠져나올 수 있어요.

출처: 문서

본문

지원되는 것 (What is supported)

의도적으로 작은 부분집합이에요. KQL 구성은 Kusto가 문서화한 의미로 번역되거나, 이름으로 거부되어 파싱 오류를 내요 — 조용히 근사하지는 않아요. 쿼리가 파싱되면 그 결과는 Kusto의 결과와 일치하도록 설계돼요. 소스(Source): 테이블 이름, print, datatable, range, 괄호로 묶인 파이프라인, union. range는 숫자에서 숫자로, datetime에서 timespan으로, timespan에서 timespan으로 진행돼요. 연산자(Operator): 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. 스칼라 연산자(Scalar operator): ==, !=, <, <=, >, >=, =~, !~, 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)하거나, 파이프를 추가해 표 형식 형태로 만들어주세요. 문(Statement): let은 스칼라, 전체 표 형식 표현식, 또는 함수를 바인딩해요:

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 쿼리이기 때문에 그 뒤에 오는 하나의 문에만 바인딩돼요 — 이것이 동시 쿼리에 바인딩이 누출되는 것을 막는 이유이기도 해요. 두 문이 모두 필요한 이름은 두 번 바인딩해야 해요. 리터럴(Literal): 문자열(verbatim @'...' 포함), 숫자, datetime(...), guid(...), 1d / 2.5h / 500ms 같은 timespan, dynamic([...]) 배열. 약 130개의 스칼라 및 집계 함수가 번역돼요. Kusto에서처럼 집계 함수는 summarize의 집계 목록에서만 호출할 수 있어요; print count()는 같은 이름의 ClickHouse 집계로 전달되는 대신 거부돼요. ClickHouse 함수도 접근할 수 있어요. KQL 레지스트리가 모르는 이름은 쓴 철자 그대로 ClickHouse에 전달되므로, 쿼리는 서버가 제공하는 무엇이든 사용할 수 있어요:

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로 작성하면 잘못된 답이 아니라 오류가 돼요.

Kusto 참조 대비 적용 범위 (Coverage against the Kusto reference)

Microsoft 자체 인덱스(표 형식 연산자, 스칼라 함수, 집계 함수) 기준으로 측정한 결과예요:

Kusto 문서 여기서 지원
표 형식 연산자 (Tabular operators) 52
스칼라·집계 함수 (Scalar and aggregate functions) 307
사용자 정의 함수 (User-defined functions) yes

지원되는 연산자는 Microsoft가 자체 Learn common operators 튜토리얼에서 가르치는 것들에 datatable, range, print, union, join을 더한 것이에요 — 그 튜토리얼과 KQL Quick Reference가 만드는 쿼리 형태를 처리하기에 충분해요.

지원되지 않는 것 (What is not supported)

잘못 번역되는 대신 파싱 오류로 거부돼요:

  • 연산자: 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_datetimeextract_all은 근사하는 대신 거부돼요: Kusto의 yyyy-MM-dd 형식 지정자는 ClickHouse의 것과 다르고, Kusto의 extract_all은 캡처 그룹당 하나의 배열을 반환해요.)
  • 다른 의미를 가진 ClickHouse 함수와 충돌하는 Kusto 이름: range(위에 표시), repeat, replace, translate, materialize. 전달하면 조용히 다른 것을 계산하게 돼요 — Kusto의 repeat(1, 3)는 배열 [1, 1, 1]인데 ClickHouse의 repeat는 문자열을 반복해요 — 그래서 각각 이름으로 거부돼요.
  • GeoJSON을 받거나 반환하는 지리공간 함수 — 모든 geo_*_to_central_point와 폴리곤·라인을 다루는 모든 것. 일반 경도/위도로 동작하는 point, geohash, H3 함수는 지원 돼요. geo_point_to_s2cell은 지원되지 않아요: ClickHouse에는 S2 토큰 형태가 없어요.
  • dynamic 객체(dynamic({"a": 1})), 멤버 접근(x.y), 키 조회(x['k']). dynamic 배열만 ClickHouse Array에 매핑돼요. 선언된 타입으로서의 dynamicdatatable 스키마, typeof(...), 함수 매개변수 — 도 거부돼요: 해당 주석에 요소 타입이 없어서, 신뢰할 수 있게 매핑할 것이 없어요.
  • cluster(...)database(...) 같은 클러스터 간·데이터베이스 간 참조.
  • 쿼리 및 join 힌트(hint.strategy, hint.shufflekey, …).
  • 연산자 옵션: mv-expand ... to typeof(T) / limit N / bagexpansion, summarize 힌트, union kind= / withsource= / isfuzzy=, join hint.*.
  • project-awayproject-keep의 와일드카드 컬럼 패턴(project-away Tmp*): 이를 확장하려면 스키마가 필요한데, 파싱 중에는 보이지 않아요. 컬럼을 직접 나열해주세요.
  • evaluate 플러그인 메커니즘 전체 — 그리고 그와 함께 bag_unpack, pivot, narrow, python, R 등.
  • 애플리케이션 문: alias database, declare pattern, declare query_parameters, restrict access to.
  • 난독화된 문자열 리터럴(h"...")과 여러 줄 리터럴(삼중 백틱).

알아두면 좋은 동작 (Behaviour worth knowing)

  • Timespan은 Interval 값이에요. 1dtoIntervalNanosecond(86400000000000)이 돼요. interval_output_format = 'kusto'를 설정하면 숫자 대신 Kusto 방식(1.00:00:00)으로 렌더링해요.
  • 나눗셈은 Kusto를 따릅니다: 7 / 23이에요. 두 피연산자가 모두 정수이기 때문이고, timespan을 timespan으로 나누면 실수 비율(15ms / 10ms1.5)이에요. 이는 인자 타입으로 결정하는 kqlDivide로 구현돼요.
  • 두 datetime을 빼면 초 단위의 숫자가 나와요, Kusto에서는 timespan을 주는데요. timespan을 더하거나 빼는 것은 기대대로 동작해요.
  • sort는 SQL과 달리 기본적으로 내림차순이고, null을 작은 쪽 끝에 놓아요.
  • project-rename은 이름 바뀐 컬럼을 행의 끝으로 옮겨요. Kusto는 원래 위치를 유지하는데, 이를 재현하려면 파싱 중에 스키마를 알아야 해요.
  • union은 피연산자들이 호환 가능한 스키마를 요구해요. Kusto는 모든 컬럼의 합집합으로 넓히고 null로 패딩하는데, ClickHouse의 UNION ALL은 그렇지 않아요.
  • 문자열 연산자는 매칭 함수이지 LIKE 패턴이 아니에요. contains '50%'는 리터럴 퍼센트 기호를 찾아요.
  • geo_*는 Kusto처럼 경도가 위도보다 먼저 와요. geo_distance_2points는 ClickHouse의 greatCircleDistance를 사용하는데, 이는 빠른 근사치로 네 번째 유효 숫자에서 Kusto와 다릅니다 — 1500 km에서 약 600 m 정도 — 그리고 use_spheroid = true는 Kusto처럼 타원체 공식인 geoDistance를 선택해요. 정확한 일치는 목표가 아니에요: 이 함수들은 보통 필터링에 쓰이지 보고용이 아니에요. [-180, 180] 또는 [-90, 90] 범위 밖의 좌표는 Kusto가 반환하는 null 대신 의미 없는 숫자를 낸다는 점에 주의하세요: ClickHouse 함수 어느 쪽도 인자를 범위 검사하지 않고, 검사하면 행당 8번의 비교가 추가돼요.
  • dayofweek()는 숫자가 아니라 timespan을 반환해요: 월요일은 1.00:00:00이에요.
  • tohex()는 음수 값을 64비트 폭으로 렌더링해요. Kusto는 인자 자신의 타입 폭으로 렌더링하는데, 이는 파싱 중에 보이지 않아요.
  • Datetime은 실제 DateTime64 값이에요, 그래서 Kusto의 2017-01-01T00:00:00.0000000 대신 ClickHouse 방식(2017-01-01 00:00:00)으로 출력돼요. 이전 구현은 형식화된 문자열 을 생성했는데, Kusto처럼 보였지만 datetime으로 비교·정렬되지는 않았어요.
  • ClickHouse의 매개변수형 집계(quantileExact(0.5)(x))에는 KQL 철자가 없어요. medianExact(x) 같은 이름 있는 대안을 사용하세요.

문제 보고하기 (Reporting a problem)

파싱되지만 Kusto가 반환하지 않을 결과를 반환하는 쿼리는 버그예요 — 두 결과와 함께 신고해주세요. 거부되지만 여러분에게 필요한 쿼리는 기능 요청이에요; 위 목록은 경계를 개략적으로 보여주는 것이지 거부되는 모든 이름을 나열한 게 아니에요 — 특정 쿼리에 대한 권위 있는 답은 파싱 오류 자체예요 — 그리고 그 어느 것도 영구적이지 않아요.

더 알아보기 (Learn more)