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_datetime과extract_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배열만 ClickHouseArray에 매핑돼요. 선언된 타입으로서의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"...")과 여러 줄 리터럴(삼중 백틱).
알아두면 좋은 동작 (Behaviour worth knowing)
- Timespan은
Interval값이에요.1d는toIntervalNanosecond(86400000000000)이 돼요.interval_output_format = 'kusto'를 설정하면 숫자 대신 Kusto 방식(1.00:00:00)으로 렌더링해요. - 나눗셈은 Kusto를 따릅니다:
7 / 2는3이에요. 두 피연산자가 모두 정수이기 때문이고, timespan을 timespan으로 나누면 실수 비율(15ms / 10ms는1.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가 반환하지 않을 결과를 반환하는 쿼리는 버그예요 — 두 결과와 함께 신고해주세요. 거부되지만 여러분에게 필요한 쿼리는 기능 요청이에요; 위 목록은 경계를 개략적으로 보여주는 것이지 거부되는 모든 이름을 나열한 게 아니에요 — 특정 쿼리에 대한 권위 있는 답은 파싱 오류 자체예요 — 그리고 그 어느 것도 영구적이지 않아요.