kql
microsoft/skills
構文、結合、動的型、日付・時刻の注意点、正規表現、シリアライズ、メモリ管理、および高度な関数について網羅し、正確かつ効率的なKusto Query Languageクエリを作成します。
...すべて拡張しますKQL 習得
実際に試してみましょう:このスキルのすべての
✅このスキルのすべての例は、パブリックヘルプクラスターで実行できます:https://help.kusto.windows.net、データベースSamples(StormEvents,SimpleGraph_Nodes/Edges,nyc_taxi、その他)。
1. KQL の基礎
Kusto クエリ言語(KQL)は、データを探索するためのパイプフォワード型クエリ言語です。これは、Azure Data Explorer (ADX)、Microsoft Fabric Real-Time Intelligence (EventHouse)、Azure Monitor Log Analytics、Microsoft Sentinel、およびその他の Microsoft データ サービス向けのネイティブ クエリ言語です。
パイプフォワード構文
KQL クエリは、 |で区切られた演算子の連鎖です。データは左から右へと流れます:
StormEvents // start with a table
| where State == "TEXAS" // filter rows
| summarize count() by EventType // aggregate
| top 5 by count_ desc // limit results
クエリと管理コマンド
KQL には 2 つの実行プレーンがあります:
| プレーン | 以下から開始されます | 例 |
|---|---|---|
| クエリ | テーブル名、 let, print, datatable |
StormEvents | where State == "TEXAS" |
| 管理 | .show, .create, .set, .drop, .alter |
.show tables, .show table T schema |
管理コマンドの後にクエリ演算子を続けることは可能ですが(出力は表形式になります)、リクエスト全体は管理プレーン上で実行されます。クエリから始めて、その出力を管理コマンドにパイプで渡すことはできません。
// ✅ WORKS — management command piped to query operators
.show tables | project TableName | where TableName has "Events"
// ❌ WRONG — query piped into management command
StormEvents | take 5 | .show tables
判断に迷った場合は、最初のトークンが .で始まる場合、それは管理コマンドです。スキーマ探索コマンドの完全な一覧については、 references/discovery-queries.md.
2. 動的型制約
KQLの dynamic の型は柔軟ですが、特定のコンテキストでは厳格です。よくある間違いとして、動的カラムを summarize by, order byや join on で動的型付けの列をキャストせずに使用してしまうことです。
ルール:動的型付けの列を by, on、または order byで動的型付けの列を使用する際は、常に明示的なキャストで囲んでください。
// ❌ ERROR: "Summarize group key ... is of a 'dynamic' type"
StormEvents | summarize count() by StormSummary.Details.Location
// ✅ FIX
StormEvents | summarize count() by tostring(StormSummary.Details.Location)
// ❌ ERROR: "order operator: key can't be of dynamic type"
StormEvents | order by StormSummary.TotalDamages desc
// ✅ FIX
StormEvents | order by tolong(StormSummary.TotalDamages) desc
// ❌ ERROR in join: dynamic join key
StormEvents | join kind=inner (PopulationData) on $left.StormSummary == $right.State
// ✅ FIX — cast both sides
StormEvents
| extend State_str = tostring(StormSummary.Details.Location)
| join kind=inner (PopulationData) on $left.State_str == $right.State
自己修正:エラーメッセージに「'動的' 型です」と表示された場合は、 tostring(), tolong()、または todouble().
3. 結合のパターンと落とし穴
KQL JOINには、SQLとは異なる制約があります。
等価条件のみ
KQL 結合条件では、==のみがサポートされます。結合条件内での <, >, !=、または関数呼び出しは使用できません。
// ❌ ERROR: "Only equality is allowed in this context"
StormEvents | join (nyc_taxi) on geo_distance_2points(BeginLon, BeginLat, pickup_longitude, pickup_latitude) < 1000
// ✅ WORKAROUND — pre-bucket into spatial cells, then join on cell ID
StormEvents
| extend cell = geo_point_to_s2cell(BeginLon, BeginLat, 8)
| join kind=inner (nyc_taxi | extend cell = geo_point_to_s2cell(pickup_longitude, pickup_latitude, 8)) on cell
範囲結合の場合、値を事前にビン分けします: | extend bin_val = bin(Value, 100)、その上で bin_valで結合します。注:ビン境界付近の値は隣接するビンに分類される場合があります。精度を確保するため、隣接するビンを確認するか、範囲を重複させることを検討してください。
左/右属性のマッチング
ジョインの on 句の両側では、式や集計関数ではなく、列エンティティのみを参照する必要があります。
// ❌ ERROR: "for each left attribute, right attribute should be selected"
StormEvents | join kind=inner (PopulationData) on $left.State
// ✅ FIX — specify both sides explicitly
StormEvents | join kind=inner (PopulationData) on $left.State == $right.State
大規模な結合前のカーディナリティチェック
行数が 1 万行を超えるテーブルを結合する前に、必ずカーディナリティを確認してください。クロス結合の爆発が、この単一のエラーの原因でした E_RUNAWAY_QUERY エラーの原因でした(25K × 195 = 最大4.8M行)。
// Before joining, check how many rows each side contributes
StormEvents | summarize dcount(State) // → 67 distinct states
PopulationData | summarize dcount(State) // → 52 — safe to join
4.KQLにおける正規表現
KQL は正規表現をネイティブに処理します。Pythonを使用する必要はありません。
この extract_all 注意点
Pythonの re.findall()とは異なり、KQLの extract_all では、正規表現内にキャプチャグループを記述する必要があります:
// ❌ ERROR: "extractall(): argument 2 must be a valid regex with [1..16] matching groups"
StormEvents | extend words = extract_all(@"[a-zA-Z]{3,}", EventNarrative)
// ✅ FIX — add parentheses around the pattern
StormEvents | extend words = extract_all(@"([a-zA-Z]{3,})", EventNarrative)
正規表現ツールキット — Pythonに依存しないように
| 関数 | ユースケース | 例 |
|---|---|---|
extract(regex, group, source) |
単一の一致 | extract(@"User '([^']+)'", 1, Msg) |
extract_all(regex, source) |
すべての一致(以下が必要 ()) |
extract_all(@"(\w+)", Text) |
parse |
構造化抽出 | parse Msg with * "User '" Sender "' sent" * |
matches regex |
ブールフィルタ | where Url matches regex @"^https?://" |
replace_regex |
検索と置換 | replace_regex(Text, @"\s+", " ") |
5. シリアライズ要件
ウィンドウ関数には、シリアル化(順序付け)された入力が必要です。
// ❌ ERROR: "Function 'row_cumsum' cannot be invoked. The row set must be serialized."
StormEvents
| where State == "TEXAS"
| summarize DailyCount = count() by bin(StartTime, 1d)
| extend CumulativeCount = row_cumsum(DailyCount)
// ✅ FIX — add | serialize (or | order by, which implicitly serializes)
StormEvents
| where State == "TEXAS"
| summarize DailyCount = count() by bin(StartTime, 1d)
| order by StartTime asc
| extend CumulativeCount = row_cumsum(DailyCount)
シリアライズが必要な関数: row_number(), row_cumsum(), prev(), next(), row_window_session().
6. メモリセーフなクエリパターン
最も一般的なメモリエラーです。事前フィルタリングを行わずに大量のデータをスキャンすることで発生します。
安全性の段階
Safest ──────────────────────────────────────────────── Most dangerous
| count | take 10 | where + summarize | summarize (no filter) | full scan
大規模テーブル(100万行以上)に関するルール
- テーブルのサイズを把握するため、常に `
| count` から始める | summarizeを実行する前に必ず| whereを実行する — まず時間範囲、パーティションキー、またはカテゴリでフィルタリングする- 事前フィルタリングを行わずに、高カーディナリティの列に対して
dcount()を実行してはならない - 実行前に結合のカーディナリティを確認する(第3節を参照)
- 複数回参照されるサブクエリには
materialize()を使用する
// ❌ OUT OF MEMORY — large table, no filter, many group-by columns
StormEvents
| summarize dcount(EventType), count() by StartTime, State, Source
| where dcount_EventType > 1
// ✅ SAFE — filter first, then aggregate
StormEvents
| where StartTime between (datetime(2007-04-15) .. datetime(2007-04-16))
| summarize dcount(EventType) by State, Source
| where dcount_EventType > 1
次のようなメッセージが表示された場合 E_LOW_MEMORY_CONDITION
「クエリが処理するデータ量が多すぎます。」というメッセージが表示された場合の対処法:
- フィルター
| whereフィルタ(時間範囲、パーティションキー)を追加する - 対象となる
by列の数をsummarize - より小さな時間ウィンドウに分割し、結果を結合する
- 探索的な作業には
| sample 10000フルスキャンではなく、探索的な作業に
次のような場合 E_RUNAWAY_QUERY
結合または集計により出力行数が多すぎた。結合のカーディナリティを確認してください — 片方、あるいは両方の側の行数が多すぎます。
7. 結果サイズの管理
結果のサイズが大きいと分析が遅くなります。予防策:
| クエリの種類 | 予防策 |
|---|---|
| 探索的 | 常に | take 10 または | take 20 |
| 集計 | 使用 | top 20 by ... 無制限ではない summarize |
| ワイド行(ベクトル、JSON) | | project 必要な列のみ |
make_list() / make_set() |
高カーディナリティのグループでは避ける(巨大なセルが生成されるため) |
| サイズ不明 | 実行 | count 最初に |
ベクトルの落とし穴:埋め込み列(1536次元の浮動小数点数配列)を含むテーブルは、1行あたり約30KBのサイズになります。たとえ | take 20 600KBにもなります。特に必要でない限り、 | project ベクトル列は、特に必要でない限り常に除外してください。
8. 文字列比較の厳密さ
KQL 計算された文字列値を比較する際、たとえ両側がすでに文字列であっても、明示的な型変換が必要になる場合があります。
// ❌ ERROR: "Cannot compare values of types string and string. Try adding explicit casts"
StormEvents | where geo_point_to_s2cell(BeginLon, BeginLat, 16) == other_cell
// ✅ FIX — wrap both sides in tostring()
StormEvents | where tostring(geo_point_to_s2cell(BeginLon, BeginLat, 16)) == tostring(other_cell)
これは、 geo_point_to_s2cell() および strcat() による比較から得られる計算済み値で最もよく見られます。迷った場合は、 tostring().
9. 高度な関数
KQL はこれらをネイティブに処理します。Pythonは必要ありません:
ベクトルの類似度
// try it! — cosine similarity on Iris feature vectors
let target = pack_array(5.1, 3.5, 1.4, 0.2);
Iris
| extend Vec = pack_array(SepalLength, SepalWidth, PetalLength, PetalWidth)
| extend sim = series_cosine_similarity(Vec, target)
| top 5 by sim desc
地理演算
// Distance between two points (meters)
StormEvents | extend dist = geo_distance_2points(BeginLon, BeginLat, EndLon, EndLat)
// Spatial bucketing for joins
StormEvents | extend cell = geo_point_to_s2cell(BeginLon, BeginLat, 8)
グラフクエリ
// Persistent graph model — try it on the help cluster!
graph("Simple")
| graph-match (src)-[e*1..3]->(dst)
where src.name == "Alice"
project src.name, dst.name, path_length = array_length(e)
// Transient graph — build inline with make-graph
SimpleGraph_Edges
| make-graph source --> target with SimpleGraph_Nodes on id
| graph-match (src)-[e*1..5]->(dst)
where src.name == "Alice"
project src.name, dst.name, path_length = array_length(e)
時系列
// try it! — create a time series and detect anomalies
StormEvents
| make-series count() default=0 on StartTime step 1d
| extend anomalies = series_decompose_anomalies(count_)
詳細な例やパターンについては、以下を参照してください references/advanced-patterns.md.
10. 自己修正ルックアップテーブル
エラーが発生した場合は、再試行する前にここで確認してください:
| エラーメッセージに以下が含まれる場合 | 考えられる原因 | 修正方法 |
|---|---|---|
is of a 'dynamic' type |
動的列が by/on/order by |
で囲む tostring()/tolong() |
Only equality is allowed |
結合条件内の範囲述語 | S2/H3セルを含むプリバケット、または bin() |
extractall(): matching groups |
欠落 () 正規表現内 |
追加 (): @"(\w+)" not @"\w+" |
row set must be serialized |
ソートされていないデータに対するウィンドウ関数 | 追加 | serialize または | order by その前に |
Cannot compare values of types string and string |
計算された文字列の比較 | その前に tostring() 両側に |
Failed to resolve column named 'X' |
列名またはテーブル名が間違っています | 実行して .show table T schema を実行して列名を確認 |
E_LOW_MEMORY_CONDITION |
クエリの対象データ量が多すぎる | フィルターを追加 | where フィルターを追加するか、期間を短縮するか、段階的に分割してください |
E_RUNAWAY_QUERY |
結合や集計により行数が多くなりすぎた | 結合前にカーディナリティを確認する;事前フィルタを追加する |
for each left attribute, right attribute |
結合 on 句が不完全 |
明示的な形式を使用してください: on $left.X == $right.Y |
needs to be bracketed |
識別子として予約語が使用されています | 次の ['keyword'] 構文 |
plugin doesn't exist |
このクラスターでは利用できないプラグイン | 同等の関数またはPythonにフォールバックする |
Expected string literal in datetime() |
datetime リテラル内の素の整数 | 使用 datetime(2024-01-01) not datetime(2024) |
Unexpected token の後に by |
summarizeのby節における複雑な式 | extend まず式を評価し、その後 summarize by 列 |
not recognized / unknown operator |
このエンジンではこの演算子は利用できません | 演算子のサポートを確認してください。同等の演算子(order by = sort by) |
11. 日時に関する落とし穴
日付・時刻のリテラルは、エラーのよくある原因です。リテラルの形式が間違っていると、些細な問題を修正するどころか、まったく異なるアプローチへと波及してしまう可能性があります。
リテラル形式
// ❌ WRONG — bare year is not a valid datetime
StormEvents | where StartTime > datetime(2007)
// ✅ RIGHT — always use full date format
StormEvents | where StartTime > datetime(2007-01-01)
年、月、または時間によるフィルタリング
// ❌ WRONG — comparing datetime column to integer
StormEvents | where StartTime == 2007
// ✅ RIGHT — use datetime_part() to extract components
StormEvents | where datetime_part("year", StartTime) == 2007
// ✅ ALSO RIGHT — use between with datetime range
StormEvents | where StartTime between (datetime(2007-01-01) .. datetime(2007-12-31T23:59:59))
SUMMARIZE での時間単位のバケット化
// This works, but can be harder to read and reuse in complex queries
StormEvents | summarize count() by startofmonth(StartTime)
// Clearer — extend first, then summarize by the computed column
StormEvents
| extend Month = startofmonth(StartTime)
| summarize count() by Month
| order by Month asc
便利な日時関数
| 関数 | 目的 | 例 |
|---|---|---|
bin(ts, 1h) |
バケットの境界値に切り下げる | bin(Timestamp, 1d) |
startofmonth(ts) |
月の初日 | startofmonth(Timestamp) |
datetime_part("hour", ts) |
構成要素の抽出 | datetime_part("year", Timestamp) |
format_datetime(ts, fmt) |
文字列としてフォーマット | format_datetime(Timestamp, "yyyy-MM") |
ago(1d) |
相対時間 | where Timestamp > ago(1d) |
between(a .. b) |
範囲フィルター(両端を含む) | where Timestamp between (datetime(2024-01-01) .. datetime(2024-01-31T23:59:59)) |
todatetime(str) |
文字列を解析 → 日時 | todatetime("2024-01-15T10:30:00Z") |
totimespan(str) |
文字列を解析 → 期間 | totimespan("01:30:00") |
12. 演算子の命名と等価性
KQL には、SQLの構文とは微妙な違いがあります。
命名規則
| エンティティ | 規則 | 例 |
|---|---|---|
| テーブル | UpperCamelCase | StormEvents, NetworkLogs |
| 列 | UpperCamelCase | StartTime, EventType |
変数 (let) |
スネークケース | let filtered_events = ... |
| 組み込み関数 | snake_case | format_bytes(), geo_distance_2points() |
| 保存関数 | UpperCamelCase | .create function GetTopUsers |
等価演算子
// In where clauses, == is case-sensitive, =~ is case-insensitive
StormEvents | where State == "TEXAS" | count // exact match
StormEvents | where State =~ "texas" | count // case-insensitive
// In joins, use == only
StormEvents | join kind=inner (PopulationData) on State
sort 対 order
どちらも sort by と order by はKQLにおいて同じように動作します — これらは別名です。どちらを使っても構いませんが、一貫性を保ってください。
contains 対 has
// contains: substring match (slower)
StormEvents | where EventNarrative contains "tree" // finds "trees", "treetop" too
// has: term/word match (faster, uses index)
StormEvents | where EventNarrative has "tree" // matches word boundaries only
// For exact prefix/suffix
StormEvents | where EventType startswith "Thunder"
StormEvents | where Source endswith "Spotter"
13. エラー回復戦略
KQLでの最初のクエリが失敗した際、そのアプローチ全体を放棄して、まったく異なる方法を試したくなるものです。しかし、正しい対応は、戦略を変えるのではなく、その特定のエラーを修正することです。
避けるべきパターン
Query 1: extract(@"pattern", 1, col) → Parse error
Query 2: todynamic(col) → Different error
Query 3: parse_json(col) → Another error
Query 4: Python script → Works but 10x tokens
避けるべきパターン
Query 1: extract(@"pattern", 1, col) → Parse error (bad escaping)
Query 2: extract(@"pattern", 1, col) → Fix the specific escaping issue → Success
エラー回復のルール:
- エラーメッセージを注意深く読みましょう。ほとんどの場合、何が問題なのかが正確に示されています
- 具体的な構文やエスケープの問題を修正し、アプローチを切り替えない
- 「自己修正表」(第10節)を使用して、エラーと修正方法を照合してください
- 同じクエリに対して2回修正に失敗した場合にのみ、アプローチを切り替えてください
- The
parse演算子は、多くの場合、extract()構造化されたテキストの場合、次のようにシンプルになることがよくあります:
// Instead of complex regex on TraceLogs:
// extract(@"file path: \"\"([^\"]+)\"\"", 1, Message)
// Use parse for structured extraction (try it on help cluster, SampleLogs db):
cluster("help").database("SampleLogs").TraceLogs
| where Message has "file path"
| parse Message with * "file path: \"\"" FilePath "\"\"" *
| project Timestamp, FilePath
| take 5
14. クエリ作成チェックリスト
KQLクエリを実行する前に、頭の中で以下を確認してください:
- 事前フィルタリングは済んでいますか? 大規模なテーブルには
| where実行前に| summarize - 結果に上限はありますか?探索的なクエリは
| take Nで終わります。動的列のキャストは行われていますか? 動的列がある場合は、| top N - 動的列の型変換は行われましたか?
by/on/order byはラップされる - 正規表現にグループがあるか?
extract_allパターンには()キャプチャしたい箇所の周囲に - 結合のカーディナリティは安全ですか?両側とも
dcount()結合前に - 必要なカラムのみですか? データ量が多いテーブルでは
| project不要な列を削除する - 日付・時刻のリテラルは有効か?
datetime(2024-01-01)リテラルではなくdatetime(2024)または単純な整数を使用 - 複雑な副式? まず
| extendを先に指定し、その後| summarize by計算カラムを使用する - エラー回復プラン? クエリが失敗した場合は、その特定のエラーを修正し、戦略を変更しないでください
---
name: kql
description: Write correct, efficient Kusto Query Language queries with coverage of syntax, joins, dynamic types, datetime pitfalls, regex, serialization, memory management, and advanced functions.
---
# KQL Mastery
> **Try it yourself**: All `✅` examples in this skill can be run against the public help cluster:
> `https://help.kusto.windows.net`, database `Samples` (contains `StormEvents`, `SimpleGraph_Nodes`/`Edges`, `nyc_taxi`, and more).
## 1. KQL Basics
Kusto Query Language (KQL) is a pipe-forward query language for exploring data. It is the native query language for Azure Data Explorer (ADX), Microsoft Fabric Real-Time Intelligence (EventHouse), Azure Monitor Log Analytics, Microsoft Sentinel, and other Microsoft data services.
### Pipe-forward syntax
KQL queries are a chain of operators separated by `|`. Data flows left to right:
```kql
StormEvents // start with a table
| where State == "TEXAS" // filter rows
| summarize count() by EventType // aggregate
| top 5 by count_ desc // limit results
```
### Query vs management commands
KQL has two execution planes:
| Plane | Starts with | Examples |
|-------|-------------|----------|
| **Query** | Table name, `let`, `print`, `datatable` | `StormEvents \| where State == "TEXAS"` |
| **Management** | `.show`, `.create`, `.set`, `.drop`, `.alter` | `.show tables`, `.show table T schema` |
Management commands can be followed by query operators (the output is tabular), but the entire request runs on the management plane. You cannot start with a query and pipe into a management command.
```kql
// ✅ WORKS — management command piped to query operators
.show tables | project TableName | where TableName has "Events"
// ❌ WRONG — query piped into management command
StormEvents | take 5 | .show tables
```
When in doubt: if the first token starts with `.`, it's a management command. For a full catalog of schema exploration commands, see `references/discovery-queries.md`.
## 2. Dynamic Type Discipline
KQL's `dynamic` type is flexible but strict in certain contexts. A common mistake is using a dynamic column in `summarize by`, `order by`, or `join on` without casting.
**The rule**: Any time you use a dynamic-typed column in `by`, `on`, or `order by`, wrap it in an explicit cast.
```kql
// ❌ ERROR: "Summarize group key ... is of a 'dynamic' type"
StormEvents | summarize count() by StormSummary.Details.Location
// ✅ FIX
StormEvents | summarize count() by tostring(StormSummary.Details.Location)
```
```kql
// ❌ ERROR: "order operator: key can't be of dynamic type"
StormEvents | order by StormSummary.TotalDamages desc
// ✅ FIX
StormEvents | order by tolong(StormSummary.TotalDamages) desc
```
```kql
// ❌ ERROR in join: dynamic join key
StormEvents | join kind=inner (PopulationData) on $left.StormSummary == $right.State
// ✅ FIX — cast both sides
StormEvents
| extend State_str = tostring(StormSummary.Details.Location)
| join kind=inner (PopulationData) on $left.State_str == $right.State
```
**Self-correction**: When you see "is of a 'dynamic' type" in an error, add `tostring()`, `tolong()`, or `todouble()`.
## 3. Join Patterns & Pitfalls
KQL joins have constraints that differ from SQL.
### Equality only
KQL join conditions support **only `==`**. No `<`, `>`, `!=`, or function calls in join predicates.
```kql
// ❌ ERROR: "Only equality is allowed in this context"
StormEvents | join (nyc_taxi) on geo_distance_2points(BeginLon, BeginLat, pickup_longitude, pickup_latitude) < 1000
// ✅ WORKAROUND — pre-bucket into spatial cells, then join on cell ID
StormEvents
| extend cell = geo_point_to_s2cell(BeginLon, BeginLat, 8)
| join kind=inner (nyc_taxi | extend cell = geo_point_to_s2cell(pickup_longitude, pickup_latitude, 8)) on cell
```
For range joins, pre-bin values: `| extend bin_val = bin(Value, 100)`, then join on `bin_val`. Note: values near bin boundaries may land in adjacent bins — consider checking neighboring bins or overlapping the range for precision.
### Left/right attribute matching
Both sides of a join `on` clause must reference **column entities only** — not expressions, not aggregates.
```kql
// ❌ ERROR: "for each left attribute, right attribute should be selected"
StormEvents | join kind=inner (PopulationData) on $left.State
// ✅ FIX — specify both sides explicitly
StormEvents | join kind=inner (PopulationData) on $left.State == $right.State
```
### Cardinality check before large joins
**Always** check cardinality before joining tables with >10K rows. A cross-join explosion was the source of the single `E_RUNAWAY_QUERY` error (25K × 195 = potential 4.8M rows).
```kql
// Before joining, check how many rows each side contributes
StormEvents | summarize dcount(State) // → 67 distinct states
PopulationData | summarize dcount(State) // → 52 — safe to join
```
## 4. Regex in KQL
KQL handles regex natively — no need for Python.
### The `extract_all` gotcha
Unlike Python's `re.findall()`, KQL's `extract_all` **requires capturing groups** in the regex:
```kql
// ❌ ERROR: "extractall(): argument 2 must be a valid regex with [1..16] matching groups"
StormEvents | extend words = extract_all(@"[a-zA-Z]{3,}", EventNarrative)
// ✅ FIX — add parentheses around the pattern
StormEvents | extend words = extract_all(@"([a-zA-Z]{3,})", EventNarrative)
```
### Regex toolkit — don't fall back to Python
| Function | Use case | Example |
|----------|----------|---------|
| `extract(regex, group, source)` | Single match | `extract(@"User '([^']+)'", 1, Msg)` |
| `extract_all(regex, source)` | All matches (needs `()`) | `extract_all(@"(\w+)", Text)` |
| `parse` | Structured extraction | `parse Msg with * "User '" Sender "' sent" *` |
| `matches regex` | Boolean filter | `where Url matches regex @"^https?://"` |
| `replace_regex` | Find and replace | `replace_regex(Text, @"\s+", " ")` |
## 5. Serialization Requirements
Window functions need serialized (ordered) input.
```kql
// ❌ ERROR: "Function 'row_cumsum' cannot be invoked. The row set must be serialized."
StormEvents
| where State == "TEXAS"
| summarize DailyCount = count() by bin(StartTime, 1d)
| extend CumulativeCount = row_cumsum(DailyCount)
// ✅ FIX — add | serialize (or | order by, which implicitly serializes)
StormEvents
| where State == "TEXAS"
| summarize DailyCount = count() by bin(StartTime, 1d)
| order by StartTime asc
| extend CumulativeCount = row_cumsum(DailyCount)
```
Functions requiring serialization: `row_number()`, `row_cumsum()`, `prev()`, `next()`, `row_window_session()`.
## 6. Memory-Safe Query Patterns
The most common memory error. Caused by scanning too much data without pre-filtering.
### The progression of safety
```
Safest ──────────────────────────────────────────────── Most dangerous
| count | take 10 | where + summarize | summarize (no filter) | full scan
```
### Rules for large tables (>1M rows)
1. **Always start with `| count`** to understand table size
2. **Always `| where` before `| summarize`** — filter time range, partition key, or category first
3. **Never `dcount()` on high-cardinality columns** without pre-filtering
4. **Check join cardinality** before executing (see Section 3)
5. **Use `materialize()`** for subqueries referenced multiple times
```kql
// ❌ OUT OF MEMORY — large table, no filter, many group-by columns
StormEvents
| summarize dcount(EventType), count() by StartTime, State, Source
| where dcount_EventType > 1
// ✅ SAFE — filter first, then aggregate
StormEvents
| where StartTime between (datetime(2007-04-15) .. datetime(2007-04-16))
| summarize dcount(EventType) by State, Source
| where dcount_EventType > 1
```
### When you see `E_LOW_MEMORY_CONDITION`
The query touched too much data. Your options:
- Add `| where` filters (time range, partition key)
- Reduce the number of `by` columns in `summarize`
- Break into smaller time windows and union results
- Use `| sample 10000` for exploratory work instead of full scans
### When you see `E_RUNAWAY_QUERY`
A join or aggregation produced too many output rows. Check join cardinality — one or both sides is too large.
## 7. Result Size Discipline
Large results slow down analysis. Prevention:
| Query type | Safeguard |
|-----------|-----------|
| Exploratory | Always end with `\| take 10` or `\| take 20` |
| Aggregation | Use `\| top 20 by ...` not unbounded `summarize` |
| Wide rows (vectors, JSON) | `\| project` only needed columns |
| `make_list()` / `make_set()` | Avoid on high-cardinality groups (produces huge cells) |
| Unknown size | Run `\| count` first |
**The vector trap**: Tables with embedding columns (1536-dim float arrays) produce ~30KB per row. Even `| take 20` yields 600KB. Always `| project` away vector columns unless you specifically need them.
## 8. String Comparison Strictness
KQL sometimes requires explicit casts when comparing computed string values — even when both sides are already strings.
```kql
// ❌ ERROR: "Cannot compare values of types string and string. Try adding explicit casts"
StormEvents | where geo_point_to_s2cell(BeginLon, BeginLat, 16) == other_cell
// ✅ FIX — wrap both sides in tostring()
StormEvents | where tostring(geo_point_to_s2cell(BeginLon, BeginLat, 16)) == tostring(other_cell)
```
This is most common with computed values from `geo_point_to_s2cell()` and `strcat()` comparisons. When in doubt, cast with `tostring()`.
## 9. Advanced Functions
KQL handles these natively — no need for Python:
### Vector similarity
```kql
// try it! — cosine similarity on Iris feature vectors
let target = pack_array(5.1, 3.5, 1.4, 0.2);
Iris
| extend Vec = pack_array(SepalLength, SepalWidth, PetalLength, PetalWidth)
| extend sim = series_cosine_similarity(Vec, target)
| top 5 by sim desc
```
### Geo operations
```kql
// Distance between two points (meters)
StormEvents | extend dist = geo_distance_2points(BeginLon, BeginLat, EndLon, EndLat)
// Spatial bucketing for joins
StormEvents | extend cell = geo_point_to_s2cell(BeginLon, BeginLat, 8)
```
### Graph queries
```kql
// Persistent graph model — try it on the help cluster!
graph("Simple")
| graph-match (src)-[e*1..3]->(dst)
where src.name == "Alice"
project src.name, dst.name, path_length = array_length(e)
// Transient graph — build inline with make-graph
SimpleGraph_Edges
| make-graph source --> target with SimpleGraph_Nodes on id
| graph-match (src)-[e*1..5]->(dst)
where src.name == "Alice"
project src.name, dst.name, path_length = array_length(e)
```
### Time series
```kql
// try it! — create a time series and detect anomalies
StormEvents
| make-series count() default=0 on StartTime step 1d
| extend anomalies = series_decompose_anomalies(count_)
```
For detailed examples and patterns, consult `references/advanced-patterns.md`.
## 10. Self-Correction Lookup Table
When you encounter an error, look it up here before retrying:
| Error message contains | Likely cause | Fix |
|---|---|---|
| `is of a 'dynamic' type` | Dynamic column in `by`/`on`/`order by` | Wrap in `tostring()`/`tolong()` |
| `Only equality is allowed` | Range predicate in join condition | Pre-bucket with S2/H3 cells or `bin()` |
| `extractall(): matching groups` | Missing `()` in regex | Add `()`: `@"(\w+)"` not `@"\w+"` |
| `row set must be serialized` | Window function on unsorted data | Add `\| serialize` or `\| order by` before it |
| `Cannot compare values of types string and string` | Computed string comparison | Add `tostring()` on both sides |
| `Failed to resolve column named 'X'` | Wrong column name or wrong table | Run `.show table T schema` to check column names |
| `E_LOW_MEMORY_CONDITION` | Query touched too much data | Add `\| where` filters, reduce time range, break into steps |
| `E_RUNAWAY_QUERY` | Join/aggregation produced too many rows | Check cardinality before joining; add pre-filters |
| `for each left attribute, right attribute` | Join `on` clause incomplete | Use explicit form: `on $left.X == $right.Y` |
| `needs to be bracketed` | Reserved word used as identifier | Use `['keyword']` syntax |
| `plugin doesn't exist` | Unavailable plugin on this cluster | Fall back to equivalent function or Python |
| `Expected string literal in datetime()` | Bare integer in datetime literal | Use `datetime(2024-01-01)` not `datetime(2024)` |
| `Unexpected token` after `by` | Complex expression in summarize by-clause | `extend` the expression first, then `summarize by` the column |
| `not recognized` / `unknown operator` | Operator not available on this engine | Check operator support; try equivalent (`order by` = `sort by`) |
## 11. Datetime Pitfalls
Datetime literals are a common source of errors. A wrong literal format can cascade into completely different approaches instead of fixing the small issue.
### Literal format
```kql
// ❌ WRONG — bare year is not a valid datetime
StormEvents | where StartTime > datetime(2007)
// ✅ RIGHT — always use full date format
StormEvents | where StartTime > datetime(2007-01-01)
```
### Filtering by year, month, or hour
```kql
// ❌ WRONG — comparing datetime column to integer
StormEvents | where StartTime == 2007
// ✅ RIGHT — use datetime_part() to extract components
StormEvents | where datetime_part("year", StartTime) == 2007
// ✅ ALSO RIGHT — use between with datetime range
StormEvents | where StartTime between (datetime(2007-01-01) .. datetime(2007-12-31T23:59:59))
```
### Time bucketing in summarize
```kql
// This works, but can be harder to read and reuse in complex queries
StormEvents | summarize count() by startofmonth(StartTime)
// Clearer — extend first, then summarize by the computed column
StormEvents
| extend Month = startofmonth(StartTime)
| summarize count() by Month
| order by Month asc
```
### Useful datetime functions
| Function | Purpose | Example |
|----------|---------|---------|
| `bin(ts, 1h)` | Round down to bucket boundary | `bin(Timestamp, 1d)` |
| `startofmonth(ts)` | First day of month | `startofmonth(Timestamp)` |
| `datetime_part("hour", ts)` | Extract component | `datetime_part("year", Timestamp)` |
| `format_datetime(ts, fmt)` | Format as string | `format_datetime(Timestamp, "yyyy-MM")` |
| `ago(1d)` | Relative time | `where Timestamp > ago(1d)` |
| `between(a .. b)` | Range filter (inclusive) | `where Timestamp between (datetime(2024-01-01) .. datetime(2024-01-31T23:59:59))` |
| `todatetime(str)` | Parse string → datetime | `todatetime("2024-01-15T10:30:00Z")` |
| `totimespan(str)` | Parse string → timespan | `totimespan("01:30:00")` |
## 12. Operator Naming & Equality
KQL has subtle differences from SQL syntax.
### Naming conventions
| Entity | Convention | Example |
|--------|-----------|---------|
| Tables | UpperCamelCase | `StormEvents`, `NetworkLogs` |
| Columns | UpperCamelCase | `StartTime`, `EventType` |
| Variables (`let`) | snake_case | `let filtered_events = ...` |
| Built-in functions | snake_case | `format_bytes()`, `geo_distance_2points()` |
| Stored functions | UpperCamelCase | `.create function GetTopUsers` |
### Equality operators
```kql
// In where clauses, == is case-sensitive, =~ is case-insensitive
StormEvents | where State == "TEXAS" | count // exact match
StormEvents | where State =~ "texas" | count // case-insensitive
// In joins, use == only
StormEvents | join kind=inner (PopulationData) on State
```
### sort vs order
Both `sort by` and `order by` work identically in KQL — they are aliases. Use whichever you prefer, but be consistent.
### contains vs has
```kql
// contains: substring match (slower)
StormEvents | where EventNarrative contains "tree" // finds "trees", "treetop" too
// has: term/word match (faster, uses index)
StormEvents | where EventNarrative has "tree" // matches word boundaries only
// For exact prefix/suffix
StormEvents | where EventType startswith "Thunder"
StormEvents | where Source endswith "Spotter"
```
## 13. Error Recovery Strategy
When a first KQL query fails, the temptation is to abandon the entire approach and try something completely different. The correct response is almost always to **fix the specific error**, not change strategy.
### The pattern to avoid
```
Query 1: extract(@"pattern", 1, col) → Parse error
Query 2: todynamic(col) → Different error
Query 3: parse_json(col) → Another error
Query 4: Python script → Works but 10x tokens
```
### The correct pattern
```
Query 1: extract(@"pattern", 1, col) → Parse error (bad escaping)
Query 2: extract(@"pattern", 1, col) → Fix the specific escaping issue → Success
```
**Rules for error recovery:**
1. Read the error message carefully — it almost always tells you exactly what's wrong
2. Fix the **specific** syntax/escaping issue, don't switch approaches
3. Use the self-correction table (Section 10) to map errors to fixes
4. Only switch approaches after 2 failed fixes of the same query
5. The `parse` operator is often simpler than `extract()` for structured text:
```kql
// Instead of complex regex on TraceLogs:
// extract(@"file path: \"\"([^\"]+)\"\"", 1, Message)
// Use parse for structured extraction (try it on help cluster, SampleLogs db):
cluster("help").database("SampleLogs").TraceLogs
| where Message has "file path"
| parse Message with * "file path: \"\"" FilePath "\"\"" *
| project Timestamp, FilePath
| take 5
```
## 14. Query Writing Checklist
Before running any KQL query, mentally check:
1. **Pre-filtered?** Large tables have a `| where` before any `| summarize`
2. **Result bounded?** Exploratory queries end with `| take N` or `| top N`
3. **Dynamic columns cast?** Any dynamic column in `by`/`on`/`order by` is wrapped
4. **Regex has groups?** `extract_all` patterns have `()` around what you want to capture
5. **Join cardinality safe?** Both sides checked with `dcount()` before joining
6. **Needed columns only?** Wide tables get `| project` to drop unneeded columns
7. **Datetime literals valid?** Using `datetime(2024-01-01)` not `datetime(2024)` or bare integers
8. **Complex by-expressions?** Use `| extend` first, then `| summarize by` the computed column
9. **Error recovery plan?** If a query fails, fix the specific error — don't change strategy
すべてのファイル
0件のファイルkqlをインストール
スキルファイルをダウンロードし、.claude/skills/ ディレクトリに解凍してください。
ZIPをダウンロードリポジトリをクローンし、スキルファイルをプロジェクトにコピーしてください。
git clone https://github.com/microsoft/skills/tree/main/.github/skills/kql # Copy SKILL.md to your .claude/skills/ directory
コピー





家
