Applied DAX with Power BI
Applied DAX with Power BI
Applied DAX with Power BI
Published by:
Prologika Press
info@prologika.com
https://prologika.com
Copyright © 2019 Teo Lachev
Made in USA
All rights reserved. No part of this book may be reproduced, stored, or
transmitted in any form or by any means, without the prior written
permission of the publisher. Requests for permission should be sent to
info@prologika.com.
Trademark names may appear in this publication. Rather than use a
trademark symbol with every occurrence of a trademarked name, the
names are used strictly in an editorial manner, with no intention of
trademark infringement. The author has made all endeavors to adhere to
trademark conventions for all companies and products that appear in this
book, however, he does not guarantee the accuracy of this information.
The author has made every effort during the writing of this book to ensure
accuracy of the material. However, this book only expresses the author's
views and opinions. The information contained in this book is provided
without warranty, either express or implied. The author, resellers or
distributors, shall not be held liable for any damages caused or alleged to
be caused either directly or indirectly by this book.
Teo Lachev
Atlanta, GA
about the book
The book doesn't require any prior experience with DAX, but
it assumes that you have experience in Power BI data
modeling. If you don't, I recommend you start with my
"Applied Microsoft Power BI" book, which teaches you how to
create self-service data models. To get the most out of this
book, read and practice the lessons in the order they appear
in the book. That's because each lesson builds upon the
previous ones, to introduce new concepts and reinforce them
with step-by-step exercises.
Part 1, Introduction, starts with the fundamentals. It
introduces you to the DAX origin and main constructs. You'll
learn important data modeling techniques, including star
schemas and relationships. You'll also learn about the Power
BI storage engine and how storage affects DAX.
Part 2, Calculated Columns and Tables, teaches you to
extend your tables with basic and advanced calculated
columns, including columns for looking up, aggregating, and
filtering data. You'll understand how calculated columns are
evaluated and how to change the evaluation context. And
you'll discover how calculated tables can help you implement
role-playing dimensions, date tables, and summarized tables.
Part 3, Measures, explains how measures give you the
needed programmatic power to travel the "last mile" and
unlock the full potential of Power BI. After learning the
measure fundamentals and filter context, it shows you how
to create basic measures. Then, it moves to more advanced
concepts, such as restricting and ignoring the filter context,
as well as grouping and filtering data.
Part 4, Time Intelligence, further expands your knowledge
of measures and teaches you how to implement time
intelligence. It starts by teaching you how to work with built-
in and custom date tables. After revisiting quick measures for
time intelligence, it teaches you how to implement custom
formulas for more advanced requirements, such as custom
date filters and semi-additive measures. You'll learn how to
centralize time intelligence formulas by using calculation
groups.
Part 5, Queries, covers creating custom queries to test
measures outside Power BI Desktop, exploring the model
data, and implementing reports with other tools that require
you to specify a dataset query, such as Power BI Report
Builder. You'll also discover how to identify and address
performance bottlenecks.
Part 6, Advanced DAX, starts by showing you how you can
use DAX to implement different types of joins, including
recursive (parent-child), many-to-many, inner, outer, and
other joins. It explains how to implement row-level security
(RLS) by applying DAX row filters. You'll also learn how to
handle more complicated security policies, such as by
externalizing secured policies in a separate table.
acknowledgements
Welcome to the Applied DAX with Power BI book! Writing
books is difficult and DAX doesn't make it any easier.
Fortunately, I had people who supported me. This book (my
eleventh) would not have been a reality without the help of
many people to whom I'm thankful. As always, I'd like to first
thank my family for their ongoing support. My daughter,
Maya, contributed the most by polishing the manuscript.
Thanks to my technical reviewer John Layden, whom I had
the privilege to work with previously on consulting
engagements, for reviewing the manuscript, and providing
valuable feedback. Thanks to Shay Zamir for another great
cover design.
As a Microsoft Most Valuable Professional (MVP), Gold Partner
(Data Analytics and Data Platform), and Power BI Red Carpet
Partner, I've been privileged to enjoy close relationships with
the Microsoft product groups. It's great to see them working
together! Special thanks to the Power BI and Analysis
Services teams.
Finally, thank you for purchasing this book!
conventions
This book uses different typefaces to differentiate between
code and regular English, and to help you identify important
concepts. Code that you type is presented in this font:
EVALUATE DimSalesTerritory
SQL Server Analysis Services Tabular Optional Implement calculation groups Part 4
2019
Tabular Editor Optional Implement calculation groups Part 4
(https://tabulareditor.github.io/)
You can download the source code for the practices from
the book page at https://prologika.com/daxbook. After
downloading the zip file, extract it to any folder on your hard
drive (I recommend C:\DAX\Source\). Once this is done, you'll
see a folder for each part of the book. In each part folder,
you'll typically find a file for each lesson and the file name
matches the lesson name. This file includes the DAX
formulas if you prefer to copy and paste them.
Start with the Adventure Works.pbix file in the
\Source\Practice folder and keep on extending it as you go
through the lessons. For your convenience, the Adventure
Works.pbix file in each part folder includes the changes you
need to make in the exercises in the corresponding part of
the book, plus any supporting files required for the exercises.
For example, the Adventure Works.pbix file in the
\Source\Part2 folder includes the changes that you'll make
during the Part 2 practices.
(Optional) Installing the AdventureWorksDW database
Extending the Adventure Works model with DAX doesn't
require reimporting the data. However, Lesson 4 shows you
how you can implement custom columns in Power Query,
and this requires reimporting the affected tables. If you
decide to do this exercise, you need to install the
AdventureWorksDW database. This is a Microsoft-provided
database that simulates a data warehouse. You can install
the database on an on-prem SQL Server (local or shared) or
Azure SQL Database. Again, you don't have to do this
(installing a SQL Server alone can be challenging).
NOTE Microsoft ships Adventure Works databases with each version of SQL
Server. More recent versions of the databases have incremental changes and
they might have different data. Although the book exercises were tested with the
AdventureWorksDW2017 database, you can use a later version if you want.
Depending on the database version you install, you might find that reports might
show somewhat different data.
Introduction
If you imagine a layered Power BI model, where the bottom
layer is Power Query (for data shaping and transformation)
and the middle layer is the data model (where your tables
and columns are), then DAX calculations will be the top layer.
Therefore, DAX is dependent on the model schema and data
quality. If you don't get these layers right, you won't be
successful with DAX either. Therefore, the book starts with
important fundamentals.
The first lesson introduces you to DAX, its origin, and main
constructs. In the second lesson, you'll learn important data
modeling techniques, including star schemas and
relationships. Lastly, it's important to have at least a high-
level understanding of the storage engine to better
understand how DAX formulas work.
When going through the exercises, start with the
Adventure Works.pbix file in the \Source\Practice folder. If
you need to refer to the completed exercises and reports for
this part of the book, you'll find them in the Adventure Works
model in the \Source\Part1 folder included in the book source
code.
Lesson 1
Introducing DAX
Although this book teaches you DAX with Power BI, a nice
bonus awaits you ahead because you're also learning how to
program Excel Power Pivot and Analysis Services Tabular. So,
if one day you find yourself working on a self-service model
in Excel or an organizational model powered by Analysis
Services Tabular, you'll find that you already have the
knowledge!
Figure 1.2 Measures are evaluated for each cell, and they
operate in filter context.
This report summarizes the SalesAmount field by countries
on rows and by years on columns. The report is further
filtered to show only sales for the Bikes product category.
The filter context of the highlighted cell is the Germany value
of the DimSalesTerritory[SalesTerritoryCountry] field (on
rows), the 2008 value of the DimDate[CalendarYear] field (on
columns), and the Bikes value of the
DimProduct[ProductCategory] field (used as a filter).
If you're familiar with the SQL language, you can think of
the measure filter context as a WHERE clause that's
determined dynamically and then applied to each cell on the
report. When Power BI calculates the expression for that cell,
it scopes the formula accordingly, such as to sum the sales
amount from the rows in the ResellerSales table where the
SalesTerritoryCountry value is Germany, the CalendarYear
value is 2008, and the ProductCategory value is Bikes.
NOTE Unlike calculated columns, which might be avoided by using other
implementation approaches, measures typically can't be replicated in other ways
– they must be written in DAX. That's because any other approach would produce
static values that don't change as a result of the user filtering data on the report.
For example, you may pre-calculate year-to-date (YTD) sales as of the most
current date, but this will not allow the user to see YTD sales as of a prior date.
Introducing DAX queries
Lastly, you can use DAX to query Power BI, Power Pivot, and
Analysis Services Tabular models. A DAX query is centered
on the DAX EVALUATE statement. For example, this simple
DAX query returns all data from the DimSalesTerritory table
in the Adventure Works Power BI model.
EVALUATE DimSalesTerritory
Once you create the quick measure, it's just like any explicit
DAX measure. You can rename it or use it on your reports.
However, you can't go back to the "Quick measures" window.
To customize the measure, you must make changes directly
to the formula, so you still need to know some DAX.
Output
Let's create a report to test the new measure (or refer to the
Quick Measure report in \Intro\Adventure Works.pbix file).
1.Add a Table visual to the report with the
DimDate[CalendarYear] and FactResellerSales[SalesAmount]
fields in the Values area. To prevent Power BI from
summarizing CalendarYear by default since it's a numeric
field, expand the drop-down next to CalendarYear in the
Values area and select "Don't summarize".
TIP Some numeric fields, such as CalendarYear, CalendarQuarter, shouldn't be
summarized at all as doing so produces non-sensical results. To tell Power BI not
to summarize a numeric field again, select the field in the Fields page, click the
Modeling ribbon, expand the Default Summarization dropdown, and select "Don't
summarize". This removes the sigma (∑) icon in the Fields pane in front of the
field to indicate that the field won't be summarized by default.
The rest of the columns are typical for date tables. Adventure
Works has a fiscal calendar, which explains the
FiscalSemester, FiscalQuarter, and FiscalYear columns. It also
supports multiple languages and it has corresponding
columns that store the language translations. For example,
EnglishMonthName stores the name of the month in English.
There is more to date tables that you need to know but I'll
stop here for now.
The rest of the dimension tables follow the same pattern.
For example, the CustomerKey column in DimCustomer
uniquely identifies each customer. Such columns are called
surrogate keys in dimensional modeling. The "alternate key"
columns, such as CustomerAlternateKey, are called business
keys and they typically correspond to identifiers in the
source systems. For example, the first customer listed, Larry
Gill, is probably identified as AW00011602 in the Adventure
Works ERP system. However, there could be changes to
Larry, such as when he moves to a new address. The source
system might simply overwrite Larry's record and the data
warehouse could follow this pattern (dimensional modeling
refers to overwrites as Type 1 changes). Of course, such
overwrites "lose" historical changes.
But other changes could be important for data analytics
and need to be preserved in the data warehouse. Suppose
you do analysis by cities and Larry moved from New York to
Atlanta. If his address is overwritten, his whole sales history
will be contributed to Atlanta which can inflate the historical
Atlanta sales. If this is problematic, one option is to add a
new row for Larry in DimCustomer that is associated now
with his new geography. Dimensional modeling refers to this
type of change as a Type 2 change. However, because
CustomerAlternateKey is not unique anymore, a system-
generated CustomerKey was introduced as a unique
(surrogate) key.
2.2 Relationship Fundamentals
Once you have multiple tables, you need a way to relate
them. If two tables aren't related, your model won't
aggregate data correctly when you use both tables on a
report. Because relationships are very important to Power BI
data modeling and DAX, let's quickly cover their
fundamentals.
Figure 2.2 The Date column (primary key) in the Date table
is related to the matching OrderDate column (foreign key) in
the ResellerSales table.
Understanding Storage
Our overview of data modeling fundamentals won't be
complete until you learn how Power BI stores data in the
model. It's the storage engine that gives your Power BI
reports excellent performance even with millions of rows. It's
important to have a least a high-level understanding of the
storage engine to better understand how DAX formulas work.
In addition, your model storage design might affect DAX
performance.
This lesson introduces you to xVelocity (also known as
VertiPaq) which powers the three implementations of
semantic layers in the Microsoft BI platform: Power BI, Power
Pivot and Analysis Services Tabular. It will also teach you how
to analyze storage of your data model, such as to
understand what columns take up the most memory.
3.1 Understanding the Storage Engine
When Microsoft started work on Power Pivot, they realized it
needed a new type of storage that would be more suitable
for data analytics. To provide the best storage performance,
Microsoft implemented a proprietary in-memory store called
VertiPaq, which was later rebranded as xVelocity.
Understanding Custom
Columns
Relating Data
If you are a heavy Excel user, you've probably used its
omnipresent VLOOKUP function to look up values from
another cell or to aggregate data in a range. This is a
common task for data modeling too, although you use
relationships and tables as opposed to cells and ranges. This
lesson teaches you how to navigate tables whether physical
relationships exist or not. You'll find the DAX formulas for this
lesson in \Source\Part2\Relating Data.dax.
5.1 Navigating Existing Relationships
Recall from Lesson 2 that relationships are very important to
Power BI data models. They promote self-service data
exploration without requiring you to create queries that join
tables. If a relationship exists between a dimension table and
a fact table, you can slice and dice the fact data by any field
in the dimension table. Calculated columns can benefit from
existing relationships too to let you "look up" or aggregate
values from a related table. DAX has two functions, RELATED
and RELATEDTABLE, for navigating active relationships.
Aggregating Data
One of the most common tasks in data analytics is
aggregating data, such as to summarize the sales for each
product or rank customers based on their overall sales. In
this lesson you'll learn how to aggregate data in calculated
columns. It also revisits the evaluation context in which a
formula is evaluated, and how the context is propagated to
related tables. You'll find the DAX formulas for this lesson in
\Source\Part2\Aggregating Data.dax.
6.1 Aggregating Columns
DAX supports various aggregation functions and Table 6.1
shows the ones that you'd probably use the most. You can
use these functions in both calculated columns (the subject
of this lesson) and measures.
Table 6.1 This table shows the most common aggregation functions in
DAX.
AVERAGE* Returns the average (arithmetic mean) of all the numbers in a AVERAGEX
column
COUNT* Counts the number of values in a column that contain COUNTX
numbers
DISTINCTCOUNT Counts the number of distinct values in a column
MAX* Returns the largest numeric value in a column, or between two MAXX
scalar expressions
MEDIAN* Returns the median of numbers in a column MEDIANX
MIN* Returns the smallest numeric value in a column, or between MINX
two scalar expressions
RANX.EQ* Returns the ranking of a number in a list of numbers RANKX
SUM* Adds all the numbers in a column SUMX
Filtering Data
DAX formulas often need to apply filters to narrow the
context in which the formula operates. This lesson teaches
you different ways to filter data with the CALCULATE and
FILTER functions, and how to remove filters to expand the
evaluation context. You'll find the DAX formulas for this
lesson in \Source\Part2\Filtering Data.dax.
7.1 Adding Filters
DAX has various functions related to filtering data, including
functions to apply and remove filters, and functions to detect
the filter selection (detecting the filter context is useful for
measures only). Table 7.1 shows the most common filter
functions to apply filters.
Table 7.1 This table shows the most common functions for applying filter
conditions.
In the case where there are only two AND (or OR) conditions,
you can use the AND and OR functions, as follows:
2013Sales = CALCULATE(SUM(FactInternetSales[SalesAmount]),
FILTER(ALL(FactInternetSales[OrderDate], FactInternetSales[ShipDate]),
OR(YEAR(FactInternetSales[OrderDate])=2013,
ISBLANK(FactInternetSales[ShipDate]))))
Note the special use of the ALL function to return the unique
combinations between OrderDate and ShipDate. This
reduces the number of rows that the storage engine needs to
scan.
TIP When you need to filter on a few columns, you could make the formula more
efficient by using ALL(column1, column2,..) to get the unique combinations
among the columns. Use DAX Studio to check the count of rows from the
following query: EVALUATE ALL(FactInternetSales[OrderDate],
FactInternetSales[ShipDate])
If you get substantially less rows than the total row count in the fact table, it's
more efficient to use ALL. However, as the number of filtered columns increase,
you'll get closer and closer to the cardinality of the entire table and RELATED or
RELATEDTABLE might be a better choice as it makes the syntax shorter for
calculated columns.
Practice
Change the 2013Sales formula to return sales only for the
Accessories product category. This will require joining
FactInternetSales and DimProduct.
2013Sales = CALCULATE(SUM(FactInternetSales[SalesAmount]),
FILTER(RELATEDTABLE(FactInternetSales),
(YEAR(FactInternetSales[OrderDate])=2013 ||
ISBLANK(FactInternetSales[ShipDate]))
&& RELATED(DimProduct[EnglishProductCategoryName]) =
"Accessories"))
Just like the FILTER version, the formula will scan the
FactInternetSales[OrderDate] column, so it will be executed
1,124 times (the unique values in
FactInternetSales[OrderDate]).
Practice
Change the 2013SalesC formula to include sales where
OrderDate is empty. Then, replace OrderDate with ShipDate
to include sales where ShipDate is empty.
2013SalesC = CALCULATE(SUM(FactInternetSales[SalesAmount]),
YEAR(FactInternetSales[OrderDate])=2013 ||
FactInternetSales[OrderDate]=BLANK() )
2013SalesC = CALCULATE(SUM(FactInternetSales[SalesAmount]),
YEAR(FactInternetSales[OrderDate])=2013 ||
FactInternetSales[ShipDate]=BLANK() )
Analysis
Notice that while the first formula works, the second fails
with the error "The expression contains multiple columns,
but only a single column can be used in a True/False
expression that is used as a table filter expression". The
reason for this error is that unlike the FILTER function, a filter
argument in CALCULATE must reference the same column.
NOTE A single filter condition involving two or more columns doesn't work with
CALCULATE. For OR filters, use the FILTER function instead. For a better
performance, consider FILTER (ALL(column1, column2), <conditions>).
Practice
Extend the 2013SalesC calculated column to include only
sales where product category is accessories.
2013SalesC = CALCULATE(SUM(FactInternetSales[SalesAmount]),
FILTER(ALL(FactInternetSales[OrderDate], FactInternetSales[ShipDate]),
YEAR(FactInternetSales[OrderDate])=2013 ||
ISBLANK(FactInternetSales[ShipDate])),
DimProduct[EnglishProductCategoryName] = "Accessories")
Analysis
Recall that CALULATE can take multiple filter arguments that
act as AND filters. While you can include all filter conditions
in a single FILTER function, you might get a better
performance if you use separate AND filters if you need to
filter on multiple columns. This formula uses the FILTER
function for the OR filter as before. Because the product
category filter is an AND filter (filter on the dates and
category), the formula passes it as another filter argument.
Why is this faster? If you use the FILTER function for all
conditions, the formula will scan all the rows in
FactInternetSales. By breaking it into two filters, the formula
will scan 1,124 rows (the FILTER function with the OR
condition), and 158 rows (the number of unique values in the
FactInternetSales[ProductKey] column).
TIP When you need multiple AND filters on different columns, test performance
with a single FILTER function and multiple filters in CALCULATE. The chances are
that multiple filters will perform better with larger tables.
Practice
Change the 2013SalesC formula to return sales for product
categories Accessories or Sales.
2013SalesC = CALCULATE(SUM(FactInternetSales[SalesAmount]),
FILTER(ALL(FactInternetSales[OrderDate], FactInternetSales[ShipDate]),
YEAR(FactInternetSales[OrderDate])=2013
|| ISBLANK(FactInternetSales[ShipDate])),
DimProduct[EnglishProductCategoryName] IN {"Accessories", "Bikes"})
Analysis
To keep the syntax shorter, use the IN operator to filter on
multiple values. Notice that the IN operator requires curly
braces to surround the values. Text values need to be
surrounded with quotes.
7.2 Removing Filters
Sometimes, your formula may require removing filters. This
is especially useful for measures, such as to calculate
"percent of total" measures, but you might need to remove
filters in calculated columns too.
Filter Description
Function
ALL Ignores all filters and returns all rows in table or column.
ALLEXCEPT Ignores all filters and returns all rows in table or column except specified columns that
retain their filters.
ALLSELECTED Removes context filters from columns and rows in the current query, while retaining all
other context filters or explicit filters.
Creating a group
Follow these steps to implement the group:
1.Create a Stacked Column Chart with
DimSalesTerritory[SalesTerritoryCountry] in the Axis area and
FactResellerSales[SalesAmount] in the Values area.
2.Hold the Ctrl key and click each of the data categories you
want to group. Currently, only charts support this way of
selecting group members. To group elements in tables or
matrices, expand the drop-down next to the field in the
Visualizations pane (or click the ellipses button in the Field
list), and click New Group.
3.Right-click any of the selected countries and click Group
from the context menu. Power BI Desktop adds a new
SalesTerritoryCountry (group) field to the DimSalesTerritory
table. This field represents the custom group and it's prefixed
with a double-square icon. Power BI Desktop adds the field to
the chart's Legend area.
4.In the Fields pane, click the ellipsis (…) button next to
SalesTerritoryCountry (group). Click Rename and change the
field name to European Countries.
Editing a group
To make changes to an existing group:
1.Click the ellipsis (…) button next to the European Countries
field and then click Edit Groups.
2.In the Groups window (see Figure 8.2), you can change
the group name and see the grouped and ungrouped
members. If the "Include Other group" checkbox is checked
(default setting), the rest of the data categories (Canada and
United States) will be grouped into an "Other" group.
Figure 8.2 Use the Groups window to view the grouped
values and make changes.
Implementing Calculated
Tables
Besides calculated columns, Power BI supports calculated
tables. This lesson starts by explaining what a calculated
table is and when to consider it. Then, it walks you through a
few examples for implementing calculated tables to handle
role-playing dimensions, generate date tables, and improve
performance. You'll find the DAX formulas for this lesson in
\Source\Part2\Calculated Tables.dax.
9.1 Understanding Calculated Tables
If you have used Power BI Desktop for a while, you've
probably noticed that it's a versatile tool and there are
usually different options to accomplish a given task. For
example, I've already explained that you have at least three
options to implement a custom column: calculated column,
Power Query, and custom SQL. Custom tables are no
exception.
Measures
A data model is rarely complete without important business
metrics. Power BI promotes rapid personal business
intelligence (BI) for essential data exploration and analysis.
Chances are, however, that in real life you might need to go
beyond just simple aggregations, such as counting and
summing. Business needs might require you to extend your
model with metrics that go beyond summing and counting
fields. This is where DAX measures come in. They give you
the needed programmatic power to travel the "last mile" and
unlock the full potential of Power BI.
This part of the book teaches you how to implement
measures. After introducing you to measures, it shows you
how to create basic measures. Then, it moves to more
advanced concepts, such as restricting and ignoring the filter
context, as well as grouping and filtering data.
You'll find the completed exercises and reports for this part
of the book in the Adventure Works model that is included in
the \Source\Part3 folder.
Lesson 10
Understanding Measures
Besides calculated columns, you can use DAX to define
measures. Unlike calculated columns, which might be
avoided by using other implementation approaches,
measures typically can't be replicated in any other way –
they need to be written in DAX. DAX measures are very
useful because they typically aggregate data, such as to
summarize a SalesAmount column or to calculate a distinct
count of customers with sales.
This lesson will help you understand how DAX measures
work and what types of measures are supported by Power BI.
I'll revisit the filter context because it's very important for
measures. You'll also learn how measures compare to
calculated columns and when to use each. You'll find the DAX
formulas for this lesson in \Source\Part3\Understanding
Measures.dax.
10.1 Understanding DAX Measures
I'll define a DAX measure as a runtime calculation that uses a
DAX formula. The most important word in this definition is
"runtime", which means that Power BI executes the measure
formula when the report runs. Unlike calculated columns,
measures never store their formula results. And this makes
measures much more flexible than calculated columns.
Figure 10.1 Measures are evaluated for each cell, and they
operate in the cell filter context.
This report summarizes the SalesAmount measure by
countries on rows and by years on columns. The report is
further filtered to show only sales for the Bikes product
category. The filter context of the highlighted cell is the
Germany value of the
DimSalesTerritory[SalesTerritoryCountry] field (on rows), the
2008 value of the DimDate[CalendarYear] field (on columns),
and the Bikes value of the DimProduct[ProductCategory] field
(used as a filter).
Although measures are associated with a table, they don't
show in the Data View's data preview pane as calculated
columns do. Instead, they're only accessible in the Fields
pane. When used on reports, measures are typically added
to the Value area of the Visualizations pane.
Relating filter context to SQL WHERE
If you're familiar with the SQL language, you can think of the
DAX filter context as a SQL WHERE clause that limits the
scope of the query. Going back to the report shown in Figure
10.1, when Power BI calculates the expression for that cell, it
scopes the formula accordingly, such as to sum the sales
amount from the rows in the FactResellerSales table where
the SalesTerritoryCountry value is Germany, the
CalendarYear value is 2008, and the ProductCategory value
is Bikes.
NOTE Remember that every DAX measure is evaluated in both row and filter
contexts. Simple measure formulas might not have row context but measures
that use iterators do. For example, as SUMX(<table>, <expression>) iterates
through the rows in the table passed as the first argument, it propagates the row
context to the expression passed as the second argument.
Evaluation Design time (before reports are Run time (when reports run)
run)
Typical context Row context (and sometimes filter Filter context (and row context with iterators,
context) such as SUMX)
Storage Formula results are stored No storage
Performance impact Increase refresh time (for Increase report execution time
imported data)
Alternative Possibly Power Query or custom Usually no alternatives
implementation SQL
(unless DAX formulas are
required)
Typical usage Row-based expressions, lookups Custom aggregation, such as YTD, QTD,
weighted averages
1.Switch to the Report View. From the Fields pane, drag the
CustomerAlternateKey column from the DimCustomer table,
and then drop it in an empty area in the report canvas.
2.Power BI Desktop defaults to a table visualization that
shows all customer identifiers. Switch the visualization type
to "Line and Clustered Column Chart".
3.In the Visualizations pane, drag CustomerAlternateKey from
the Shared Axis area to the Column Values area. Double-click
the field and rename the implicit measure to Count of
Customers.
4.Expand the drop-down in the "Count of Customers" field.
Note that it uses the Count aggregation function, as shown in
Figure 11.2.
Figure 11.2 Text-based implicit measures use the Count
function by default.
5.A product can be sold more than once within a given time
period. If you simply count on the business key, you might
get an inflated count. Instead, you want to count customers
uniquely. Expand the drop-down next to the "Count of
Customers" field in the "Column values" area and change the
aggregation function from Count to Count (Distinct).
6.(Optional) Use the ribbon's Modeling tab to change the
CustomerAlternateKey default summarization to Count
(Distinct) so you don't have to overwrite the aggregation
behavior every time this field is used on a report.
7.With the new visualization selected, check the
DimDate[EnglishMonthName] field in the Fields pane to add
it to the Shared Axis area of the Visualizations pane.
8.If months sort alphabetically on the chart, select the
DimDate[EnglishMonthName] field in the Fields pane. In the
Modeling ribbon, expand the "Sort By Column" dropdown and
select MonthNumberOfYear. This sorts the
DimDate[EnglishMonthName] field by the ordinal number of
the month. If the chart sorting order doesn't change, remove
DimDate[EnglishMonthName] from the chart and add it
again.
Configuring bidirectional filtering
At this point, the chart might be incorrect. Specifically, the
count of customers might not change across months. The
issue is that the aggregation happens over the
FactInternetSales fact table via the DimDate <-
FactInternetSales -> DimCustomer path (notice that the
relationship direction changes). Furthermore, the cardinality
of the DimDate and DimCustomer tables is Many-to-Many
(there could be many customers who purchased something
on the same date, and a repeating customer could buy
multiple times).
1.Switch to the Model View tab. Double-click the
FactInternetSales -> DimCustomer relationship. In the
Advanced Options properties of the relationship, change the
cross-filter direction to Both.
2.Switch to the Report View tab. Note that now the results
vary by month.
3.Drag the FactInternetSales[SalesAmount] field to the Line
Values area of the Visualizations pane. Note that because
SalesAmount is numeric, Power BI Desktop defaults to the
SUM aggregation function.
Analysis
Basic reports may not need explicit measures if the Power BI
standard aggregation functions are enough. The Count of
Customers measure counts distinct customers. Analyzing the
report, you can conclude that seasonality affects sales.
Specifically, the customer base decreases during the
summer. And as the number of customers decreases, so do
sales.
You might think you can shorten the above formula by using
SELECTEDVALUE:
SELECTEDVALUE(FactInternetSales[SalesOrderNumber])
In the case of filtering dates, you can also use the FIRSTDATE
and LASTDATE functions. The following measure achieves
the same result:
BOP = FIRSTDATE (DimDate[Date])
SalesAmount YoY% =
VAR Sales =
SUM ( FactResellerSales[SalesAmount] )
VAR SalesLastYear =
CALCULATE (
SUM ( FactResellerSales[SalesAmount] ),
SAMEPERIODLASTYEAR ( DimDate[Date] )
)
RETURN
IF (
NOT ISBLANK ( Sales ) && NOT ISBLANK ( SalesLastYear ),
DIVIDE ( Sales - SalesLastYear, Sales )
)
Output
To test the measures, add a Table visual with
DimDate[CalendarYear] and the two measures, as shown in
Figure 13.1.
Grouping Data
Sometimes, you might face a requirement that calls for
grouping data. For example, in the lesson "Determining Filter
Context" you've implemented an aggregate-over-aggregate
measure that produces different results across levels in a
date hierarchy. This lesson goes into more detail of how to
group data before you can calculate metrics on the
aggregated results. It also teaches you how to add
expression-based columns when using the grouping
functions. You'll find the DAX formulas for this lesson in
\Source\Part3\Grouping Data.dax.
15.1 Understanding Grouping
Functions
CALCULATE (with possibly FILTER) should help you tackle
most of your measure requirements. Grouping data is
typically required to implement aggregate-over-aggregate
measures, such as a measure that aggregates at a month
level in one way but in a different way at a year level. As a
relatively new language, DAX has had its fair share of
growing pains and this is no more evident than in its
grouping functions. I'll quickly go through these functions
and provide recommendations about their usage. Table
15.1 compares at a glance the three grouping functions that
I'll discuss and lists their main characteristics.
Table 15.1 DAX supports various grouping functions.
ADDCOLUMNS/ Returns a summary table with optional Less restrictions Avoid extended columns
SUMMARIZE extended columns. Retains column in SUMMARIZE (use
values with no data. ADDCOLUMNS)
GROUPBY Creates a summary of the input table Can aggregate Requires an extended "X"
grouped by the specified columns. over function for extended
Excludes column values with no data. extended columns columns
SUMMARIZE Creates a summary table for the Best performance Doesn't always work in
COLUMNS requested totals over a set of groups. modified filter context
Excludes column values with no data.
AvgOrderRevenue (o2) =
AVERAGEX (
CALCULATETABLE (
GROUPBY (
FactResellerSales,
FactResellerSales[SalesOrderNumber],
"OrderTotal", SUMX ( CURRENTGROUP (), FactResellerSales[SalesAmount] )
),
USERELATIONSHIP ( FactResellerSales[ShipDateKey], DimDate[DateKey] )
),
[OrderTotal]
)
Analysis
GROUPBY has the same syntax as SUMMARIZE. The
difference is that you need to use SUMX and
CURRENTGROUP(). Using the profiling techniques you'll learn
in the "Queries" part of the book, you can see that this
version uses only two queries to the storage engines versus
four with ADDCOLUMNS/SUMMARIZE. Therefore, it makes
sense to test GROUPBY and use it if it performs better.
15.3 Summary
Most measure requirements can be met with CALCULATE or
CALCULATETABLE. Use the DAX grouping functions to
aggregate data when you need to produce aggregates over
aggregates. Consider GROUPBY when you can use an "X"
function for the aggregation. Otherwise, stick to
ADDCOLUMNS/SUMMARIZE.
PA RT 4
Time intelligence
One of the most common data analytics tasks is
implementing time calculations, such as year-to-date,
parallel period, previous period, period-over-period
variances, and so on. DAX has about 40 functions for
extending your data models with time calculations, but you
don't need to know them all.
This part of the book teaches you how to implement time
intelligence. Since time intelligence requires a date table, it
starts by teaching you how to work with built-in and custom
date tables. After revisiting quick measures for time
intelligence, it shows you how to implement custom formulas
for more advanced requirements, such as custom date filters
and semi-additive measures. You'll also learn how to
centralize time intelligence formulas by using calculation
groups.
You'll find the completed exercises and reports for this part
of the book in the Adventure Works and Inventory models
that are included in the \Source\Part4 folder.
Lesson 16
Once you have the filtered date table, you can add it as an
argument to CALCULATE or CALCULATETABLE to modify the
measure filter context.
18.2 Implementing Custom Time
Intelligence
Let's put what you've learned about customizing time
intelligence into practice. Analyzing data by weeks is a
common requirement. However, as you'll quickly discover
there are no DAX functions for working with weeks, except
WEEKDAY (returns a number identifying the day of the week)
and WEEKNUM (returns the week number in the year). In the
first practice, you'll add a column to the DimDate table in the
format "W <weekstartdate>". In the second practice, you'll
implement a rolling variance for comparing the revenue in
the last seven days to the revenue in the seven days prior to
that.
Semi-additive Measures
All the measures you've implemented until now aggregate
uniformly across all dimensions, including the Date
dimension. Sometimes, you might encounter semi-additive
measures, such as to handle inventory or account balances.
This lesson explains how additivity affects measures and
shows you how to implement semi-additive measures for
analyzing inventory balances. You'll find the DAX formulas for
this lesson in \Source\Part4\Semi-additive Measures.dax.
19.1 Understanding Measure
Additivity
The most common usage of data analytics is to aggregate
measures across dimensions. When you add an implicit
measure to the report, the measure is aggregated according
to the aggregate function you specify in the Visualizations
pane. The default aggregation is SUM for numeric fields and
Count for text fields. Explicit measures, of course, aggregate
using the formulas you write.
Product A 10 15 25 (15)
Product B 20 25 45 (25)
Total by 30 40 70 (40)
Product
This fact table stores the closing product quantity at the end
of each day. Aggregating the product quantity over the
Product dimension produces the correct total. However,
summing the product quantity over time is meaningless and
wrong. What is really needed is taking the ending balance as
of the requested date (the numbers in bold). For example,
the product quantity for Product A spanning two subsequent
days, March 1st and March 2nd, should show 15.
Understanding semi-additive functions
To support semi-additive measures, Power BI provides
several functions, including FIRSTDATE, FIRSTNONBLANK,
LASTDATE, LASTNONBLANK, OPENINGBALANCEMONTH,
OPENINGBALANCEQUARTER, OPENINGBALANCEYEAR,
CLOSINGINGBALANCEMONTH, CLOSINGBALANCEQUARTER,
and CLOSINGBALANCEYEAR. As the lesson "Determining
Filter Context" demonstrated, more complex requirements
that cannot be addressed by the semi-additive functions
alone, may require more involved formulas, such as to
produce aggregate-over-aggregate results.
Understanding non-additive measures
Lastly, some measures, such as rates and percentages,
shouldn't be aggregated with standard aggregation functions
at all. For example, the ResellerSales[UnitPriceDiscountPct]
stores the discount percent and cannot be meaningfully
aggregated across any dimension. However, a calculated
column can use this measure to compute the net profit,
which can be aggregated. Or, an extended function, such as
SUMX can perform the arithmetic for each order line item
before the result is rolled up.
19.2 Working with Semi-additive
Measures
As a manufacturing company, Adventure Works maintains an
inventory. You're tasked to model inventory balances and to
produce a measure that returns the product quantity at
hand. This will help the Adventure Works management
analyze and forecast inventory levels.
Function Description
Current SELECTEDMEASURE
Simple Average DIVIDE(SELECTEDMEASURE(), COUNTROWS(DimDate))
3-mo Average CALCULATE (AVERAGEX ( VALUES( DimDate[EnglishMonthName] ), [Sales] ),
DATESINPERIOD (DimDate[Date], MAX ( DimDate[Date] ), -3, MONTH ))
Analysis
At the end of this practice the Time Intelligence calculation
group should look like Figure 20.3.
1.Save and deploy your changes. This is where you need the
write connectivity to Power BI so you can apply the changes
directly to the published dataset while waiting for Power BI
Desktop to catch up and provide user interface.
2.(Optional) To support more flexible report layouts, consider
creating separate measures that use the Time Intelligence
calculation group to "flatten" measures, such as:
[Sales YTD] = CALCULATE ([Sales] ), 'Time Intelligence'[Time Measure] = "YTD")
[Sales QTD] = CALCULATE ([Sales] ), 'Time Intelligence'[Time Measure] = "QTD")
Queries
Besides calculated columns and measures, you can use DAX
to query Power BI and Tabular models. In fact, when you
interact with a report, Power BI generates DAX queries and
sends them to the backend Analysis Services Tabular service.
You can create your own DAX queries. This brings several
benefits, such as testing measures outside Power BI Desktop,
exploring the model data, and implementing reports with
other tools that require you to specify a dataset query, such
as Power BI Report Builder.
This part of the book introduces you to DAX queries. You'll
learn how to create and test measures and variables, and
how to identify and address performance bottlenecks. You'll
also implemented a paginated report with Power BI Report
Builder. You'll find the completed exercises for this part of the
source code included in the \Source\Part5 folder.
Lesson 21
Using SSMS
SSMS is the Microsoft premium tool for all tasks related to
SQL Server (not just DAX). SSMS has two features that are
specifically designed to help you work with DAX queries:
DAX Query Editor – Once you connect to a Power BI or
Tabular model, right-click the database and click New
Query -> DAX. To connect to a Power BI Desktop model, in
Object Explorer connect to Analysis Services using the
localhost:port connection string (you can obtain the port
from DAX Studio as the lesson "Understanding storage"
demonstrated). This opens a new query editor, where you
can type and execute your DAX query.
DAX Query Designer – Right-click the database but instead
of New Query, click Browse. This opens the same DAX
Query Designer that is available in Power BI Report
Builder. It can auto-generate DAX queries as you drag and
drop fields. I'll demonstrate this feature in the "Using
Power BI Report Builder" lesson.
Using DAX Studio
DAX Studio is a free community tool, created and maintained
by the community, including prominent Microsoft Most
Valuable Professionals (MVPs). It's specifically designed for
working with DAX and it has features that SSMS doesn't
have, such as analyzing the query performance and
formatting DAX code. Because of this, I recommend you use
DAX Studio.
21.2 Working with Basic Queries
In this practice, I'll introduce you to DAX Studio IDE and how
you can use it for testing custom DAX code. Then, you'll
execute a few sample DAX queries.
Output
The DMV tab lists dynamic management views (DMVs).
Analysis Services provides dynamic management views
(DMVs) to help administrators monitor the health of a server
instance, to diagnose problems, and to tune performance.
You can use SQL-like SELECT statements to query these
views just like you can query a SQL Server relational table.
The views are documented at
https://docs.microsoft.com/sql/analysis-
services/instances/use-dynamic-management-views-dmvs-
to-monitor-analysis-services.
TIP Looking for a quick way to get a list of all measures, their formulas, and other
metadata, such as display folders? Just drag the MDSCHEMA_MEASURES DMV and
drop it in the query. Then, run the resulting SELECT statement.
The right pane is where your DAX queries go. You can have
multiple files open and you can have multiple queries in a
file. If you press the Run button in the ribbon, DAX Studio
runs all the queries in the file loaded in the active tab. Or,
you can select a query and click Run (or F5) to run just this
query. There are three tabs in the bottom of the query pane:
Output – Gives you high-level execution statistics, such as
the count of rows in the query results and the query
execution time. This tab also shows the error text if a
formula generates an error or the query syntax is
incorrect.
Results – By default, the query results are shown in a grid
in the Results tab. But you can use the Output ribbon
button to save the results in a tab-delimited text file if you
prefer.
Query History – Lists previously run queries. You can
double-click a query to load it in the query pane.
Many other features are available in the ribbon, such as
formatting queries, commenting, and analyzing the query
performance.
Analysis
DAX Studio can connect to models hosted in Analysis
Services Tabular or Power BI Desktop. You must have your
file open in Power BI Desktop in order for DAX Studio to
connect to its backend Analysis Services Tabular instance
when querying Power BI Desktop models.
21.2.2 Running DAX Queries
Next, you'll execute a few basic DAX queries in DAX Studio to
get familiar with both the tool and the query syntax.
Practice
You'll start with a barebone DAX query and enhance it. Select
the following query and click F5 to run it.
EVALUATE FactResellerSales
Output
The query will run for a few seconds due to the large number
of rows. Once it's done, the Results tab shows all rows and
columns from the FactResellerSales table.
Analysis
EVALUATE <table> is equivalent to SELECT * FROM <table>
in SQL.
Practice
The EVALUATE clause can use any function that returns a
table, such as the FILTER function. The following query
returns the rows with SalesAmount exceeding 20,000:
EVALUATE FILTER(FactResellerSales, [SalesAmount] > 20000)
EVALUATE
TOPN(502,
SUMMARIZECOLUMNS(
ROLLUPADDISSUBTOTAL(
ROLLUPGROUP('DimDate'[CalendarYear], 'DimDate'[EnglishMonthName],
'DimDate'[MonthNumberOfYear]), "IsGrandTotalRowTotal" ),
__DS0FilterTable,
__DS0FilterTable2,
"SumSalesAmount", CALCULATE(SUM('FactResellerSales'[SalesAmount])),
"SalesAmount__qm__YTD", 'FactResellerSales'[SalesAmount (qm) YTD]
),
[IsGrandTotalRowTotal], 0,
'DimDate'[CalendarYear], 1,
'DimDate'[MonthNumberOfYear], 1,
'DimDate'[EnglishMonthName], 1
)
ORDER BY
[IsGrandTotalRowTotal] DESC,
'DimDate'[CalendarYear],
'DimDate'[MonthNumberOfYear]
Analysis
The query uses the DEFINE clause to declare two table
variables that correspond to the two slicers. Although the
second variable uses a different syntax (TREATAS), the exact
syntax doesn't matter. What matters is that these variables
apply filters (think of the WHERE clause in a SQL SELECT
statement) that return a subset of the data for testing. To
make things simple, forget about TREATAS and use just the
FILTER function.
NOTE Although not required for a simple filter, the KEEPFILTERS function
preserves any previous filters so that both the new and previous filters are
applied. Suppose you want to filter the table T on the column C. The following
expression returns rows from T where [C] = 1 or [C] = 2. Although there is filter
T[C]=3, only the latest (innermost) filter takes effect:
CALCULATETABLE(CALCULATETABLE (T, T[C]=1 || T[C]=2), T[C]=2 || T[C]=3)
Whereas, the next formula returns rows from T where [C]=2. In other words,
KEEPFILTERS returns the intersection of both filters:
CALCULATETABLE(CALCULATETABLE(T, KEEPFILTERS(T[C]=1 || T[C]=2)), T[C]=2 ||
T[C]=3)
Advanced DAX
Now that you know how to create the three DAX constructs
(calculated columns, measures, and queries), you're ready to
tackle more advanced scenarios with DAX. This part of the
book starts by showing you how DAX can help you work with
different types of joins, including recursive (parent-child),
many-to-many, inner, outer, and other joins.
If you need to restrict certain users to a subset of the data,
you need data security. I'll show you how to implement row-
level security (RLS) with DAX. You'll also learn how to handle
more complicated security policies, such as by externalizing
the secured entities in a separate table.
You'll find the completed exercises and reports for this part
of the book in the Adventure Works and Bank models that
are included in the \Source\Part6 folder.
Lesson 25
Recursive Relationships
So far, you've created DAX calculations that work with
regular relationships where a dimension (lookup) table joins
to the fact table directly. In this lesson, you'll learn how to
work with recursive relationships, which DAX doesn't support
natively, but it has functions that are specifically designed
for this relationship type. You'll find the DAX formulas in
\Source\Part6\Recursive Relationships.dax.
25.1 Understanding Recursive
Relationships
A recursive (also known as parent-child) relationship
represent is a hierarchical relationship formed between two
entities with an arbitrary number of levels. Common
examples of parent-child relationships include an employee
hierarchy, where a manager has subordinates who in turn
have subordinates, and an organizational hierarchy, where a
company has divisions, offices, and branches.
Many-to-Many Relationships
Another advanced relationship type that you might
encounter is a many-to-many relationship. This lesson
teaches you how to model many-to-many relationships
declaratively and programmatically. You'll find the DAX
formulas in \Source\Part6\ Many-to-Many Relationships.dax.
and you'll use the \Source\Part6\Bank.pbix model for this
practice.
26.1 Understanding Many-to-Many
Relationships
A many-to-many relationship models a many-to-many
cardinality between two tables. This occurs when a row in
the dimension table relates to many rows in the fact table,
and vice versa. Common real-life examples of many-to-many
relationships are joint bank accounts (one customer can
have multiple accounts and a joint account has multiple
customers) and student course enrollment (a student can
enroll in multiple courses and a course has multiple
students).
Customer AccountNumber
Teo A1
Maya A1
Teo A2
For example, Teo owns two accounts (A1 and A2) and
account A1 is joined by Teo and Maya. So, this table will have
duplicated customers (if the customer owns multiple
accounts) and duplicated account numbers in the case of
joint accounts.
Analysis
Notice that the first argument of GROUPBY is the table on
which the extended column operates. Unlike
SUMMARIZECOLUMNS, GROUPBY is designed to aggregate
data from a single table on the many side of the relationship
by columns from one or more dimension tables on the one
side of the relationship.
Virtual Relationships
Sometimes, you may need to relate tables that don't have a
physical relationship. A "virtual" relationship is a runtime join
that doesn't use an existing active or inactive relationship.
This lesson reviews different ways to implement virtual
relationships. You'll also learn how to implement more
involved joins, such as cross joins and unions. As with the
previous lesson, I recommend you run the sample DAX
queries in \Source\Part6\Virtual Relationships.dax either
using DAX Studio or SQL Server Management Studio (SSMS)
to see the effect of the different join operations.
28.1 Implementing Virtual
Relationships
You can use the functions shown in Table 28.1 to implement
simple lookups and virtual joins when physical relationships
don't exist.
Table 28.1 DAX functions for joining tables without relationships.
Figure 29.3 The report shows only data for United States.
Implementing Dynamic
Security
The row filter in the previous lesson returns a fixed (static)
set of allowed rows. This works well if you have a finite set of
unique permissions. For example, if there are three regions,
you can build three roles. Static filters are simple to
implement and work well when the number of roles is
relatively small. However, suppose you must restrict
managers to view only the sales data of the employees that
are reporting directly or indirectly to them. If static filters
were the only option, you'd have no choice except to set up
a database role for each manager. This might lead to a huge
number of roles and maintenance issues. Therefore, Power BI
supports dynamic data security. You'll find the DAX formulas
for this lesson in \Source\Part6\Implementing Dynamic
Security.dax.
30.1 Understanding Dynamic Data
Security
Dynamic security relies on the identity of the interactive user
to filter data. For example, if I log in to Power BI as
teo.lachev@adventure-works.com, a role can filter the
Employee table to me and my subordinates. Instead of
creating a role per user, you need only a single role with the
following table filter applied to the Employee table:
PATHCONTAINS(DimEmployee[Path],
LOOKUPVALUE(DimEmployee[EmployeeKey], DimEmployee[EmailAddress],
USERPRINCIPALNAME()))
Glossary of Terms
Analysis Services Tabular An instance of SQL Server Analysis Services that's configured
in Tabular mode to host Power BI models and organizational
semantic models.
Analysis Services An instance of SQL Server Analysis Services that's configured
Multidimensional in Multidimensional mode to host Power BI models and
organizational semantic models (OLAP cubes).
Business Intelligence BISM A unifying name that includes both Multidimensional (OLAP)
Semantic Model and Tabular (relational) features of Microsoft SQL Server
Analysis Services.
Calculated column A DAX expression-based column added to a table in the data
model.
Calculated table A table that is produced with a DAX expression.
Composite model A data model with hybrid (import and DirectQuery) storage.
Extraction, transformation, ETL Processes extract from data sources, clean the data, and load
loading the data into a target database, such as data warehouse.
Explicit measure A DAX measure that you create by entering a DAX formula.
Implicit measure A DAX measure that is created automatically when you add a
field to the visual's Values area.
Fact table A table that keeps a historical record of numeric
measurements (facts), such as the
FactResellerSales table in the Adventure Works model.
Filter context Typically used by measures, represents the scope in which the
measure formula is executed.
Key Performance Indicator KPI A key performance indicator (KPI) is a quantifiable measure
that is used to measure the company performance, such as
Profit or Return on Investment (ROI).
M The expression-based language of Power Query
Row-level Security RLS A security mechanism for ensuring restricted access to data.
Semantic model Layered between the data and users, the semantic model
translates database structures into a user-friendly model that
centralizes business calculations and security.
SQL Server Analysis Services SSAS A SQL Server add-on, Analysis Services provides analytical
and data mining services. The Business Intelligence Semantic
Model represents the analytical services.
SQL Server Integration SSIS A SQL Server add-on, Integration Services is a platform for
Services implementing extraction, transformation, and loading (ETL)
processes.
SQL Server Management SSMS A management tool that's bundled with SQL Server that
Studio allows administrators to manage Database Engine, Analysis
Services, Reporting Services and Integration Services
instances.
SQL Server Reporting SSRS A SQL Server add-on, Reporting Services is a server-based
Services reporting platform for the creation, management, and delivery
of standard and ad hoc reports.
Snowflake schema Unlike a star schema, a snowflake schema has some
dimension tables that relate to other dimension tables and
not directly to the fact table.
Star schema A model schema where a fact table is surrounded by
dimension tables and these dimension tables reference
directly the fact table.
Tabular Tabular is the second implementation path in Analysis
Services that lets BI pros implement relational-like (tabular)
semantic models.
Time intelligence Type of analytics to analyze the data by time.
Variable A DAX construct for refactoring certain parts of a formula to
improve readability and performance.
Vertipaq Analyzer A community tool for analyzing the model storage.
xVelocity xVelocity is a columnar data engine that compresses and
stores data in memory.