Prepare Data · Topic 7 of 7
Query with KQL and DAX
Select, filter and aggregate data with KQL in an Eventhouse and with DAX queries in DAX query view: where, summarize, EVALUATE, SUMMARIZECOLUMNS.
- 5 min
- KQL reads top to bottom: table, then
where,extend,project,summarize ... by,order by,top. bin()buckets time;ago()is relative to now;render timechartdraws a chart.- KQL is case-sensitive;
hasis faster thancontains; default join kind is innerunique.
What it is
KQL (Kusto Query Language) queries KQL databases in an Eventhouse. It is built for large volumes of time-stamped data such as logs and telemetry, and you write it in a KQL queryset or directly in the database.
DAX queries a semantic model. Besides writing measures, you can run whole DAX queries in DAX query view in Power BI Desktop or the Fabric web editor, or in tools such as DAX Studio over the XMLA endpoint.
How it works
KQL: a pipeline of operators
A KQL query starts with a table and passes it through operators separated by |. Each operator works on the output of the one before.
SensorReadings
| where Timestamp > ago(1h)
| where DeviceType == "Thermostat"
| summarize AvgTemp = avg(Temperature), Readings = count()
by DeviceId, bin(Timestamp, 5m)
| order by AvgTemp desc
| SQL idea | KQL operator |
|---|---|
SELECT col1, col2 | project col1, col2 |
| Computed column | extend Total = Qty * Price |
WHERE | where |
GROUP BY with aggregates | summarize sum(Amount) by Region |
ORDER BY | order by (or sort by) |
TOP 10 | top 10 by Amount desc, or take 10 for any 10 rows |
COUNT(DISTINCT x) | dcount(x), an estimate; count_distinct(x) is exact |
JOIN | join kind=inner (OtherTable) on Key |
bin(Timestamp, 5m) groups times into five-minute buckets, and ago(1h) means one hour before now. Adding | render timechart draws the result as a chart.
KQL details the exam likes
- Names and the
==operator are case-sensitive;=~compares strings without case. hasmatches a whole term and uses the index;containsmatches any substring and is slower.- The default
joinkind is innerunique, which keeps only one row per key from the left table. Writekind=innerwhen you need every match.
Readings
| join kind=inner (Devices | project DeviceId, Building) on DeviceId
| summarize Readings = count() by Building
| top 5 by Readings desc
DAX queries
A DAX query starts with EVALUATE and must return a table. SUMMARIZECOLUMNS is the usual way to group and aggregate, and it is the function Power BI visuals generate.
EVALUATE
SUMMARIZECOLUMNS (
'Product'[Category],
FILTER ( VALUES ( 'Date'[Year] ), 'Date'[Year] = 2026 ),
"Total Sales", [Total Sales]
)
ORDER BY [Total Sales] DESC
DEFINE adds measures or variables that exist only for the query:
DEFINE
MEASURE Sales[Avg Order Value] =
DIVIDE ( [Total Sales], DISTINCTCOUNT ( Sales[OrderID] ) )
EVALUATE
TOPN (
5,
SUMMARIZECOLUMNS (
'Customer'[Customer Name],
"Sales", [Total Sales],
"AOV", [Avg Order Value]
),
[Sales], DESC
)
ORDER BY [Sales] DESC
To return a single value, wrap it in a table constructor: EVALUATE { [Total Sales] }.
DAX query view
DAX query view lets you write and run queries, and right-click a table or measure for quick queries such as showing the top rows or defining all measures. A measure defined in a query can be added to the model from the query view, which makes it a safe place to test a measure first.
Because a DAX query runs against the model, it uses the model's relationships and measures, and row-level security applies to users who belong to a role. The same question asked in SQL against the warehouse would need its own joins and could give different totals if the measure logic differs.
Which language for which store
| Data lives in | Query it with |
|---|---|
| Warehouse or SQL analytics endpoint | T-SQL or the Visual query editor |
| Eventhouse (KQL database) | KQL |
| Semantic model | DAX |
| Lakehouse files and tables | Spark (PySpark or Spark SQL), or T-SQL for tables |
A worked example
An operations team stores thermostat telemetry in an Eventhouse and has a semantic model of building costs.
- To find devices whose average temperature in the last hour is above 26, run
where Timestamp > ago(1h), thensummarize avg(Temperature) by DeviceId, thenwhereon the average. - To check a new "cost per square metre" measure before adding it to the model, define it in DAX query view and evaluate it in a
SUMMARIZECOLUMNSquery. - When the numbers match the finance team's figures, add the measure to the model from the query view.
In KQL, where after summarize filters the aggregated rows, just as HAVING does in SQL.
How the exam asks about this
- "Count events in 15-minute intervals over the last day."
where Timestamp > ago(1d) | summarize count() by bin(Timestamp, 15m). - "Return every matching row from both tables in a KQL join." Use
join kind=inner, not the default innerunique. - "Test a measure against the model without changing the model." Define it with
DEFINE MEASUREin DAX query view.
What to remember for the exam
- KQL reads top to bottom: table, then
where,extend,project,summarize ... by,order by,top. bin()buckets time;ago()is relative to now;render timechartdraws a chart.- KQL is case-sensitive;
hasis faster thancontains; default join kind is innerunique. - A DAX query starts with
EVALUATEand returns a table;DEFINE MEASUREcreates query-scoped measures. SUMMARIZECOLUMNSgroups and aggregates in DAX; DAX queries use the model's relationships, and RLS applies to role members.
EVALUATE [Total Sales] is an error because a measure returns a scalar. Use EVALUATE { [Total Sales] } or ROW ( "Sales", [Total Sales] ).
Common mistakes
Using take when the question needs the top values. take returns any rows. top N by column sorts and returns the highest values.
Writing a KQL filter with the wrong case. where Country == "france" misses "France". Use =~ or match the exact case.
Querying the warehouse to check a model measure. SQL does not apply the model's measures or relationships. Test the measure with a DAX query against the semantic model.
Check yourself
Which KQL operator aggregates rows, and how do you group by time buckets?
summarize, withbin(Timestamp, 5m)or another interval in thebyclause.Why might a KQL join return fewer rows than expected?
The default join kind is innerunique, which keeps one left row per key. Use
kind=innerto keep all matches.What must a DAX query return?
A table.
EVALUATEneeds a table expression, so wrap a single value in{ }orROW.
Topic 7 of 7 in Prepare Data
Test yourself on Prepare Data
The full Microsoft DP-600 question bank has 648 questions, with timed practice exams and an explanation for every answer.
This study guide is independent and is not affiliated with, endorsed by, or sponsored by Microsoft. Vendor names, certification names and exam codes are trademarks of their respective owners and are used for identification only.