You are on page 1of 31

Master DAX Measures

Jeffrey Wang
Engineering Manager
Microsoft Power BI
jewang@microsoft.com
About me
• Started my BI career in 2002 soon after the dot-com bubble burst.
• Joined Microsoft in 2004 and stayed in BI engine ever since.
• Currently on the Desktop team in charge of the DAX engine.
• On the committee which invented DAX.
How do users use DAX in Power BI?
• Measures
• Calculated columns
• Calculated tables
• Row level security
Agenda
• Learn how DAX measures work
• Deep dive into filter context
• Make my measures return correct results
• Make my measures run fast
Filter Context
Row Context and Filter Context
• Row Context
• Calculated columns
• RLS
• Iteration functions
• Filter Context
• Power BI visuals
• Excel pivot tables
• Calculate functions
Filter contexts

C1 C2 C3 C4 C5 C6 C3 C4 C1 C5 C7 C1 C8 C9

Filter Context

* Filter columns must be columns in the model


A table scan semijoins with filter tables
• A table scan semijoins with filter tables with common columns
• In DAX, a table reference 'Table' is actually the result of virtual join (left outer
join) operations starting from the root table following all many-to-one
relationships
• DAX expression 'Sales' is a virtual join of 'Sales’ and all its related tables
'Sales'
LOJ 'Product' LOJ 'Product Subcategory' LOJ 'Product Category'
LOJ 'Date'
LOJ 'Customer'

Bidirectional crossfiltering injects implicit
filter expression
• For a slicer on a column from the 'Product' table to filter the
'Customer' table when bidirectional crossfiltering is enabled, DAX
Engine injects a filter right before the 'Customer' table is scanned
that’s equivalent to
CalculateTable('Customer', Summarize('Sales', 'Customer’[CustomerKey]))
Filter contexts apply to leaf nodes
• First filter, then calculate
• All related filters are applied
Calculate
• Multiple filters on the same
column are effectively ...
intersected + Filter1 FilterN

Filter first, calculate next


• In contrast, Filter function
performs post filtering - x
semantically
/

Filters in filter
context apply to
leaf nodes
How Does Filter Context Work?
Calculate(SumX(Filter(FactSales, [SalesQuantity] > 1000), [SalesAmount]), Date[Year] = 2011)
Order of operations
Sales Product
Date Product Key Product Key

Date Date Name

Month Amount

Year Cost

• [Profit Margin] =
(Sum(Sales[Amount]) – Sum(Sales[Cost])) / Sum(Sales[Amount])
• Calculate([Profit Margin], 'Product'[Name] = "P1")
DAX functions that work with
filter context
DAX was inspired by existing programming
languages
• Excel formulas – math and trigonometry, statistical, date/time, text,
logical, information, financial
• SQL – relational algebra operations
• MDX – measures, implicit join
DAX functions from Excel
• +, -, *, /, ^, =, >, <, >=, <=, <>, &, DateValue, Day, EDate, EOMonth, Hour, Minute,
Month, Now, Second, Time, TimeValue, Today, Weekday, WeekNum, Year,
YearFrac, Date, IsBlank, IsError, IsLogical, IsNonText, IsNumber, IsText, IsEven,
IsOdd, And, False, If, IfError, Not, Or, Switch, True, Abs, Ceiling, ISO.Ceiling, Exp,
Fact, Floor, Int, Ln, Log, Log10, Mod, MRound, Pi, Power, Quotient, Rand,
RandBetween, Round, RoundDown, RoundUp, Sign, Sqrt, Trunc, Acos, Acosh, Acot,
Acoth, Asin, Asinh, Atan, Atanh, Combin, Cos, Cosh, Cot, Coth, Degrees, Even, Odd,
Permut, Radians, Sin, Sinh, SqrtPi, Tan, Tanh, Chisq.Dist, Chisq.Dist.Rt, Chisq.Inv,
Chisq.Inv.Rt, Combina, Confidence.Norm, Confidence.T, Expon.Dist, GCD, LCM,
Norm.Dist, Norm.Inv, Norm.S.Dist, Norm.S.Inv, Poisson.Dist, Beta.Dist, Beta.Inv,
T.Dist.2T, T.Dist, T.Dist.Rt, T.Inv.2T, T.Inv, Blank, Concatenate, Exact, Find, Fixed, Left,
Len, Lower, Mid, Replace, Rept, Right, Search, Substitute, Format, Trim, Upper,
Value, Unicode, Unichar, XNPV, XIRR
DAX functions inspired by SQL
• Selection: Filter, IN, ContainsRow
• TopN: TopN, Sample, TopNSkip
• Projection/Rename: SelectColumns, AddColumns
• GroupBy – Distinct, Values, Summarize, GroupBy
• Aggregation functions: IsEmpty, ConcatenateX, Sum, SumX, Average, AverageA, AverageX, Count,
CountRows, CountA, CountAX, CountX, CountBlank, DistinctCount, DistinctCountNoBlank, Max, MaxX, MaxA,
Median, MedianX, Min, MinX, MinA, Percentile.Exc, Percentile.Inc, PercentileX.Exc, PercentileX.Inc, Rank.EQ,
RankX, Stdev.S, StdevX.S, StdevX.P, Stdev.P, Var.S, VarX.S, VarX.P, Var.P, Product, ProductX, Geomean,
GeomeanX, CurrentGroup
• Joins: CrossJoin, Generate, GenerateAll, NaturalLeftOuterJoin, NaturalInnerJoin, FullOuterJoin
• Set functions: Union, Intersect, Except
• Table construction:{}, Row, DataTable, GenerateSeries
• Fetch column value: Related, LookupValue
• Hierarchical functions: Path, PathItem, PathItemReverse, PathLength, PathContains
DAX functions inspired by MDX
• Filter context functions: IsCrossFiltered, HasOneValue, IsFiltered, HasOneFilter,
RemoveFilters, All, AllExcept, AllNoBlankRow, AllSelected, Calculate, CalculateTable,
Filters, Values, SelectedValue, TreatAs, UseRelationship, CrossFilter, KeepFilters,
IsInScope, NonVisual
• BI query functions: SummarizeColumns, Rollup, RollupGroup, RollupAddIsSubtotal,
RollupIsSubtotal, IsSubtotal, Ignore, AddMissingItems
• Time intelligence functions: DateAdd, FirstDate, LastDate, FirstNonBlank, LastNonBlank,
StartOfMonth, StartOfQuarter, StartOfYear, EndOfMonth, EndOfQuarter, EndOfYear,
DatesBetween, DatesInPeriod, ParallelPeriod, PreviousDay, PreviousMonth,
PreviousQuater, PreviousYear, NextDay, NextMonth, NextQuarter, NextYear, DatesMTD,
DatesQTD, DatesYTD, SamePeriodLastYear, TotalMTD, TotalQTD, TotalYTD,
OpeningBalanceMonth, OpeningBalanceQuarter, OpeningBalanceYear,
ClosingBalanceMonth, ClosingBalanceQuarter, ClosingBalanceYear
Convert Row Context to Filter Context
• Calculate/CalculateTable
• Convert all rows in row context into filters in filter context
• Add new filters
• Remove filters
• Replace existing filters
• [measure] := <expression> is actually Calculate(<expression>)
• All rows have already been converted to filters before <expression> is
evaluated
• 'Table'[Column] does not work at the top level of a measure expression
• Filter is an iteration function. It doesn’t change filter contexts.
Common DAX patterns that may
produce unexpected results
Set simple filters
• The following two ways of setting filters are different:
1. Calculate(Sum('Sales'[Sales Amount]), Geography[Country] = "United States")
2. Calculate(Sum('Sales'[Sales Amount]), Filter(Geography, [Country] = "United States"))

• Option 1 is equivalent to
1.1. Calculate(Sum('Sales'[Sales Amount]), Filter(All(Geography[Country]), [Country] = "United States"))

• Expressions affected by surrounding filters:


• CountRows(Geography)
• CountRows(Distinct(Geography[Country]))
• CountRows(Values(Geography[Country]))
• HasOneValue(Geography[Country])
• IsFiltered(Geography[Country])
• Sum('Sales'[Sales Amount]) is equivalent to SumX('Sales', [Sales Amount])

• Expressions not affected by surrounding filters:


• Filter(All(Geography), [Country] = "United States")
• All(Geography[Country])
Use DAX variables to avoid unexpected side
effects
• Using measures on the right-side of iterator functions (Filter, SumX, etc.) may pick up unexpected filters
Current Date = Max('Date'[Date])

YTD Sales =
Calculate(
[Total Sales],
Filter(All('Date'[Date]), Year([Date]) = Year([Current Date]) && [Date] <= [Current Date])
)
• Solution: Use variables to store measure values in the correct context
YTD Sales =
Var vCurrentDate = [Current Date]
Return
Calculate(
[Total Sales],
Filter(All('Date'[Date]), Year([Date]) = Year(vCurrentDate) && [Date] <= vCurrentDate)
)
Time Intelligence Functions
Excel date/time functions vs. DAX time
intelligence functions
• DAX stores dates as sequential numbers representing the number of days starting from 12/30/1899.
• Example: convert number of seconds into DateTime:
• Date(1899, 12, 30) + <number of seconds> / (24 * 60 * 60)
• Time(0, 0, <number of seconds>) has limitations
• Arithmetic operators and Excel functions operate on a single date/time value.
• Extract components from a date/time value
• Construct a date/time value from parts
• Convert a text value to a date/time value
• Shift a single date
• Often used to add calculated columns
• Can be done in M as well
• Don't use DAX in M editor
• DAX time intelligence functions operate on a filtered date column.
• Shift selected continuous dates
• Primarily used in measures
Excel date/time functions vs. DAX time
intelligence functions (Examples)
Scenario Right way Wrong way
7 days ago from today Today() - 7 DateAdd(Today(), -7, DAY)
1 month ago from today EDate(Today(), -1) DateAdd(Today(), -1, MONTH)
the last day of this month EOMonth(Today(), 0) EndOfMonth(Today())
the first day of this month Today() – Day(Today()) + 1 StartOfMonth(Today())

Scenario Use time intelligence functions Use Excel operators/functions


Flexible Inflexible
year over year sales SamePeriodLastYear, flexible Filter(…, [Calendar Year] = Values([Calendar Year])
– 1)

year to date sales TotalYTD, flexible Filter(…, [Date] <= Values([Date]) &&
[Date] >= Date(Year(Values([Date])), 1, 1))
DAX time intelligence function example:
SamePeriodLastYear

Year Quarter Date

2012 1 04/01/2012

X
2012 2 04/02/2012

2012 3 ...
06/30/2012
2012 4
2013 1
2013 2
2013 3
2013 4

Row Context Filter Context

Evaluation Context
DAX time intelligence functions shift
continuous dates
• Common error message
MdxScript(Model) (X, XX) Calculation error in measure 'XXX'[X]: Function
'DATEADD' only works with contiguous date selections.
• Examples
1. Multi-selection of years
2. Bi-directional cross-filtering to the Date table
• Time intelligence functions work on filtered date column.
Hidden date tables and DAX dot notation
• PowerBI automatically creates a hidden data table for each date/time
column
• A uni-direction relationship is created between the visible date/time
column and the hidden date table
• Users can access the columns in a hidden date table using DAX dot
notation through the visible date/time column
• [Order Date].[Date] is equivalent to Related('Hidden Table'[Date])
• [Order Date].[Year] is equivalent to Related('Hidden Table'[Year])
Common DAX patterns that
produce slow queries
DistinctCount with changing filters
• Calculate(DistinctCount('Sales'[CustomerKey]), Except(All('Product'),
'Product’))
• Calculate(DistinctCount('Sales'[CustomerKey]),
Filter(Values('Product'[Standard Cost]), [Standard Cost] > 100))
Avoid dense measures combined with
columns from different dimension tables
• BLANK value is your friend in BI queries.
• Examples of dense measures:
• [measure] = 1
• [measure] = Count('Sales'[ProductKey]) + 0
• [measure] = If ([Total Sales] <> 0, [Gross Margin] / [Total Sales], 0)
• The combination of many columns from different dimension tables
and a dense measure is a leading cause of out-of-memory errors.
Replace multiple instances of the same
measure by a variable
• [YoY growth] = Divide([CY Sales] – [LY Sales], [LY Sales])

You might also like