@microsoft/powerbi-modeling-mcp-linux-arm64 0.5.0-beta.13 → 0.5.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -1
- package/LICENSE +21 -99
- package/NOTICE.txt +15014 -0
- package/README.md +33 -8
- package/dist/Resources/calendar_instructions_and_examples.md +249 -0
- package/dist/Resources/dax_query_instructions_and_examples.md +714 -0
- package/dist/Resources/dax_udf_instructions_and_examples.md +270 -0
- package/dist/Resources/powerbi_project_instructions.md +84 -0
- package/dist/Resources/tools/tool-metadata.override.yaml +7 -0
- package/dist/Resources/tools/tool-metadata.yaml +495 -0
- package/dist/powerbi-modeling-mcp +2 -2
- package/package.json +3 -3
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 'DAX Query Instructions and Examples'
|
|
3
|
+
description: 'Guidelines for writing Power BI DAX queries'
|
|
4
|
+
uriTemplate: 'resource://dax_query_instructions_and_examples'
|
|
5
|
+
---
|
|
6
|
+
# DAX Query Language Guide
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
DAX (Data Analysis Expressions) is a formula language used in Power BI for creating custom calculations and queries. This guide provides comprehensive instructions and examples for writing valid DAX query expressions.
|
|
11
|
+
|
|
12
|
+
## DAX Query Best Practices
|
|
13
|
+
|
|
14
|
+
When writing DAX queries, follow these recommendations for optimal results:
|
|
15
|
+
|
|
16
|
+
* Include comments for clarity (DAX comments use `//` not `--`)
|
|
17
|
+
* Always include an ORDER BY clause when returning multiple rows
|
|
18
|
+
* Use meaningful variable names to improve readability
|
|
19
|
+
* Define measures with fully qualified names in DEFINE blocks
|
|
20
|
+
|
|
21
|
+
## DAX Query Syntax Rules
|
|
22
|
+
|
|
23
|
+
### Query Structure
|
|
24
|
+
|
|
25
|
+
#### DEFINE Block
|
|
26
|
+
|
|
27
|
+
* Use DEFINE at the beginning if the query includes VAR, MEASURE, COLUMN, or TABLE definitions
|
|
28
|
+
* Only use a single DEFINE block per query
|
|
29
|
+
* Separate definitions with new lines (no commas or semicolons)
|
|
30
|
+
|
|
31
|
+
#### Measure Definitions
|
|
32
|
+
|
|
33
|
+
* When defining: ALWAYS fully qualify the measure name including its host table
|
|
34
|
+
* Example: `DEFINE MEASURE 'TableName'[MeasureName] = ...`
|
|
35
|
+
* The host table must exist in the semantic model
|
|
36
|
+
* When using: Refer to the measure by name only, without the table qualifier
|
|
37
|
+
* Example: Use `[MeasureName]` in expressions like `CALCULATE([MeasureName], ...)`
|
|
38
|
+
|
|
39
|
+
#### Ordering Results
|
|
40
|
+
|
|
41
|
+
* ALWAYS include an ORDER BY clause when EVALUATE returns multiple rows
|
|
42
|
+
* Do not use the ORDERBY function to sort the final query result
|
|
43
|
+
|
|
44
|
+
### CALCULATE and CALCULATETABLE Filter Rules
|
|
45
|
+
|
|
46
|
+
Boolean filters in CALCULATE or CALCULATETABLE have important restrictions:
|
|
47
|
+
|
|
48
|
+
* Cannot directly use a measure or another CALCULATE function
|
|
49
|
+
* Solution: Use a variable to store the result, then reference the variable
|
|
50
|
+
* Cannot reference columns from two different tables
|
|
51
|
+
* When using the IN operator, the table operand must be a table variable, not a table expression
|
|
52
|
+
* Do not assign a boolean filter to a VAR definition
|
|
53
|
+
|
|
54
|
+
### SUMMARIZECOLUMNS Function
|
|
55
|
+
|
|
56
|
+
**Purpose**: Build summary tables with groupby columns and measure-like extension columns
|
|
57
|
+
|
|
58
|
+
**Parameter Order** (all optional, but must follow this order if used):
|
|
59
|
+
|
|
60
|
+
1. Groupby columns (can be from one or multiple tables)
|
|
61
|
+
2. Filters
|
|
62
|
+
3. Measures or measure-like calculations
|
|
63
|
+
|
|
64
|
+
**Key Rules**:
|
|
65
|
+
|
|
66
|
+
* Use SUMMARIZECOLUMNS as the default for building summary tables with measures
|
|
67
|
+
* Do not use SUMMARIZECOLUMNS without measure-like extension columns
|
|
68
|
+
* Returns only rows where at least one measure value is not BLANK
|
|
69
|
+
* Allows ANY number of measure-like calculations of arbitrary complexity
|
|
70
|
+
* DO NOT use boolean filters with SUMMARIZECOLUMNS
|
|
71
|
+
|
|
72
|
+
**When to Use Alternatives**:
|
|
73
|
+
|
|
74
|
+
* If there are no measures or calculations, use SUMMARIZE instead
|
|
75
|
+
|
|
76
|
+
### SUMMARIZE Function
|
|
77
|
+
|
|
78
|
+
**Allowed Pattern**:
|
|
79
|
+
|
|
80
|
+
```dax
|
|
81
|
+
SUMMARIZE(<table expression>, <column1>, …, <columnN>)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Critical Restrictions**:
|
|
85
|
+
|
|
86
|
+
* NEVER use SUMMARIZE with measure-like expressions
|
|
87
|
+
* ❌ Incorrect: `SUMMARIZE(<table>, <column>, "expr1", <expr1>, …)`
|
|
88
|
+
* ✅ Correct: Use SUMMARIZECOLUMNS for measure calculations
|
|
89
|
+
* Use for extracting distinct combinations of columns only
|
|
90
|
+
* `VALUES('Table'[Column])` is a shortcut for `SUMMARIZE('Table', 'Table'[Column])`
|
|
91
|
+
* When extracting a column from a table variable: `SUMMARIZE(_TableVar, [Column])`
|
|
92
|
+
* Note: `_TableVar[Column]` is not valid syntax
|
|
93
|
+
|
|
94
|
+
**When to Use Alternatives**:
|
|
95
|
+
|
|
96
|
+
* For measure calculations: Use SUMMARIZECOLUMNS
|
|
97
|
+
* For aggregations on table variables: Use GROUPBY
|
|
98
|
+
|
|
99
|
+
### GROUPBY Function
|
|
100
|
+
|
|
101
|
+
**Purpose**: Perform simple aggregations on table-valued variables at a grouped level
|
|
102
|
+
|
|
103
|
+
**Key Rules**:
|
|
104
|
+
|
|
105
|
+
* Only use GROUPBY with a table-valued variable as the first argument
|
|
106
|
+
* The CURRENTGROUP function is valid ONLY within GROUPBY
|
|
107
|
+
* CURRENTGROUP must not be used elsewhere
|
|
108
|
+
|
|
109
|
+
### SELECTCOLUMNS Function
|
|
110
|
+
|
|
111
|
+
**Purpose**: Project columns while preserving duplicates or renaming columns
|
|
112
|
+
|
|
113
|
+
**Key Rules**:
|
|
114
|
+
|
|
115
|
+
* Use to preserve duplicate rows (unlike SUMMARIZE which removes them)
|
|
116
|
+
* Use to rename columns for clarity
|
|
117
|
+
* When renaming columns, subsequent expressions (TOPN, ORDER BY) must use the NEW column names
|
|
118
|
+
|
|
119
|
+
**Important**: Include all columns needed for later operations (ORDER BY, FILTER, etc.)
|
|
120
|
+
|
|
121
|
+
### Table Expressions and Filters
|
|
122
|
+
|
|
123
|
+
* When using table expressions (SELECTCOLUMNS, CALCULATETABLE), include any columns needed later
|
|
124
|
+
* Filters applied to one table can propagate across relationships based on filter direction (unidirectional or bidirectional)
|
|
125
|
+
|
|
126
|
+
### Set Functions
|
|
127
|
+
|
|
128
|
+
When using INTERSECT, UNION, or EXCEPT:
|
|
129
|
+
|
|
130
|
+
* Both input tables must produce an identical number of columns
|
|
131
|
+
|
|
132
|
+
### Time Intelligence Functions
|
|
133
|
+
|
|
134
|
+
**DATESINPERIOD Rolling Windows**:
|
|
135
|
+
|
|
136
|
+
* The negative period offset must precisely match the number of periods required
|
|
137
|
+
* Examples:
|
|
138
|
+
* 12-month window: Use -12 (not -11)
|
|
139
|
+
* 3-month window: Use -3 (not -2)
|
|
140
|
+
* This prevents off-by-one errors
|
|
141
|
+
|
|
142
|
+
**Maintaining Clear Date Context**:
|
|
143
|
+
|
|
144
|
+
* Always establish a valid date context for time intelligence calculations
|
|
145
|
+
* Methods:
|
|
146
|
+
* Include groupby columns from the date table, OR
|
|
147
|
+
* Apply filters on date columns
|
|
148
|
+
* Without date context, time intelligence functions cannot determine a "current date" reference
|
|
149
|
+
* When using ROW function with time intelligence, supply external filters through CALCULATETABLE
|
|
150
|
+
|
|
151
|
+
## Sample Data Model
|
|
152
|
+
|
|
153
|
+
These examples use a simplified hypothetical data model:
|
|
154
|
+
|
|
155
|
+
```yaml
|
|
156
|
+
Tables:
|
|
157
|
+
- Name: Sales
|
|
158
|
+
Measures:
|
|
159
|
+
- Name: Total Discount
|
|
160
|
+
Type: Decimal
|
|
161
|
+
- Name: Total Amount
|
|
162
|
+
Type: Decimal
|
|
163
|
+
- Name: Total Quantity
|
|
164
|
+
Type: Integer
|
|
165
|
+
Columns:
|
|
166
|
+
- Name: CustomerKey
|
|
167
|
+
Type: Text
|
|
168
|
+
- Name: Order Quantity
|
|
169
|
+
Type: Integer
|
|
170
|
+
- Name: ProductKey
|
|
171
|
+
Type: Text
|
|
172
|
+
- Name: OrderDate
|
|
173
|
+
Type: Date
|
|
174
|
+
- Name: Sales Amount
|
|
175
|
+
Type: Decimal
|
|
176
|
+
- Name: Product
|
|
177
|
+
Measures:
|
|
178
|
+
- Name: Median List Price
|
|
179
|
+
Type: Decimal
|
|
180
|
+
Columns:
|
|
181
|
+
- Name: Category
|
|
182
|
+
Type: Text
|
|
183
|
+
MinValue: Consumer Electronics
|
|
184
|
+
MaxValue: Toys
|
|
185
|
+
- Name: Color
|
|
186
|
+
Type: Text
|
|
187
|
+
MinValue: Beige
|
|
188
|
+
MaxValue: Red
|
|
189
|
+
- Name: List Price
|
|
190
|
+
Description: Retail price of the product
|
|
191
|
+
Type: Decimal
|
|
192
|
+
- Name: Name
|
|
193
|
+
Type: Text
|
|
194
|
+
- Name: ProductKey
|
|
195
|
+
Type: Text
|
|
196
|
+
- Name: Customer
|
|
197
|
+
Columns:
|
|
198
|
+
- Name: CustomerKey
|
|
199
|
+
Type: Text
|
|
200
|
+
- Name: Name
|
|
201
|
+
Type: Text
|
|
202
|
+
- Name: Age
|
|
203
|
+
Type: Integer
|
|
204
|
+
- Name: Calendar
|
|
205
|
+
Columns:
|
|
206
|
+
- Name: Date
|
|
207
|
+
Type: Date
|
|
208
|
+
- Name: Month
|
|
209
|
+
Type: Text
|
|
210
|
+
SortByColumnName: MonthNumberOfYear
|
|
211
|
+
- Name: MonthNumberOfYear
|
|
212
|
+
Type: Integer
|
|
213
|
+
- Name: Year
|
|
214
|
+
Type: Integer
|
|
215
|
+
Active Relationships:
|
|
216
|
+
- PK: 'Product'[ProductKey]
|
|
217
|
+
FK: 'Sales'[ProductKey]
|
|
218
|
+
Unidirectional Filter Propagation: "'Product' filters 'Sales'"
|
|
219
|
+
- PK: 'Customer'[CustomerKey]
|
|
220
|
+
FK: 'Sales'[CustomerKey]
|
|
221
|
+
Unidirectional Filter Propagation: "'Customer' filters 'Sales'"
|
|
222
|
+
- PK: 'Calendar'[Date]
|
|
223
|
+
FK: 'Sales'[OrderDate]
|
|
224
|
+
Unidirectional Filter Propagation: "'Calendar' filters 'Sales'"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## DAX Query Examples
|
|
228
|
+
|
|
229
|
+
The following examples demonstrate proper DAX query syntax and best practices using the data model defined above.
|
|
230
|
+
|
|
231
|
+
### Example 1: Time Intelligence with Rolling Averages
|
|
232
|
+
|
|
233
|
+
**Scenario**: Calculate year-to-date total sales and 14-day moving average for red products.
|
|
234
|
+
|
|
235
|
+
```dax
|
|
236
|
+
// Year-to-date total sales and 14-day moving average of sales for red products.
|
|
237
|
+
EVALUATE
|
|
238
|
+
CALCULATETABLE(
|
|
239
|
+
ROW(
|
|
240
|
+
"Total Sales Amount YTD", TOTALYTD([Total Amount], 'Calendar'[Date]),
|
|
241
|
+
"Total Sales Amount 14-Day MA", AVERAGEX(DATESINPERIOD('Calendar'[Date], MAX('Calendar'[Date]), -14, DAY), [Total Amount]) // Note that the number_of_intervals parameter must be -14 instead of -13.
|
|
242
|
+
),
|
|
243
|
+
'Product'[Color] == "Red",
|
|
244
|
+
TREATAS({ MAX('Sales'[OrderDate]) }, 'Calendar'[Date]) // Establish a reference date for TI functions TOTALYTD and DATESINPERIOD.
|
|
245
|
+
)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Key Concepts**:
|
|
249
|
+
|
|
250
|
+
* Uses CALCULATETABLE with ROW to establish date context for time intelligence functions
|
|
251
|
+
* TREATAS establishes a clear "current date" reference for time intelligence
|
|
252
|
+
* DATESINPERIOD uses -14 (not -13) for a proper 14-day window
|
|
253
|
+
|
|
254
|
+
### Example 2: Multi-Level Aggregation with GROUPBY
|
|
255
|
+
|
|
256
|
+
**Scenario**: Calculate average, minimum, and maximum monthly sales quantity by year for Consumer Electronics before 2023.
|
|
257
|
+
|
|
258
|
+
```dax
|
|
259
|
+
DEFINE
|
|
260
|
+
// Filters for products in Consumer Electronics category
|
|
261
|
+
VAR _Filter1 = TREATAS(
|
|
262
|
+
{
|
|
263
|
+
"Consumer Electronics"
|
|
264
|
+
},
|
|
265
|
+
'Product'[Category]
|
|
266
|
+
)
|
|
267
|
+
// Filters to years before 2023
|
|
268
|
+
VAR _Filter2 = FILTER(
|
|
269
|
+
ALL('Calendar'[Year]),
|
|
270
|
+
'Calendar'[Year] < 2023
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
// Quantity filtered to Consumer Electronics products for years before 2023, grouped by month
|
|
274
|
+
VAR _SummaryTable = SUMMARIZECOLUMNS(
|
|
275
|
+
'Calendar'[Year],
|
|
276
|
+
'Calendar'[Month],
|
|
277
|
+
// [Month] is a required groupby column.
|
|
278
|
+
// A query always sorts by required groupby columns.
|
|
279
|
+
// [MonthNumberOfYear] is the orderby column for [Month].
|
|
280
|
+
// Also include [MonthNumberOfYear] to be used in the ORDER BY clause.
|
|
281
|
+
'Calendar'[MonthNumberOfYear],
|
|
282
|
+
_Filter1,
|
|
283
|
+
_Filter2,
|
|
284
|
+
"Monthly Quantity", [Total Quantity]
|
|
285
|
+
)
|
|
286
|
+
// Aggregate the summarized monthly data by year and month to derive average, minimum, and maximum monthly quantities.
|
|
287
|
+
EVALUATE
|
|
288
|
+
// GROUPBY function is used to summarize intermediate tables.
|
|
289
|
+
GROUPBY(
|
|
290
|
+
_SummaryTable,
|
|
291
|
+
'Calendar'[Year],
|
|
292
|
+
'Calendar'[Month],
|
|
293
|
+
'Calendar'[MonthNumberOfYear],
|
|
294
|
+
"Avg Monthly Quantity",
|
|
295
|
+
AVERAGEX(
|
|
296
|
+
CURRENTGROUP(), // must be used inside GROUPBY function
|
|
297
|
+
[Monthly Quantity]
|
|
298
|
+
),
|
|
299
|
+
"Min Monthly Quantity",
|
|
300
|
+
MINX(
|
|
301
|
+
CURRENTGROUP(), // must be used inside GROUPBY function
|
|
302
|
+
[Monthly Quantity]
|
|
303
|
+
),
|
|
304
|
+
"Max Monthly Quantity",
|
|
305
|
+
MAXX(
|
|
306
|
+
CURRENTGROUP(), // must be used inside GROUPBY function
|
|
307
|
+
[Monthly Quantity]
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
ORDER BY
|
|
311
|
+
'Calendar'[Year] ASC,
|
|
312
|
+
// [MonthNumberOfYear] is the orderby column for [Month].
|
|
313
|
+
// ORDER BY [MonthNumberOfYear] instead of [Month].
|
|
314
|
+
'Calendar'[MonthNumberOfYear] ASC
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Key Concepts**:
|
|
318
|
+
|
|
319
|
+
* SUMMARIZECOLUMNS creates initial summary with measures
|
|
320
|
+
* GROUPBY performs secondary aggregation on the summary table
|
|
321
|
+
* CURRENTGROUP is used exclusively within GROUPBY
|
|
322
|
+
* Includes MonthNumberOfYear for proper sorting
|
|
323
|
+
|
|
324
|
+
### Example 3: Filtering with Measures Using Variables
|
|
325
|
+
|
|
326
|
+
**Scenario**: Find products with total sales over $1 million that are red or black.
|
|
327
|
+
|
|
328
|
+
```dax
|
|
329
|
+
DEFINE
|
|
330
|
+
// Red or Black product filter
|
|
331
|
+
VAR _Filter = TREATAS(
|
|
332
|
+
{
|
|
333
|
+
"Red",
|
|
334
|
+
"Black"
|
|
335
|
+
},
|
|
336
|
+
'Product'[Color]
|
|
337
|
+
)
|
|
338
|
+
// Sales of Red or Black products
|
|
339
|
+
VAR _SummaryTable = SUMMARIZECOLUMNS(
|
|
340
|
+
'Product'[Name],
|
|
341
|
+
_Filter,
|
|
342
|
+
"Total Sales", [Total Amount]
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
// Products with total sales above $1,000,000
|
|
346
|
+
EVALUATE
|
|
347
|
+
SELECTCOLUMNS(
|
|
348
|
+
FILTER(
|
|
349
|
+
_SummaryTable,
|
|
350
|
+
[Total Sales] > 1000000
|
|
351
|
+
),
|
|
352
|
+
'Product'[Name]
|
|
353
|
+
)
|
|
354
|
+
ORDER BY
|
|
355
|
+
'Product'[Name] ASC
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**Key Concepts**:
|
|
359
|
+
|
|
360
|
+
* TREATAS creates a filter from a list of values
|
|
361
|
+
* SUMMARIZECOLUMNS builds summary with measure
|
|
362
|
+
* FILTER applied to summary table variable
|
|
363
|
+
* SELECTCOLUMNS projects only needed columns
|
|
364
|
+
|
|
365
|
+
### Example 4: SUMMARIZE for Distinct Values (No Duplicates)
|
|
366
|
+
|
|
367
|
+
**Scenario**: Get unique combinations of color, category, and product key for products sold in 2022.
|
|
368
|
+
|
|
369
|
+
```dax
|
|
370
|
+
// Product color, category and key for products sold in 2022
|
|
371
|
+
EVALUATE
|
|
372
|
+
CALCULATETABLE(
|
|
373
|
+
// SUMMARIZE is used to remove duplicate rows
|
|
374
|
+
SUMMARIZE(
|
|
375
|
+
'Sales',
|
|
376
|
+
'Product'[Color],
|
|
377
|
+
'Product'[Category],
|
|
378
|
+
'Sales'[ProductKey]
|
|
379
|
+
),
|
|
380
|
+
'Calendar'[Year] == 2022
|
|
381
|
+
)
|
|
382
|
+
ORDER BY
|
|
383
|
+
'Product'[Color] ASC,
|
|
384
|
+
'Product'[Category] ASC,
|
|
385
|
+
'Sales'[ProductKey] ASC
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Key Concepts**:
|
|
389
|
+
|
|
390
|
+
* SUMMARIZE removes duplicate rows
|
|
391
|
+
* CALCULATETABLE applies year filter
|
|
392
|
+
* Related table columns accessed via relationships
|
|
393
|
+
|
|
394
|
+
### Example 5: SELECTCOLUMNS for Preserving Duplicates
|
|
395
|
+
|
|
396
|
+
**Scenario**: Get color, category, and product key for products sold in 2022, keeping all duplicate rows.
|
|
397
|
+
|
|
398
|
+
```dax
|
|
399
|
+
// Product color, category and key for products sold in 2022
|
|
400
|
+
EVALUATE
|
|
401
|
+
CALCULATETABLE(
|
|
402
|
+
SELECTCOLUMNS(
|
|
403
|
+
'Sales',
|
|
404
|
+
"Color",
|
|
405
|
+
RELATED('Product'[Color]),
|
|
406
|
+
"Category",
|
|
407
|
+
RELATED('Product'[Category]),
|
|
408
|
+
'Sales'[ProductKey]
|
|
409
|
+
),
|
|
410
|
+
'Calendar'[Year] == 2022
|
|
411
|
+
)
|
|
412
|
+
ORDER BY
|
|
413
|
+
[Color] ASC,
|
|
414
|
+
[Category] ASC,
|
|
415
|
+
'Sales'[ProductKey] ASC
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
**Key Concepts**:
|
|
419
|
+
|
|
420
|
+
* SELECTCOLUMNS preserves duplicate rows (unlike SUMMARIZE)
|
|
421
|
+
* Column renaming requires using new names in ORDER BY
|
|
422
|
+
* RELATED accesses columns from related tables
|
|
423
|
+
|
|
424
|
+
### Example 6: Finding Products with No Sales
|
|
425
|
+
|
|
426
|
+
**Scenario**: Identify products that have never been sold.
|
|
427
|
+
|
|
428
|
+
```dax
|
|
429
|
+
DEFINE
|
|
430
|
+
// Sale row count
|
|
431
|
+
MEASURE 'Sales'[Row Count] = COUNTROWS()
|
|
432
|
+
|
|
433
|
+
// Products with no sales
|
|
434
|
+
EVALUATE
|
|
435
|
+
FILTER(
|
|
436
|
+
'Product',
|
|
437
|
+
ISBLANK([Row Count])
|
|
438
|
+
)
|
|
439
|
+
ORDER BY
|
|
440
|
+
'Product'[Name] ASC,
|
|
441
|
+
'Product'[ProductKey] ASC
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**Key Concepts**:
|
|
445
|
+
|
|
446
|
+
* Defines a measure for row counting
|
|
447
|
+
* FILTER with ISBLANK identifies products with no related sales
|
|
448
|
+
* Filter context propagates from Product to Sales table
|
|
449
|
+
|
|
450
|
+
### Example 7: Using Variables to Store Measure Results
|
|
451
|
+
|
|
452
|
+
**Scenario**: Find products with list prices above the median.
|
|
453
|
+
|
|
454
|
+
**Solution 1 - Using CALCULATETABLE**:
|
|
455
|
+
|
|
456
|
+
```dax
|
|
457
|
+
DEFINE
|
|
458
|
+
// Calculate the value of the [Median List Price] measure and store the result in a variable.
|
|
459
|
+
VAR _MedianListPrice = [Median List Price]
|
|
460
|
+
|
|
461
|
+
// Products with list price over the median.
|
|
462
|
+
EVALUATE
|
|
463
|
+
CALCULATETABLE(
|
|
464
|
+
VALUES('Product'[Name]),
|
|
465
|
+
'Product'[List Price] > _MedianListPrice // boolean filter uses variable instead of measure directly
|
|
466
|
+
)
|
|
467
|
+
ORDER BY
|
|
468
|
+
'Product'[Name] ASC
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Solution 2 - Using FILTER**:
|
|
472
|
+
|
|
473
|
+
```dax
|
|
474
|
+
DEFINE
|
|
475
|
+
// Calculate the value of the [Median List Price] measure and store the result in a variable.
|
|
476
|
+
VAR _MedianListPrice = [Median List Price]
|
|
477
|
+
|
|
478
|
+
// Products with list price over the median.
|
|
479
|
+
EVALUATE
|
|
480
|
+
SELECTCOLUMNS(
|
|
481
|
+
FILTER(
|
|
482
|
+
VALUES('Product'),
|
|
483
|
+
'Product'[List Price] > _MedianListPrice // Use variable instead of measure reference to ensure the median list price is calculated across all products
|
|
484
|
+
),
|
|
485
|
+
'Product'[Name]
|
|
486
|
+
)
|
|
487
|
+
ORDER BY
|
|
488
|
+
'Product'[Name] ASC
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**Key Concepts**:
|
|
492
|
+
|
|
493
|
+
* Variables store measure results for use in boolean filters
|
|
494
|
+
* Cannot use measures directly in CALCULATE boolean filters
|
|
495
|
+
* Both approaches yield the same result with different syntax
|
|
496
|
+
|
|
497
|
+
### Example 8: Using Table Variables as Filters
|
|
498
|
+
|
|
499
|
+
**Scenario**: Find the product with highest demand since 2020 and get its sale dates.
|
|
500
|
+
|
|
501
|
+
```dax
|
|
502
|
+
DEFINE
|
|
503
|
+
// To make query more readable, a filter can be defined separately.
|
|
504
|
+
VAR _Filter = FILTER(
|
|
505
|
+
ALL('Calendar'[Year]),
|
|
506
|
+
'Calendar'[Year] >= 2020
|
|
507
|
+
)
|
|
508
|
+
// Get the product with the maximum Total Quantity
|
|
509
|
+
VAR _TopProduct = TOPN(
|
|
510
|
+
1,
|
|
511
|
+
SUMMARIZECOLUMNS(
|
|
512
|
+
'Product'[ProductKey],
|
|
513
|
+
_Filter,
|
|
514
|
+
"Total Quantity", [Total Quantity]
|
|
515
|
+
),
|
|
516
|
+
[Total Quantity],
|
|
517
|
+
DESC
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
// Name and order date for sales of the top product
|
|
521
|
+
EVALUATE
|
|
522
|
+
SELECTCOLUMNS(
|
|
523
|
+
CALCULATETABLE(
|
|
524
|
+
'Sales',
|
|
525
|
+
// Use table-valued variable _TopProduct directly as a filter.
|
|
526
|
+
// No need to extract the 'Product'[ProductKey] first.
|
|
527
|
+
// Calculated column [Total Quantity] has no effect in the filter context.
|
|
528
|
+
_TopProduct
|
|
529
|
+
),
|
|
530
|
+
"Product Name",
|
|
531
|
+
RELATED('Product'[Name]),
|
|
532
|
+
'Sales'[OrderDate]
|
|
533
|
+
)
|
|
534
|
+
ORDER BY
|
|
535
|
+
[Product Name] ASC,
|
|
536
|
+
'Sales'[OrderDate] ASC
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**Key Concepts**:
|
|
540
|
+
|
|
541
|
+
* TOPN identifies the product with highest total quantity
|
|
542
|
+
* Table variables can be used directly as filters in CALCULATETABLE
|
|
543
|
+
* Calculated columns in table variables don't affect filter context
|
|
544
|
+
|
|
545
|
+
### Example 9: Calculating Averages on Filtered Subsets
|
|
546
|
+
|
|
547
|
+
**Scenario**: Calculate the average list price of products sold in 2022.
|
|
548
|
+
|
|
549
|
+
```dax
|
|
550
|
+
DEFINE
|
|
551
|
+
// Distinct products sold in 2022
|
|
552
|
+
VAR _ProductsSold2022 = CALCULATETABLE(
|
|
553
|
+
SUMMARIZE(
|
|
554
|
+
'Sales',
|
|
555
|
+
'Product'[ProductKey]
|
|
556
|
+
),
|
|
557
|
+
'Calendar'[Year] == 2022
|
|
558
|
+
)
|
|
559
|
+
EVALUATE
|
|
560
|
+
ROW(
|
|
561
|
+
"Average List Price of Products Sold in 2022",
|
|
562
|
+
CALCULATE(
|
|
563
|
+
AVERAGE('Product'[List Price]),
|
|
564
|
+
_ProductsSold2022 // Apply table-valued variable as a filter
|
|
565
|
+
)
|
|
566
|
+
)
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
**Key Concepts**:
|
|
570
|
+
|
|
571
|
+
* SUMMARIZE extracts distinct products sold in 2022
|
|
572
|
+
* ROW returns a single-row result
|
|
573
|
+
* Table variable applied as filter in CALCULATE
|
|
574
|
+
|
|
575
|
+
### Example 10: Column Renaming with SELECTCOLUMNS
|
|
576
|
+
|
|
577
|
+
**Scenario**: Get products sold and customers, sorted by renamed column names.
|
|
578
|
+
|
|
579
|
+
```dax
|
|
580
|
+
// Sorted product and customer names for all sales
|
|
581
|
+
DEFINE
|
|
582
|
+
VAR _UniqueProductCustomerPairs = SUMMARIZE(
|
|
583
|
+
'Sales',
|
|
584
|
+
'Product'[Name],
|
|
585
|
+
'Customer'[Name]
|
|
586
|
+
)
|
|
587
|
+
EVALUATE
|
|
588
|
+
SELECTCOLUMNS(
|
|
589
|
+
_UniqueProductCustomerPairs,
|
|
590
|
+
"Product Name", // New name for the 'Product'[Name] column
|
|
591
|
+
'Product'[Name],
|
|
592
|
+
"Customer Name", // New name for the 'Customer'[Name] column
|
|
593
|
+
'Customer'[Name]
|
|
594
|
+
)
|
|
595
|
+
// ORDER BY needs to use the renamed column names
|
|
596
|
+
ORDER BY
|
|
597
|
+
[Product Name] ASC, // Use the new column name assigned by SELECTCOLUMNS instead of the original column name 'Product'[Name]
|
|
598
|
+
[Customer Name] ASC // Use the new column name assigned by SELECTCOLUMNS instead of the original column name 'Customer'[Name]
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
**Key Concepts**:
|
|
602
|
+
|
|
603
|
+
* SELECTCOLUMNS renames columns for clarity
|
|
604
|
+
* ORDER BY must reference the NEW column names, not original names
|
|
605
|
+
* SUMMARIZE removes duplicate product-customer pairs
|
|
606
|
+
|
|
607
|
+
### Example 11: Multiple Filters with TREATAS
|
|
608
|
+
|
|
609
|
+
**Scenario**: For the three oldest customers, show total discounts by year for the last three years.
|
|
610
|
+
|
|
611
|
+
```dax
|
|
612
|
+
DEFINE
|
|
613
|
+
VAR _OldestThreeCustomers = TOPN(
|
|
614
|
+
3,
|
|
615
|
+
'Customer',
|
|
616
|
+
'Customer'[Age],
|
|
617
|
+
DESC
|
|
618
|
+
)
|
|
619
|
+
// Determine the last year based on actual sales dates.
|
|
620
|
+
// Avoid using the last year in the 'Calendar' table, as it may include future dates without sales.
|
|
621
|
+
VAR _LastYear = YEAR(MAX('Sales'[OrderDate]))
|
|
622
|
+
EVALUATE
|
|
623
|
+
SUMMARIZECOLUMNS(
|
|
624
|
+
'Customer'[Name],
|
|
625
|
+
'Calendar'[Year],
|
|
626
|
+
TREATAS({_LastYear, _LastYear - 1, _LastYear - 2}, 'Calendar'[Year]),
|
|
627
|
+
_OldestThreeCustomers, // Apply table-valued variable as filter.
|
|
628
|
+
"Total Discount",
|
|
629
|
+
[Total Discount]
|
|
630
|
+
)
|
|
631
|
+
ORDER BY
|
|
632
|
+
'Customer'[Name] ASC,
|
|
633
|
+
'Calendar'[Year] ASC
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
**Key Concepts**:
|
|
637
|
+
|
|
638
|
+
* TOPN selects top 3 customers by age
|
|
639
|
+
* TREATAS creates filter from calculated year values
|
|
640
|
+
* Determines last year from actual sales data, not calendar table
|
|
641
|
+
|
|
642
|
+
### Example 12: Filtering Aggregated Results
|
|
643
|
+
|
|
644
|
+
**Scenario**: For each customer, show products purchased at least three times with purchase count.
|
|
645
|
+
|
|
646
|
+
```dax
|
|
647
|
+
DEFINE
|
|
648
|
+
MEASURE 'Sales'[Purchase Count] = COUNTROWS()
|
|
649
|
+
VAR _SummaryTable = SUMMARIZECOLUMNS(
|
|
650
|
+
'Customer'[Name],
|
|
651
|
+
'Product'[Name],
|
|
652
|
+
"Purchase Count", [Purchase Count]
|
|
653
|
+
)
|
|
654
|
+
EVALUATE
|
|
655
|
+
FILTER(_SummaryTable, [Purchase Count] >= 3)
|
|
656
|
+
ORDER BY
|
|
657
|
+
'Customer'[Name] ASC,
|
|
658
|
+
'Product'[Name] ASC
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
**Key Concepts**:
|
|
662
|
+
|
|
663
|
+
* Defines measure for counting purchases
|
|
664
|
+
* SUMMARIZECOLUMNS creates summary with measure
|
|
665
|
+
* FILTER applied to summary table variable
|
|
666
|
+
* Simple and efficient two-step pattern
|
|
667
|
+
|
|
668
|
+
### Example 13: Calculated Columns in DEFINE
|
|
669
|
+
|
|
670
|
+
**Scenario**: Categorize products by price (above/below median) and show max/min quantities per category.
|
|
671
|
+
|
|
672
|
+
```dax
|
|
673
|
+
DEFINE
|
|
674
|
+
// define a new column so that it can be used in SUMMARIZECOLUMNS
|
|
675
|
+
COLUMN 'Product'[Price Group] =
|
|
676
|
+
VAR _MedianListPrice = [Median List Price]
|
|
677
|
+
RETURN
|
|
678
|
+
IF(
|
|
679
|
+
'Product'[List Price] > _MedianListPrice,
|
|
680
|
+
"High Priced",
|
|
681
|
+
"Low Priced"
|
|
682
|
+
)
|
|
683
|
+
MEASURE 'Sales'[Max Quantity] = MAX('Sales'[Order Quantity])
|
|
684
|
+
MEASURE 'Sales'[Min Quantity] = MIN('Sales'[Order Quantity])
|
|
685
|
+
EVALUATE
|
|
686
|
+
SUMMARIZECOLUMNS(
|
|
687
|
+
'Product'[Price Group],
|
|
688
|
+
"Max Quantity",
|
|
689
|
+
[Max Quantity],
|
|
690
|
+
"Min Quantity",
|
|
691
|
+
[Min Quantity]
|
|
692
|
+
)
|
|
693
|
+
ORDER BY 'Product'[Price Group] ASC
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
**Key Concepts**:
|
|
697
|
+
|
|
698
|
+
* COLUMN defines a calculated column in DEFINE block
|
|
699
|
+
* Calculated columns can be used as groupby columns in SUMMARIZECOLUMNS
|
|
700
|
+
* Multiple measures defined and used in same query
|
|
701
|
+
* IF expression for conditional logic
|
|
702
|
+
|
|
703
|
+
## Summary
|
|
704
|
+
|
|
705
|
+
This guide covers the essential rules and patterns for writing valid DAX queries. Key takeaways:
|
|
706
|
+
|
|
707
|
+
1. **Always use ORDER BY** when returning multiple rows
|
|
708
|
+
2. **Store measure results in variables** before using in boolean filters
|
|
709
|
+
3. **Choose the right function**: SUMMARIZECOLUMNS for measures, SUMMARIZE for distinct values, GROUPBY for table variables
|
|
710
|
+
4. **Establish date context** for time intelligence functions
|
|
711
|
+
5. **Use renamed columns** in ORDER BY after SELECTCOLUMNS
|
|
712
|
+
6. **Leverage table variables** as filters for cleaner, more maintainable code
|
|
713
|
+
|
|
714
|
+
Practice these patterns to write efficient, readable DAX queries that follow best practices.
|