Option

Verfassen Sie korrekte und effiziente Abfragen in der Kusto Query Language – mit Schwerpunkt auf Syntax, Joins, dynamischen Typen, Fallstricken bei Datums- und Zeitangaben, regulären Ausdrücken, Serialisierung, Speicherverwaltung und fortgeschrittenen Funktionen.

...Alle erweitern
11
Zeit aktualisiert 10. September 2026

KQL Meisterschaft

Probieren Sie es selbst aus: Alle Beispiele in dieser Fertigkeit können auf dem öffentlichen Hilfe-Cluster ausgeführt werden: https://help.kusto.windows.net, Datenbank Samples (enthält StormEvents, SimpleGraph_Nodes/Edges, nyc_taxiund mehr).

1. Grundlagen der „KQL“

Die Kusto-Abfragesprache (KQL) ist eine Pipe-Forward-Abfragesprache zur Datenauswertung. Sie ist die native Abfragesprache für Azure Data Explorer (ADX), Microsoft Fabric Real-Time Intelligence (EventHouse), Azure Monitor Log Analytics, Microsoft Sentinel und andere Microsoft-Datendienste.

Pipe-Forward-Syntax

KQL Abfragen bestehen aus einer Kette von Operatoren, die durch |getrennte Operatoren. Der Datenfluss erfolgt von links nach rechts:

StormEvents                          // start with a table
| where State == "TEXAS"             // filter rows
| summarize count() by EventType     // aggregate
| top 5 by count_ desc              // limit results

Abfragen vs. Verwaltungsbefehle

KQL verfügt über zwei Ausführungsebenen:

Ebene Beginnt mit Beispiele
Abfrage Tabellenname, let, print, datatable StormEvents | where State == "TEXAS"
Verwaltung .show, .create, .set, .drop, .alter .show tables, .show table T schema

Auf Verwaltungsbefehle können Abfrageoperatoren folgen (die Ausgabe erfolgt tabellarisch), aber die gesamte Anfrage wird auf der Verwaltungsebene ausgeführt. Sie können nicht mit einer Abfrage beginnen und diese an einen Verwaltungsbefehl weiterleiten.

// ✅ 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

Im Zweifelsfall gilt: Wenn das erste Token mit ., handelt es sich um einen Verwaltungsbefehl. Eine vollständige Liste der Befehle zur Schema-Erkundung finden Sie unter references/discovery-queries.md.

2. Dynamische Typdisziplin

KQLs dynamic Typ ist flexibel, in bestimmten Kontexten jedoch streng. Ein häufiger Fehler ist die Verwendung einer dynamischen Spalte in summarize by, order byoder join on ohne Typumwandlung.

Die Regel: Immer wenn Sie eine dynamisch typisierte Spalte in by, onoder order byverwenden, müssen Sie diese in einen expliziten Typumwandlungsausdruck einschließen.

// ❌ 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

Selbstkorrektur: Wenn in einer Fehlermeldung „ist vom Typ ‚dynamisch‘“ steht, fügen Sie tostring(), tolong()oder todouble().

3. Verknüpfungsmuster und Fallstricke

KQL Joins unterliegen Einschränkungen, die sich von denen in SQL unterscheiden.

Nur Gleichheitsbedingungen

KQL Verbindungsbedingungen unterstützen ausschließlich „==“. Keine <, >, !=oder Funktionsaufrufe in Join-Prädikaten.

// ❌ 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

Bei Bereichsverknüpfungen müssen die Werte vorab in Klassen eingeteilt werden: | extend bin_val = bin(Value, 100), dann wird nach bin_val. Hinweis: Werte nahe den Bin-Grenzen können in benachbarte Bins fallen – erwägen Sie zur Genauigkeit die Überprüfung benachbarter Bins oder eine Überlappung des Bereichs.

Abgleich von Attributen links/rechts

Beide Seiten einer Join- on müssen ausschließlich auf Spaltenentitäten verweisen – nicht auf Ausdrücke oder Aggregate.

// ❌ 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

Kardinalitätsprüfung vor umfangreichen Verknüpfungen

Überprüfen Sie stets die Kardinalität, bevor Sie Tabellen mit mehr als 10.000 Zeilen verknüpfen. Eine Cross-Join-Explosion war die Ursache für den einzigen E_RUNAWAY_QUERY Fehler (25.000 × 195 = potenziell 4,8 Millionen Zeilen).

// 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 unterstützt reguläre Ausdrücke nativ – Python ist nicht erforderlich.

Der extract_all Haken

Im Gegensatz zu Python re.findall()erfordert der „KQL“ extract_all erfordert in der Regex das Einfügen von Erfassungsgruppen:

// ❌ 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 – greifen Sie nicht auf Python

Funktion Anwendungsfall Beispiel
extract(regex, group, source) Einzelne Übereinstimmung extract(@"User '([^']+)'", 1, Msg)
extract_all(regex, source) Alle Übereinstimmungen (erfordert ()) extract_all(@"(\w+)", Text)
parse Strukturierte Extraktion parse Msg with * "User '" Sender "' sent" *
matches regex Boolescher Filter where Url matches regex @"^https?://"
replace_regex Suchen und Ersetzen replace_regex(Text, @"\s+", " ")

5. Anforderungen an die Serialisierung

Fensterfunktionen benötigen serialisierte (geordnete) Eingaben.

// ❌ 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)

Funktionen, die eine Serialisierung erfordern: row_number(), row_cumsum(), prev(), next(), row_window_session().

6. Speichersichere Abfragemuster

Der häufigste Speicherfehler. Verursacht durch das Durchsuchen zu großer Datenmengen ohne Vorfilterung.

Die Stufen der Sicherheit

Safest ──────────────────────────────────────────────── Most dangerous
| count    | take 10    | where + summarize    | summarize (no filter)    | full scan

Regeln für große Tabellen (>1 Mio. Zeilen)

  1. Beginnen Sie immer mit „| count“, um die Tabellengröße zu ermitteln
  2. Führen Sie immer zuerst| where“ aus, bevor Sie „| summarize“ anwenden – filtern Sie zunächst den Zeitbereich, den Partitionsschlüssel oder die Kategorie
  3. Führen Sie niemals „dcount()“ auf Spalten mit hoher Kardinalität durch, ohne zuvor zu filtern
  4. Überprüfen Sie die Join-Kardinalität vor der Ausführung (siehe Abschnitt 3)
  5. Verwenden Sie „materialize() für Unterabfragen, auf die mehrfach verwiesen wird
// ❌ 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

Wenn Sie Folgendes sehen E_LOW_MEMORY_CONDITION

Die Abfrage hat zu viele Daten erfasst. Ihre Optionen:

  • Fügen Sie | where Filter hinzu (Zeitbereich, Partitionsschlüssel)
  • Reduzieren Sie die Anzahl der by Spalten in summarize
  • In kleinere Zeitfenster unterteilen und die Ergebnisse zusammenführen
  • Verwenden Sie | sample 10000 für explorative Analysen anstelle von Vollscans

Wenn Sie feststellen, dass E_RUNAWAY_QUERY

dass ein Join oder eine Aggregation zu viele Ausgabezeilen erzeugt hat, überprüfen Sie die Join-Kardinalität – eine oder beide Seiten ist zu groß.

7. Disziplin bei der Ergebnisgröße

Große Ergebnisse verlangsamen die Analyse. Vorbeugung:

Abfragetyp Sicherheitsmaßnahme
Explorativ Immer mit | take 10 oder | take 20
Aggregation Verwenden Sie | top 20 by ... nicht unbegrenzt summarize
Breite Zeilen (Vektoren, JSON) | project nur benötigte Spalten
make_list() / make_set() Bei Gruppen mit hoher Kardinalität vermeiden (erzeugt riesige Zellen)
Unbekannte Größe Ausführen | count zuerst

Die Vektor-Falle: Tabellen mit eingebetteten Spalten (1536-dimensionale Float-Arrays) erzeugen ca. 30 KB pro Zeile. Selbst | take 20 ergeben 600 KB. Verwenden Sie | project Vektorspalten entfernen, es sei denn, Sie benötigen sie ausdrücklich.

8. Strenge beim Zeichenfolgenvergleich

KQL erfordert manchmal explizite Typumwandlungen beim Vergleich berechneter Zeichenfolgenwerte – selbst wenn beide Seiten bereits Zeichenfolgen sind.

// ❌ 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)

Dies tritt am häufigsten bei berechneten Werten aus geo_point_to_s2cell() und strcat() Vergleichen. Im Zweifelsfall sollten Sie eine Typumwandlung mit tostring().

9. Erweiterte Funktionen

KQL behandelt diese nativ – Python ist nicht erforderlich:

Vektorähnlichkeit

// 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-Operationen

// 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)

Graphabfragen

// 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)

Zeitreihen

// 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_)

Ausführliche Beispiele und Muster finden Sie unter references/advanced-patterns.md.

10. Lookup-Tabelle zur Selbstkorrektur

Wenn ein Fehler auftritt, schlagen Sie ihn hier nach, bevor Sie den Vorgang wiederholen:

Die Fehlermeldung enthält Mögliche Ursache Behebung
is of a 'dynamic' type Dynamische Spalte in by/on/order by In tostring()/tolong()
Only equality is allowed Bereichsprädikat in der Join-Bedingung Vorab-Bucket mit S2/H3-Zellen oder bin()
extractall(): matching groups Fehlendes () in regulärem Ausdruck Hinzufügen (): @"(\w+)" nicht @"\w+"
row set must be serialized Fensterfunktion auf unsortierte Daten Hinzufügen | serialize oder | order by vorher
Cannot compare values of types string and string Berechneter Zeichenfolgenvergleich Hinzufügen tostring() auf beiden Seiten
Failed to resolve column named 'X' Falscher Spaltenname oder falsche Tabelle Ausführen .show table T schema um Spaltennamen zu überprüfen
E_LOW_MEMORY_CONDITION Die Abfrage hat zu viele Daten erfasst Fügen Sie | where Filter hinzu, verkürze den Zeitbereich, teile in Schritte auf
E_RUNAWAY_QUERY Join/Aggregation hat zu viele Zeilen erzeugt Überprüfen Sie die Kardinalität vor der Verknüpfung; fügen Sie Vorfilter hinzu
for each left attribute, right attribute Die Join- on Klausel unvollständig Explizite Form verwenden: on $left.X == $right.Y
needs to be bracketed Reserviertes Wort als Bezeichner verwendet Verwenden Sie ['keyword'] Syntax
plugin doesn't exist Plugin auf diesem Cluster nicht verfügbar Auf eine äquivalente Funktion oder Python ausweichen
Expected string literal in datetime() Einfache Ganzzahl in einem Datums-/Uhrzeit-Literal Verwende datetime(2024-01-01) nicht datetime(2024)
Unexpected token nach by Komplexer Ausdruck in der „summarize“-By-Klausel extend zuerst den Ausdruck, dann summarize by die Spalte
not recognized / unknown operator Operator in dieser Engine nicht verfügbar Überprüfen Sie die Operatorunterstützung; versuchen Sie es mit einem Äquivalent (order by = sort by)

11. Fallstricke bei Datums- und Zeitangaben

Datums- und Zeitliterale sind eine häufige Fehlerquelle. Ein falsches Literalformat kann zu völlig anderen Lösungsansätzen führen, anstatt das kleine Problem zu beheben.

Literalformat

// ❌ 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)

Filtern nach Jahr, Monat oder Stunde

// ❌ 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))

Zeitbündelung in „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

Nützliche Datums- und Zeitfunktionen

Funktion Zweck Beispiel
bin(ts, 1h) Auf die nächste Zeitgruppe abrunden bin(Timestamp, 1d)
startofmonth(ts) Erster Tag des Monats startofmonth(Timestamp)
datetime_part("hour", ts) Komponente extrahieren datetime_part("year", Timestamp)
format_datetime(ts, fmt) Als Zeichenfolge formatieren format_datetime(Timestamp, "yyyy-MM")
ago(1d) Relative Zeit where Timestamp > ago(1d)
between(a .. b) Bereichsfilter (inklusiv) where Timestamp between (datetime(2024-01-01) .. datetime(2024-01-31T23:59:59))
todatetime(str) Zeichenkette analysieren → Datums- und Zeitangabe todatetime("2024-01-15T10:30:00Z")
totimespan(str) Zeichenkette in Zeitdauer parsen totimespan("01:30:00")

12. Benennung von Operatoren und Gleichheit

KQL weist feine Unterschiede zur SQL-Syntax auf.

Namenskonventionen

Entität Konvention Beispiel
Tabellen UpperCamelCase StormEvents, NetworkLogs
Spalten UpperCamelCase StartTime, EventType
Variablen (let) snake_case let filtered_events = ...
Eingebaute Funktionen snake_case format_bytes(), geo_distance_2points()
Gespeicherte Funktionen UpperCamelCase .create function GetTopUsers

Gleichheitsoperatoren

// 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

Beides sort by und order by funktionieren in „KQL“ identisch – es handelt sich um Aliase. Verwenden Sie den Begriff, den Sie bevorzugen, aber bleiben Sie dabei konsequent.

„contains“ vs. „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. Strategie zur Fehlerbehebung

Wenn eine erste „KQL“-Abfrage fehlschlägt, ist man versucht, den gesamten Ansatz aufzugeben und etwas völlig anderes zu versuchen. Die richtige Reaktion besteht fast immer darin, den konkreten Fehler zu beheben, nicht die Strategie zu ändern.

Das zu vermeidende Muster

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

Das richtige Muster

Query 1: extract(@"pattern", 1, col)  → Parse error (bad escaping)
Query 2: extract(@"pattern", 1, col)  → Fix the specific escaping issue → Success

Regeln für die Fehlerbehebung:

  1. Lies die Fehlermeldung sorgfältig durch – sie sagt dir fast immer genau, was falsch ist
  2. Beheben Sie das konkrete Syntax- oder Escaping-Problem, wechseln Sie nicht den Ansatz
  3. Verwenden Sie die Selbstkorrekturtabelle (Abschnitt 10), um Fehler den entsprechenden Korrekturen zuzuordnen
  4. Wechseln Sie erst dann die Vorgehensweise, wenn zwei Korrekturversuche für dieselbe Abfrage fehlgeschlagen sind
  5. Der parse Operator ist oft einfacher als extract() bei strukturiertem Text:
// 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. Checkliste zum Verfassen von Abfragen

Bevor Sie eine „KQL“-Abfrage ausführen, prüfen Sie im Kopf:

  1. Vorab gefiltert? Große Tabellen haben eine | where vor jeder | summarize
  2. Ist das Ergebnis begrenzt? Explorative Abfragen enden mit | take N oder | top N
  3. Wurden dynamische Spalten umgewandelt? Wird jede dynamische Spalte in by/on/order by wird umschlossen
  4. Enthält der Regex-Ausdruck Gruppen? extract_all Muster enthalten () um das, was du erfassen möchtest
  5. Ist die Join-Kardinalität sicher? Beide Seiten werden mit dcount() vor der Verknüpfung
  6. Nur benötigte Spalten? Breite Tabellen werden | project nicht benötigte Spalten entfernt
  7. Sind Datums- und Zeitliterale gültig? Verwendung von datetime(2024-01-01) nicht datetime(2024) oder bloße Ganzzahlen
  8. Komplexe Nebenausdrücke? Verwenden Sie | extend zuerst, dann | summarize by die berechnete Spalte
  9. Fehlerbehebungsplan? Wenn eine Abfrage fehlschlägt, beheben Sie den konkreten Fehler – ändern Sie nicht die Strategie
Auf GitHub ansehen
---
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

Alle Dateien

0 Dateien

kql installieren

Laden Sie die Skill-Dateien herunter und entpacken Sie sie in Ihr Verzeichnis „.claude/skills/“.

ZIP herunterladen

Klonen Sie das Repository und kopieren Sie die Skill-Dateien in Ihr Projekt.

git clone https://github.com/microsoft/skills/tree/main/.github/skills/kql # Copy SKILL.md to your .claude/skills/ directory

Kopieren Kopieren
Schnelle Einrichtung: Kopiere den Skill-Ordner nach .claude/skills/ Claude erkennt den Skill automatisch und nutzt ihn.
Repository microsoft/skills

Ähnliche Skills

microservices-patterns
Zeit aktualisiert 29. Juni 2026
jpa-patterns
Zeit aktualisiert 30. Juni 2026
fabric-lakehouse
Zeit aktualisiert 30. Juni 2026
PostgreSQL Syntax Reference
Zeit aktualisiert 29. Juni 2026
OR