In this module
1.6 Module Summary
Module 1 Summary
What you learned
The tabular model — every KQL input and output is a table. A count is a table. A summarize is a table. An empty result is a table. Understanding this means you can predict the output shape of any operator chain.
The query pipeline — operators execute sequentially, top to bottom. Each operator consumes the previous operator's table and produces a new table. Operator order affects both results and performance. Filter early to reduce data volume before expensive operations.
Data types — string, datetime, dynamic, bool, int, long, real, timespan, guid. The type determines which operators work on a column. The most common source of incorrect results is type mismatch — comparing a string column to an integer literal.
Type conversion — every to* function returns null on failure. After conversion, check for nulls. Use coalesce() to provide defaults. Use parse_json() to convert string-encoded JSON into queryable dynamic objects.
Null handling — null fails all comparisons, including !=. Any where clause silently excludes rows with null values in the compared column. In security investigations, adversary activity can hide in null fields. Always account for null rates in critical columns.
Key rules from this module
- Always check column types with
getschemabefore writing comparisons - Place
wherefilters as early as possible in the pipeline - After type conversion, verify with
isnotnull()orisnotempty() - When filtering for "not X," always include nulls:
where Field != "X" or isempty(Field) - Use
coalesce()to replace null with meaningful defaults
Practical application
Before proceeding to Module 2, verify your understanding by running these three diagnostic queries:
Query 1: Schema awareness
SigninLogs
| getschema
| where DataType == "System.Object" // dynamic columns
| project ColumnNameYou should be able to identify what data each dynamic column contains without looking it up.
Query 2: Type safety
SigninLogs
| where TimeGenerated > ago(1h)
| take 100
| extend ResultAsInt = toint(ResultType)
| summarize
Converted = countif(isnotnull(ResultAsInt)),
Failed = countif(isnull(ResultAsInt))If Failed is greater than zero, you have data where toint(ResultType) fails — the exact scenario this module teaches you to handle.
Query 3: Null audit
SigninLogs
| where TimeGenerated > ago(24h)
| summarize
Total = count(),
NullIP = countif(isempty(IPAddress)),
NullLocation = countif(isempty(tostring(LocationDetails.countryOrRegion))),
NullUA = countif(isempty(UserAgent))
| extend
PctNullIP = round(100.0 * NullIP / Total, 1),
PctNullLoc = round(100.0 * NullLocation / Total, 1),
PctNullUA = round(100.0 * NullUA / Total, 1)Record these null percentages. Every investigation query and detection rule you write against SigninLogs should account for these null rates.
How was this module?
Your feedback helps us improve the course. One click is enough — comments are optional.
You've learned how KQL processes data.
K0 gave you the query language's place in the Microsoft security stack. K1 took you through the semantics — tables, operators, the pipe model, and why KQL isn't SQL. Now you write the queries that find what attackers hope you miss.
- 12 modules of query craft — filtering and shaping, joins and unions, time-series analysis, summarisation, string manipulation, and geospatial analysis
- 68 KQL exercises — every one with a realistic dataset, a reference solution, and a discussion of alternative approaches
- K11 — Threat Hunting with KQL — the course's flagship module. Hypothesis-driven methodology, MITRE ATT&CK-aligned hunting across 7 techniques, UEBA composite risk scoring, and retroactive IOC sweeps
- K13 Capstone — The Hunting Lab — three complete investigation scenarios requiring every query skill from the course
- Hunt management and ROI metrics — the operating model that justifies KQL hunt programs to leadership