You are on page 1of 129

EXERCISE BOOK

2019-2020 © SQLBI corp. All rights reserved.


Guide to the exercise book
This document contains the exercises of the Mastering DAX Workshop.

The files for each lab are stored in Power BI files in separate folders.

In each lab, you find the starting point of the lab and the solution.

Always start with the starting point and try to solve the scenario.

Many exercises have a basic part and an advanced part. Before approaching the advanced part,
compare your solution of the basic part with the proposed solution.

• Online video course: at the end of each chapter you find the list of exercises to complete.
• In-person training: the teacher provides the list of exercises and the time to complete them.
02.10 Average sales per customer
BASIC EXERCISE

The initial model contains two measures:


• # Customers counts the number of customers
• Sales Amount computes the sales amount
Compute the Amount/Customer measure that shows the average purchase amount
per customer as follows.

ADVANCED EXERCISE Requires CALCULATE

The Customer table contains a hidden column (Customer Type) that separates
companies from individuals. Purchases made by individuals are much smaller than
purchases made by companies. Therefore, you need to author two measures:
• Amount/Person that computes the average sales, to customers of type
Person
• Amount/Company that computes the average sales, to customers of type
Company
You could look at the number by slicing through the Customer Type column in the
report. However, your task is to build the two measures, so that the user does not
have to include Customer Type in the report. The final output should look like this:

02.10
SOLUTIONS

BASIC SOLUTION

--
-- Average sales per customer
-- Measure in Sales table
--
Amount/Customer := DIVIDE ( [Sales Amount], [# Customers] )

ADVANCED SOLUTION

--
-- Average sales per Person
-- Measure in Sales table
--
Amount/Person :=
CALCULATE (
[Amount/Customer],
Customer[Customer Type] = "Person"
)

--
-- Average sales per Company
-- Measure in Sales table
--
Amount/Company :=
CALCULATE (
[Amount/Customer],
Customer[Customer Type] = "Company"
)

02.10 Average sales per customer


02.20 Average delivery time
BASIC EXERCISE

There are two date columns in the Sales table: Order Date and Delivery Date.
Consider the number of days between Order Date and Delivery Date (zero if they are
the same day) as your delivery time.
You must compute the average delivery time in a measure called Avg Delivery, thus
obtaining the following result.

ADVANCED EXERCISE Requires ALL

Create an Avg All Delivery measure that computes the average delivery time of ALL
the sales; then, categorize each sale transaction by creating a Delivery State
calculated column in the Sales table containing either “Below Average” or “Above
Average”.
You should obtain a report like the one below, which is showing the number of sales
(existing # Sales measure) above or below average, sliced by time and Delivery State.
The card shows the average over all deliveries as a reference.

02.20
SOLUTIONS

BASIC SOLUTION

--
-- Average delivery
-- Measure in Sales table
--
Avg Delivery :=
AVERAGEX (
Sales,
Sales[Delivery Date] - Sales[Order Date]
)

ADVANCED SOLUTION

--
-- Average delivery of all orders
-- Measure in Sales table
--
Avg All Delivery :=
AVERAGEX (
ALL ( Sales ),
Sales[Delivery Date] - Sales[Order Date]
)

--
-- Delivery Category
-- Calculated column in Sales table
--
Delivery State =
IF (
Sales[Delivery Date] - Sales[Order Date] >= [Avg All Delivery],
"Above Average",
"Below Average"
)

02.20 Average delivery time


02.30 Discount categories
BASIC EXERCISE

A discount may sometimes be applied to a sales transaction. Sales[Unit Discount]


represents the discount amount. You need to create a DiscountCategory calculated
column in Sales to categorize the discount percentage of each sale into four different
buckets:
• FULL PRICE if the discount is zero.
• LOW if the discount on the sale is less than or equal to 5%.
• MEDIUM if the discount on the sale is greater than 5% and less than or equal
to 10% .
• HIGH if the discount percentage is higher than 10%.
Please note that the discount category is based on a percentage, whereas
Sales[Unit Discount] contains the amount of the discount. Transforming the amount
into a percentage is part of the challenge.

The final report should be like this:

ADVANCED EXERCISE

The DiscountCategory column obtained in the previous step is not sorted in a


reasonable way; right now, it is just using alphabetical sorting.
The required sort order should be Full Price/Low/Medium/High. Create a
DiscountCategorySort calculated column to implement the Sort by Column feature in
Power BI. Finally, compute the average discount percentage in a measure called Avg
Discount. The solution is not a simple AVERAGE of the discount percentage – the
result must be weighted by the gross amount (using Unit Price instead of Net Price).

The final report must look like this:

02.30
SOLUTIONS

BASIC SOLUTION

-- DiscountCategory
-- Calculated column in Sales table
--
DiscountCategory =
VAR DiscountPct = DIVIDE ( Sales[Unit Discount], Sales[Unit Price] )
RETURN
IF (
DiscountPct = 0,
"FULL PRICE",
IF (
DiscountPct <= 0.05,
"LOW",
IF ( DiscountPct <= 0.1, "MEDIUM", "HIGH" )
)
)

ADVANCED SOLUTION

-- DiscountCategory Sort
-- Calculated column in Sales table
--
DiscountCategory Sort =
VAR DiscountPct = DIVIDE ( Sales[Unit Discount], Sales[Unit Price] )
RETURN
IF (
DiscountPct = 0,
0,
IF (
DiscountPct <= 0.05,
1,
IF ( DiscountPct <= 0.1, 2, 3 )
)
)

-- Avg Discount
-- Measure in Sales table
--
Avg Discount :=
VAR GrossAmount = SUMX ( Sales, Sales[Quantity] * Sales[Unit Price] )
VAR Discount = SUMX ( Sales, Sales[Quantity] * Sales[Unit Discount] )
VAR AvgDiscount = DIVIDE ( Discount, GrossAmount )
RETURN
AvgDiscount

02.30 Discount categories


02.40 Working days
BASIC EXERCISE

You need to create the following calculated columns in the Date table:
• Weekday: the full name of the day of the week.
• WeekdayNumber: the day of the week as a number, where Sunday is the
first day of the week.
• WorkingDay: must contain 1 for working days (Monday-Friday) and 0 for
non-working days.
Sort the Weekday calculated column by using the WeekdayNumber column.
Hide the WeekdayNumber column.
Build a measure named # Days that computes the number of days with at least one
transaction in the Sales table (ignoring Delivery Date). This is the report you want to
obtain:

ADVANCED EXERCISE

Contoso pays its salespeople a bonus of 0.1% of Sales Amount on working days and
0.2% of Sales Amount on non-working days.
Create a Bonus measure and produce a report slicing Bonus by time (your results may
be slightly different due to rounding effects):

02.40
SOLUTIONS

BASIC SOLUTION

--
-- Weekday
-- Calculated column in Date table
--
Weekday = FORMAT ( 'Date'[Date], "dddd" )

--
-- Weekday number
-- Calculated column in Date table
--
Weekday number = WEEKDAY ( 'Date'[Date] )

--
-- WorkingDay
-- Calculated column in Date table
--
WorkingDay =
INT ( NOT 'Date'[WeekdayNumber] IN { 1, 7 } )

ALTERNATIVE SOLUTION

--
-- WorkingDay
-- It is possible to use IF instead of the INT function over
-- the condition. Performance-wise, the solution with INT is better.
--
WorkingDay =
IF ( 'Date'[Weekday number] IN { 1, 7 }, 0, 1 )

--
-- # Days
-- Measure in Sales table
--
# Days := DISTINCTCOUNT ( Sales[Order Date] )

(continued on next page)

02.40 Working days


ADVANCED SOLUTION

--
-- Bonus
-- Measure in Sales table
--
Bonus :=
SUMX (
Sales,
VAR Amt = Sales[Quantity] * Sales[Net Price]
VAR Pct = IF ( RELATED ( 'Date'[WorkingDay] ) = 1, 0.001, 0.002 )
RETURN
Amt * Pct
)

ALTERNATIVE SOLUTION

--
-- Bonus Alt
-- Measure in Sales table
-- Alternative solution producing a slightly different result
-- because the percentage is applied to the displayed total
-- instead of being applied to every transaction:
--
Bonus Alt :=
CALCULATE ( [Sales Amount], 'Date'[WorkingDay] = 1 ) * .001
+
CALCULATE ( [Sales Amount], 'Date'[WorkingDay] = 0 ) * .002

02.40
(this page was intentionally left blank)

02.40 Working days


02.50 Last update of customer
Create a LastUpdate calculated column in the Customer table that contains the date
of the last order made by the customer.
Add a CustomerAge calculated column in the Customer table that computes the age
of the customer at the time of their last order by computing the days between Birth
Date and LastUpdate, dividing the result by 365.25. If the customer has no orders the
LastUpdate column is blank: in that case, also keep a blank in CustomerAge. Keep a
blank in CustomerAge also when the Customer[Birth Date] column is blank.
Create the following report by slicing the Sales Amount measure by the CustomerAge
column – the CustomerAge calculated column must already include the word “years”
at the end of the string in order to obtain a result identical to this (the picture only
shows the first few rows of the result):

02.50
SOLUTIONS

--
-- Last Update
-- Calculated column in Customer table
--
LastUpdate =
MAXX (
RELATEDTABLE ( Sales ),
Sales[Order Date]
)

--
-- Customer Age
-- Calculated column in Customer table
--
CustomerAge =
IF (
ISBLANK( Customer[LastUpdate] ) || ISBLANK ( Customer[Birth Date] ),
BLANK (),
VAR CustomerAgeDays = Customer[LastUpdate] - Customer[Birth Date]
VAR CustomerAgeYears = INT ( CustomerAgeDays / 365.25 )
VAR CustomerAge = CustomerAgeYears & " years"
RETURN CustomerAge
)

02.50 Last update of customer


03.10 Delivery working days
BASIC EXERCISE

Create a DeliveryWorkingDays calculated column in the Sales table that computes


the time to deliver an order expressed in working days.
Hint: for each row in the Sales table, count the number of rows in the Date table
where the Working Day column is equal to “WorkDay” and the Date[Date] column is
included between Sales[Order Date] and Sales[Delivery Date].
Create a Delivery WD measure that computes the average of the
Sales[DeliveryWorkingDays] column.

Produce the following report by slicing Sales Amount and Delivery WD by year and
month.

ADVANCED EXERCISE

Our users need to check how frequently the promise of delivering within 7 days is
fulfilled in every country.
Create a % Within 7 days measure that calculates the percentage of orders delivered
within 7 days. Hint: divide the number of transactions in the Sales table that have
DeliveryWorkingDays less than or equal to 7 by the number of transactions in the
Sales table.

Produce the following report by slicing Sales Amount and % Within 7 days by Country.

03.10
SOLUTIONS

BASIC SOLUTION

--
-- DeliveryWorkingDays
-- Calculated column in Sales table
--
DeliveryWorkingDays =
VAR OrderDate = Sales[Order Date]
VAR DeliveryDate = Sales[Delivery Date]
VAR WorkingDays =
FILTER (
ALL ( 'Date' ),
'Date'[Date] >= OrderDate
&& 'Date'[Date] <= DeliveryDate
&& 'Date'[Working Day] = "WorkDay"
)
VAR CountWorkingDays = COUNTROWS ( WorkingDays )
RETURN CountWorkingDays

--
-- Delivery WD
-- Measure in Sales table
--
Delivery WD := AVERAGE ( Sales[DeliveryWorkingDays] )

ADVANCED SOLUTION

--
-- %InNextWeek
-- Measure in Sales table
--
% Within 7 days :=
VAR OrdersWithin7Days =
COUNTROWS (
FILTER ( Sales, Sales[DeliveryWorkingDays] <= 7 )
)
VAR Orders = COUNTROWS ( Sales )
VAR Result =
DIVIDE (
OrdersWithin7Days,
Orders
)
RETURN
Result

03.10 Delivery working days


03.20 Percentage of sales
BASIC EXERCISE

Create a % Sales measure that shows the percentage of Sales Amount against all the
sales.
Hint: divide the Sales Amount measure by an expression that implement the same
logic as Sales Amount, iterating all the sales.
Produce the following report:

ADVANCED EXERCISE

Create a % Year measure that computes the percentage of Sales Amount against the
Sales Amount total of the selected year. The result should be like the following figure,
where the column total is always 100%.
Hint: try to solve this exercise without using CALCULATE, even though a solution using
CALCULATE would be a best practice in similar scenarios.

03.20
SOLUTIONS

BASIC SOLUTION

--
-- % Sales
-- Measure in Sales table
--
% Sales :=
VAR SalesAmt = [Sales Amount]
VAR AllSales =
SUMX (
ALL ( Sales ),
Sales[Quantity] * Sales[Net Price]
)
VAR Result = DIVIDE ( SalesAmt, AllSales )
RETURN
Result

(continued on next page)

03.20 Percentage of sales


ADVANCED SOLUTION

--
-- % Year
-- Measure in Sales table
--
% Year :=
VAR SalesAmount = [Sales Amount]
VAR TotalSalesTransactions =
FILTER (
ALL ( Sales ),
RELATED ( 'Date'[Year] ) IN VALUES ( 'Date'[Year] )
)
VAR AllSalesAmount =
SUMX (
TotalSalesTransactions,
Sales[Quantity] * Sales[Net Price]
)
VAR Result = DIVIDE ( SalesAmount, AllSalesAmount)
RETURN
Result

ALTERNATIVE SOLUTION

--
-- % Year using Calculate
-- Measure in Sales table
--
-- Be mindful, the measure uses ALL and VALUES and not ALLEXCEPT
-- In this report, the result would be the same, but if you were to
-- browse by the YearMonth column (without the year), then the
-- difference would be evident.
--
-- See
-- https://www.sqlbi.com/articles/using-allexcept-versus-all-and-values/
--
% Year using Calculate :=
VAR SalesAmount = [Sales Amount]
VAR AllSalesAmount =
CALCULATE (
[Sales Amount],
ALL ( Sales ),
VALUES ( 'Date'[Year] )
)
VAR Result = DIVIDE ( SalesAmount, AllSalesAmount)
RETURN
Result

03.20
(this page was intentionally left blank)

03.20 Percentage of sales


03.30 Customers with children
BASIC EXERCISE

Create a # CustWithChildren measure that computes the number of customers with


children – only consider customers where Customer[Total Children] is greater than
zero.

ADVANCED EXERCISE

Create a # CustNoToys measure that computes the number of customers with


children that never bought any product from the “Games and Toys” category. In
other words, only consider the customers that have no rows in the Sales table related
to a product with Product[Category] equal to “Games and Toys”.
Hint: try to solve this exercise without using CALCULATE, even though a solution using
CALCULATE would be a best practice in similar scenarios.

03.30
SOLUTIONS

BASIC SOLUTION

--
-- # CustWithChildren
-- Measure in Sales table
--
# CustWithChildren :=
COUNTROWS (
FILTER (
Customer,
Customer[Total Children] > 0
)
)

(continued on next page)

03.30 Customers with children


ADVANCED SOLUTION

--
-- # CustNoToys
-- Measure in Sales table
--
# CustNoToys :=
VAR CustomerWithChildren =
FILTER (
Customer,
Customer[Total Children] > 0
)
VAR CustomerNoToys =
FILTER (
CustomerWithChildren,
ISEMPTY (
FILTER (
RELATEDTABLE ( Sales ),
RELATED ( 'Product'[Category] ) = "Games and Toys"
)
)
)
VAR Result =
COUNTROWS ( CustomerNoToys )
RETURN
Result

ALTERNATIVE SOLUTION

--
-- # CustNoToys using Calculate
-- Measure in Sales table
--
# CustNoToys using Calculate :=
CALCULATE (
COUNTROWS (
FILTER (
Customer,
CALCULATE ( ISEMPTY ( Sales ) )
)
),
Customer[Total Children] > 0,
'Product'[Category] = "Games and Toys"
)

03.30
(this page was intentionally left blank)

03.30 Customers with children


03.40 Sales of products in the first week
BASIC EXERCISE

Create a new FirstSaleDate calculated column in Product that stores the order date of
the first transaction of a product in the Sales table. Hint: You can obtain this
information by retrieving the minimum value of the Order Date column in the Sales
table considering the rows related to the current row in Product.
Create a new FirstWeekSales calculated column in Product that computes the
amount of sales in the first week after the first sale. Hint: filter the transaction in
Sales for the product where Order Date is between FirstSaleDate and FirstSaleDate+7.

Add the new FirstWeekSales calculated column in the report, checking that the
aggregation is Sum. The final report should look like this:

ADVANCED EXERCISE

Being a calculated column, FirstWeekSales does not work when filtered by attributes
like Year.
Create a FirstWeekSalesMeasure measure that computes the same number as
FirstWeekSales when it is not filtered, but that adjusts its value according to slicers
and filters being applied to the report.

Add a slicer by Year and select CY 2008. The final output should look like this:

03.40
SOLUTIONS

BASIC SOLUTION

--
-- FirstSaleDate
-- Calculated column in Product table
--
FirstSaleDate =
MINX (
RELATEDTABLE ( Sales ),
Sales[Order Date]
)

--
-- FirstWeekSales
-- Calculated column in Product table
--
FirstWeekSales =
SUMX (
FILTER (
RELATEDTABLE ( Sales ),
Sales[Order Date] >= 'Product'[FirstSaleDate] &&
Sales[Order Date] < 'Product'[FirstSaleDate] + 7
),
Sales[Quantity] * Sales[Net Price]
)

ADVANCED SOLUTION

--
-- FirstWeekSalesMeasure
-- Measure in Product table
--
FirstWeekSalesMeasure :=
SUMX (
'Product',
SUMX (
FILTER (
RELATEDTABLE ( Sales ),
Sales[Order Date] >= 'Product'[FirstSaleDate] &&
Sales[Order Date] < 'Product'[FirstSaleDate] + 7
),
Sales[Quantity] * Sales[Net Price]
)
)

03.40 Sales of products in the first week


04.10 Nested iterators
The model contains the Max Discount Sales measure that simulates the
application of the maximum discount of a product (Product[Max Discount]) to all
the corresponding sales. The measure uses two nested iterations: one on Product
and one on Sales.
Modify the Max Discount Sales measure so that it only uses one iteration. This
will improve the execution speed of the measure.

04.10
SOLUTIONS

--
-- Max Discount Sales
-- Measure in Sales table
--
Max Discount Sales :=
SUMX (
Sales,
Sales[Quantity] * Sales[Unit Price]
* ( 1 - RELATED ( 'Product'[Max Discount] ) )
)

04.10 Nested iterators


04.20 Transactions in North America
BASIC EXERCISE

Create a new # Sales Transactions NA calculated column in the Product table that
counts the number of rows in Sales related to customers living in North America. Do
not use CALCULATE in the calculated column.
Hint: The Continent column in the Customer table contains North America, Europe,
and so on. You should only rely on the RELATEDTABLE, RELATED, COUNTROWS, and
FILTER functions.
Create a # Sales NA measure that sums the # Sales Transactions NA calculated
column and produce the following report:

ADVANCED EXERCISE Requires CALCULATE

Remove Brand and apply the Year column from the Date table in the rows of the
report. At this point the report produces bad results, because it is based on a
calculated column in Product. Therefore, the value of # Sales NA does not change
depending on filters on different tables.
Create a new Sales NA (Fixed) measure that dynamically computes the number of
rows in the Sales table related to customers in North America, so that it considers the
Year column in the rows of the visualization. Use CALCULATE in this measure.
The final report should look like the following.

04.20
SOLUTIONS

BASIC SOLUTION

--
-- # Sales Transactions NA
-- Calculated column in Product table
--
# Sales Transactions NA =
COUNTROWS (
FILTER (
RELATEDTABLE ( Sales ),
RELATED ( Customer[Continent] ) = "North America"
)
)

--
-- # Sales NA
-- Measure in Sales table
--
# Sales NA :=
SUM ( 'Product'[# Sales Transactions NA] )

ADVANCED SOLUTION

--
-- Sales NA (Fixed)
-- Measure in Sales table
--
Sales NA (Fixed) :=
CALCULATE (
COUNTROWS ( Sales ),
KEEPFILTERS ( Customer[Continent] = "North America" )
)

04.20 Transactions in North America


05.05 Sales of red & blue products
BASIC EXERCISE

Create a Red/Blue Sales measure that only computes Sales Amount for the Red and
Blue colors.

ADVANCED EXERCISE

Create a RedLitware/BlueContoso measure that computes Sales Amount for the


following combinations of Brand and Color that are also visible in the initial filter
context:
• Red color and Litware brand
• Blue color and Contoso brand
Hint: There are many ways to solve this calculation. The best way requires you to use
some table functions along with CALCULATE, to prepare the correct filter.

05.05
SOLUTIONS

BASIC SOLUTION

--
-- Red/Blue Sales
-- Measure in Sales table
--
Red/Blue Sales :=
CALCULATE (
[Sales Amount],
'Product'[Color] IN { "Red", "Blue" }
)

ADVANCED SOLUTION

--
-- RedLitware/BlueContoso Sales
-- Measure in Sales table
--
RedLitware/BlueContoso :=
CALCULATE (
[Sales Amount],
KEEPFILTERS (
FILTER (
ALL ( 'Product'[Color], 'Product'[Brand] ),
( 'Product'[Color], 'Product'[Brand] ) IN
{
( "Red", "Litware" ),
( "Blue", "Contoso" )
}
)
)
)

05.05 Sales of red & blue products


05.10 Create a parameter table
BASIC EXERCISE

The model includes a Discount table with four rows. Each row contains a Discount
Name and a Discount % to apply different discounts to the Sales Amount measure in
the report.
Create a Discounted Sales measure that computes Sales Amount, reduced by the
discount percentage selected by the user through a slicer. You must obtain the report
below, where Discount Sales changes according to the slicer selection.
Hint: multiply Sales Amount by (1 - Discount%) to obtain the discounted value.

ADVANCED EXERCISE

Create a Price calculated table with a Price column with two strings: “Use Net Price”
and “Use Unit Price”.
The Price table will be an additional parameter table of the model, so that the users
decide if they want to apply the discount to the Sales Amount measure – multiplying
Sales[Quantity] by Sales[Net Price] – or to the original price of the product –
multiplying Sales[Quantity] by Sales[Unit Price] – for each transaction in the Sales
table.
Add the Price[Price] column in the report using a slicer; create a Discounted Sales
(Adv) measure that applies the selected discount to the amount computed using the
price selected in the related slicer. You should obtain the following report.

05.10
SOLUTIONS

BASIC SOLUTION

--
-- Discounted Sales
-- Measure in Sales table
--
Discounted Sales :=
VAR Discount = SELECTEDVALUE ( Discounts[Discount], 0 )
VAR SalesAmount = [Sales Amount]
VAR Result = SalesAmount * ( 1 - Discount )
RETURN
Result

ADVANCED SOLUTION

--
-- Price
-- New calculated table
--
Price =
DATATABLE (
"Price", STRING,
{ { "Use Net Price" }, { "Use Unit Price" } }
)

--
-- Discounted Sales
-- Measure in Sales table
--
Discounted Sales (Adv) :=
VAR Discount = SELECTEDVALUE ( Discounts[Discount], 0 )
VAR PriceToUse = SELECTEDVALUE ( Price[Price], "Use Net Price" )
VAR SalesAmount =
IF (
PriceToUse = "Use Unit Price",
SUMX ( Sales, Sales[Quantity] * Sales[Unit Price] ),
[Sales Amount]
)
VAR Result = SalesAmount * (1 - Discount )
RETURN
Result

05.10 Create a parameter table


05.15 Computing percentages
BASIC EXERCISE

Create a % On All measure that computes the percentage of Sales Amount against
the value of Sales Amount for all the transactions in the Sales table.
Create a % On Cat measure that computes the percentage of Sales Amount against
the value of Sales Amount for all the brands. Because Product[Category] is in the
report, the measure reports the percentage against all brands in the same
Product[Category].
You should obtain the following report.

ADVANCED EXERCISE

Add a slicer to the report using the Product[Category] column.


When you select the two categories, “Audio” and “Cell phones”, the % On All
measure might stop working and thus return incorrect results. If you are not able to
obtain the report below, then modify the % On All measure so that it considers the
selection made on the Category slicer computing correct results:

05.15
SOLUTIONS

BASIC SOLUTION

--
-- % On All
-- Measure in Sales table
--
% On All :=
DIVIDE (
[Sales Amount],
CALCULATE ( [Sales Amount], ALL ( 'Product' ) )
)

--
-- % On Cat
-- Measure in Sales table
--
% On Cat :=
DIVIDE (
[Sales Amount],
CALCULATE (
[Sales Amount],
ALL ( 'Product'[Brand] )
)
)

ADVANCED SOLUTION

--
-- % On All
-- Measure in Sales table
--
% On All :=
DIVIDE (
[Sales Amount],
CALCULATE ( [Sales Amount], ALLSELECTED ( 'Product' ) )
)

05.15 Computing percentages


05.20 Understanding CALCULATE
BASIC EXERCISE

The model contains an Audio Sales measure that computes the sales of the Audio
category by using CALCULATE to force the filter on Product[Category].
The following matrix shows the result of Audio Sales compared with Sales Amount.
Explain the behavior of the Audio Sales measure in the three rows with red arrows:
1. The value is the same as Sales Amount.
2. The value is the total of Audio sales even though the Category in the row is
different.
3. The value is empty.

ADVANCED EXERCISE

Correct the Audio Sales measure so that it always shows the value of Sales Amount
for the Audio category, no matter what is being used in the matrix.
You must obtain the following result:

05.20
SOLUTIONS

BASIC SOLUTION

1. The filter on Product[Subcategory] is still active, therefore the


filter context inside CALCULATE is:

Product[Category] = "Audio"
Product[Subcategory] = "Bluetooth Headphones"

Therefore, the result is the same as Sales Amount.

2. The filter context only contains Product[Category] equals to


Cameras and camcorders, which is being overwritten by CALCULATE.
Therefore, the final filter context is:

Product[Category] = "Audio"

3. The filter on Product[Subcategory] is still active, therefore


CALCULATE modifies the filter context in:

Product[Category] = "Audio"
Product[Subcategory] = "Digital Cameras"

There are no products that belong to two Category at the same time
(Audio and Digital Cameras). For this reason, there are no
products visible through the filter context and the result is
blank.

ADVANCED SOLUTION

--
-- Audio Sales
-- Measure in Sales table
--
Audio Sales :=
CALCULATE (
[Sales Amount],
'Product'[Category] = "Audio",
REMOVEFILTERS ( 'Product' )
)

05.20 Understanding CALCULATE


05.25 Sales of blue products

The model contains a Blue Sales measure that computes sales of blue products
using a SUMX, FILTER and RELATED.
Rewrite the Blue Sales measure using CALCULATE and obtaining the exact result
shown here:

05.25
SOLUTIONS

--
-- Blue Sales
-- Measure in Sales table
--
Blue Sales :=
CALCULATE (
[Sales Amount],
KEEPFILTERS ( 'Product'[Color] = "Blue" )
)

05.25 Sales of blue products


05.30 Percentage of customers
The model contains a Perc measure that computes the percentage of customers
in a selected area against the total number of customers on the same continent.
The measure works fine using the hierarchy Continent/Country/State, as shown in
the first visual of the report – every country represents a percentage of the
continent:

Nevertheless, the second visual in the report shows the same Perc measure sliced
by Country and applies a filter showing only Canada and United States.
The result is different. The same Perc measure shows different numbers for
Canada and the United States compared to the other visuals; furthermore, the
total is not 100%:

You need to first understand why the same Perc measure produces different
results depending on the visualization. Then, fix the Perc measure so that it
computes the correct value, generating the following result in the second visual:

05.30
SOLUTIONS

The denominator uses ALLEXCEPT. ALLEXCEPT is a CALCULATE modifier, which removes all
filters from a table except from the ones specified.
The existing measure uses ALLEXCEPT ( Customer, Customer[Continent] ). Therefore, all the
filters are removed from the Customer table, except for the filter on the Continent column.
The first matrix has an active filter on the Continent column coming from the hierarchy. In the
second matrix, there is no filter on the Continent column - the filter context is only filtering the
Country. Thus, the filter on Country is removed by ALLEXCEPT and there are no active filters on
the Continent column.
Remember that the ALL* family behaves as a REMOVEFILTER. If you need to retain a filter on a
column and this filter might be a cross-filter coming from another column, then ALLEXCEPT is
not the way to go. You need to use a pair of REMOVEFILTERS and VALUES, like in the following
solution.

--
-- Perc
-- Measure in Sales table
--
Perc :=
DIVIDE (
[# Customers],
CALCULATE (
[# Customers],
REMOVEFILTERS ( Customer ),
VALUES ( Customer[Continent] )
)
)

05.30 Percentage of customers


05.35 Correct sales of grey products
The model contains a Grey Sales measure that computes a wrong result under
certain conditions.
The two matrixes in the report show different results for the Fabrikam brand, as
indicated by the red arrow. The one on the left is using the wrong Grey Sales
measure, whereas the one on the right is using the Sales Amount measure
applying a visual filter that filters only Grey for Product[Color].
You need to understand the issue and then fix the Grey Sales measure using
CALCULATE so that the matrix on the left produces the same numbers as the
matrix on the right in the report.

05.35
SOLUTIONS

The Grey Sales measure does not work correctly because it is iterating over a subset of rows of
Sales (the result of FILTER) and then it is relying on context transition to compute the Sales
Amount for each row retrieved by FILTER.
Because Sales contains duplicated rows, this inflates the final result. Context transition should
not be used over tables that might contain duplicated rows.

--
-- Grey Sales
-- Measure in Sales table
--
Grey Sales :=
CALCULATE (
[Sales Amount],
'Product'[Color] = "Grey"
)

05.35 Correct sales of grey products


05.40 Best customers
BASIC EXERCISE

The initial report only displays the # Customers measure. The requirement is to
extend the report showing the number of customers with a Sales Amount value larger
than the average in a # GoodCustomers measure.
Create an AvgSales measure that returns the average of Sales Amount by customer.
Create a # GoodCustomers measure that returns the number of customers whose
Sales Amount is above AvgSales.

You must obtain the following result:

ADVANCED EXERCISE

Create a # GoodCustomers (Year) measure by modifying the # GoodCustomers


definition so that it always considers the yearly average sales, even when the matrix
drills down at the month level.
The # GoodCustomers measure uses the current filter context on time, therefore at
the monthly level it uses the monthly average. Instead, you want to always consider
the yearly average in # GoodCustomers (Year). In other words, the # GoodCustomers
measure uses 577.64 for January, as the average sales; however # GoodCustomers
(Year) needs to use 1,413.92 as the average, although the matrix is showing a single
month.

You must obtain the following result:

05.40
SOLUTIONS

BASIC SOLUTION

--
-- AvgSales
-- Measure in Sales table
--
AvgSales := AVERAGEX ( Customer, [Sales Amount] )

--
-- # GoodCustomers
-- Measure in Sales table
--
# GoodCustomers :=
VAR AverageSales = [AvgSales]
VAR CustomersAboveAverage =
FILTER (
Customer,
[Sales Amount] > AverageSales
)
VAR Result = COUNTROWS ( CustomersAboveAverage )
RETURN
Result

ADVANCED SOLUTION

--
-- # GoodCustomers (Year)
-- Measure in Sales table
--
# GoodCustomers (Year) :=
VAR AverageSales =
CALCULATE (
[AvgSales],
REMOVEFILTERS ( 'Date' ),
VALUES ( 'Date'[Year] )
)
VAR CustomersAboveAverage =
FILTER (
Customer,
[Sales Amount] > AverageSales
)
VAR Result = COUNTROWS ( CustomersAboveAverage )
RETURN
Result

05.40 Best customers


05.45 Customers buying many products
BASIC EXERCISE

Create a # CustMultProds measure that computes the number of customers that


purchased at least two different products in the current time selection.
You must obtain the following result:

ADVANCED EXERCISE

Instead of a fixed value of two products, make the number of products purchased a
parameter that the user can select with a slicer.
Create a Num Products calculated table that you can use as a parameter.
Create a slicer based on Num Products.
Create the # CustSelProds measure that computes the number of customers who
purchased - in the current time selection – at least the number of products selected
in the slicer.
Create a Sales CustSelProds measure that returns the Sales Amount of the customers
filtered with the logic implemented in the # CustSelProds measure.
You must obtain the following result:

05.45
SOLUTIONS

BASIC SOLUTION

--
-- # CustMultProds
-- Measure in Sales table
--
# CustMultProds :=
VAR CustomersWithTwoProducts =
FILTER (
Customer,
CALCULATE (
DISTINCTCOUNT ( Sales[ProductKey] )
) >= 2
)
VAR Result = COUNTROWS ( CustomersWithTwoProducts )
RETURN
Result

(continued on next page)

05.45 Customers buying many products


ADVANCED SOLUTION

--
-- Num of Products
-- New calculated table
--
Num of Products =
DATATABLE (
"Num Products", INTEGER,
{ { 1 }, { 2 }, { 3 }, { 4 }, { 5 }, { 10 }, { 20 } }
)

--
-- # CustSelProds
-- Measure in Sales table
--
# CustSelProds :=
VAR NumProductsSelection =
SELECTEDVALUE ( 'Num of Products'[Num Products], 2 )
VAR CustomersWithSelectedProducts =
FILTER (
Customer,
CALCULATE (
DISTINCTCOUNT ( Sales[ProductKey] )
) >= NumProductsSelection
)
VAR Result = COUNTROWS ( CustomersWithSelectedProducts )
RETURN
Result

--
-- Sales CustSelProds
-- Measure in Sales table
--
Sales CustSelProds :=
VAR NumProductsSelection =
SELECTEDVALUE ( 'Num of Products'[Num Products], 2 )
VAR CustomersWithSelectedProducts =
FILTER (
Customer,
CALCULATE (
DISTINCTCOUNT ( Sales[ProductKey] )
) >= NumProductsSelection
)
VAR Result =
CALCULATE (
[Sales Amount],
CustomersWithSelectedProducts
)
RETURN
Result

05.45
(this page was intentionally left blank)

05.45 Customers buying many products


05.50 Large sales
A customer complains about the Large Sales measure, which seems to compute
an incorrect result if sliced by Sales[Quantity].
The Large Sales measure computes the sales of transactions with a value greater
than or equal to 1,000. The Power BI Desktop file contains a report that shows
the problem: Large Sales is always greater than Sales Amount, which does not
make sense.
Create a Large Sales (Correct) measure that implements the right calculation
considering the Quantity slicer.
You must obtain the following result:

05.50
SOLUTIONS

The issue of the Large Sales measure is that it uses ALL on both Sales[Quantity] and Sales[Net
Price] in order to generate the table used as a filter.
ALL ignores any filter on both columns; therefore, it ignores the filter coming from the slicer
that is filtering Sales[Quantity]. The Sales Amount measure is filtered by the slicer and can
produce a value smaller than Large Sales, which ignores that slicer.
Equivalent solutions based on filtering the Sales table without using KEEPFILTERS are not a good
practice for performance reasons.

--
-- Large Sales (Correct) 1
-- Measure in Sales table
--
Large Sales (Correct) =
CALCULATE (
[Sales Amount],
KEEPFILTERS (
FILTER (
ALL ( Sales[Quantity], Sales[Net Price] ),
Sales[Quantity] * Sales[Net Price] >= 1000
)
)
)

ALTERNATIVE SOLUTION

--
-- Large Sales (Correct)
-- Measure in Sales table
--
-- Alternative solution using variables
--
Large Sales (Correct) =
VAR FilterAmount =
FILTER (
ALL ( Sales[Quantity], Sales[Net Price] ),
Sales[Quantity] * Sales[Net Price] >= 1000
)
VAR Result =
CALCULATE (
[Sales Amount],
KEEPFILTERS ( FilterAmount )
)
RETURN
Result

1
KEEPFILTERS must be inside CALCULATE though you store the result of FILTER in a variable. See the
alternative solution for an example.

05.50 Large sales


05.55 Counting spikes
BASIC EXERCISE

The existing report displays a line chart with monthly Sales Amount, along with a Threshold measure
showing a line representing 75% of the largest monthly value for Sales Amount.
Create a High Months measure that counts the number of times the monthly sales amount has
been greater than the threshold.
The following screenshot shows the expected result with a visual indication of the spikes that should
be counted in the High Months measure.

(continued on next page)

05.55
ADVANCED EXERCISE

The High Months measure counts the number of times that Sales Amount is greater than Threshold
on a monthly basis. In the previous chart, High Months shows 6 because there are multiple
consecutive months with a Sales Amount greater than Threshold.
Create a Spikes measure that counts how many times Sales Amount has crossed the Threshold while
moving upwards. Hint: you might want to take advantage of a Time Intelligence function in order to
retrieve the previous month.
The following screenshot shows the expected result by selecting Litware in the Brand slicer. It
highlights the points that should be considered in the Spikes measure for the current selection.

05.55 Counting spikes


(this page was intentionally left blank – solutions on next page)

05.55
SOLUTIONS

BASIC SOLUTION

--
-- High Months
-- Measure in Sales table
--
High Months :=
VAR Threshold = [Threshold]
VAR MonthsOverThreshold =
FILTER (
ALL ( 'Date'[Year Month] ),
[Sales Amount] > Threshold
)
VAR Result = COUNTROWS ( MonthsOverThreshold )
RETURN
Result

ADVANCED SOLUTION

--
-- Spikes
-- Measure in Sales table
--
Spikes :=
VAR Threshold = [Threshold]
VAR MaxSpikes =
FILTER (
VALUES ( 'Date'[Year Month] ),
VAR SalesCurrentMonth = [Sales Amount]
VAR SalesPreviousMonth =
CALCULATE ( [Sales Amount], PREVIOUSMONTH ( 'Date'[Date] ) )
VAR EnteringThreshold =
SalesCurrentMonth >= Threshold
&& SalesPreviousMonth < Threshold
RETURN EnteringThreshold
)
VAR Result = COUNTROWS ( MaxSpikes )
RETURN
Result

05.55 Counting spikes


07.10 Ranking customers (static)
BASIC EXERCISE

The current report shows the Sales Amount and Margin measures for every
customer.
Create a Ranking calculated column in the Customer table that ranks each customer
based on their Margin. You can consider creating additional calculated columns to
support the Ranking calculation, or you can implement a solution based on a single
expression in Ranking.
You should obtain the following report, where customers are sorted by Sales Amount.
Hint: use “Don’t summarize” when you include the Ranking column in the report.

ADVANCED EXERCISE

Create a Country Ranking calculated column in the Customer table that provides the
ranking between customers of a same country.
You should be able to create the following report by slicing the customers in the
United Kingdom. Hint: use “Don’t summarize” when you include the Country Ranking
column in the report.

07.10
SOLUTIONS

BASIC SOLUTION

--
-- Ranking
-- Calculated column in Customer
--
Ranking = RANKX ( Customer, [Margin] )

ADVANCED SOLUTION

--
-- Country Ranking
-- Calculated column in Customer
--
Country Ranking =
VAR CurrentCountry = Customer[CountryRegion]
VAR CustomersInSameCountry =
FILTER (
Customer,
Customer[CountryRegion] = CurrentCountry
)
RETURN
RANKX (
CustomersInSameCountry,
[Margin]
)

ALTERNATIVE SOLUTION

--
-- Alternative solution using ALLEXCEPT
--
Country Ranking =
RANKX (
ALLEXCEPT ( Customer, Customer[CountryRegion] ),
[Margin]
)

07.10 Ranking customers (static)


07.20 Ranking customers (dynamic)
BASIC EXERCISE

Create a Customer Rank measure that ranks selected customers based on the margin
they represent. The Total should return blank for the Customer Rank measure.
The result should be as follows:

ADVANCED EXERCISE

Compute a Geo Rank measure that computes the ranking of Customer[Continent] or


Customer[Country], depending on the hierarchical level displayed in the report. The
Total should return blank for the Geo Rank measure.
You should obtain the following result:

07.20
SOLUTIONS

BASIC SOLUTION

--
-- Customer Rank
-- Measure in Sales
--
Customer Rank :=
IF (
ISINSCOPE ( Customer[Name] ),
RANKX (
ALLSELECTED ( Customer ),
[Margin]
)
)

ADVANCED SOLUTION

--
-- Ranking
-- Measure in Sales
--
Geo Rank :=
IF (
ISINSCOPE ( Customer[CountryRegion] ),
RANKX ( ALLSELECTED ( Customer[CountryRegion] ), [Sales Amount] ),
IF (
ISINSCOPE ( Customer[Continent] ),
RANKX ( ALLSELECTED ( Customer[Continent] ), [Sales Amount] )
)
)

07.20 Ranking customers (dynamic)


07.30 Date with the highest sales
The model has a Max Daily measure that computes the maximum sales per day in
the selection.
Create a Date of Max measure that returns the date (or dates) that generated the
value reported by Max Daily. Hint: there may be multiple dates with the same
amount. In that case, show all of them separated by a comma and formatted as
mm/dd/yy.
The result should be as follows:

07.30
SOLUTIONS

--
-- Date of Max
-- Measure in Sales
--
Date of Max :=
VAR MaxSales = [Max Daily]
VAR DatesWithMax =
FILTER (
'Date',
[Sales Amount] = MaxSales
)
VAR Result =
CONCATENATEX (
DatesWithMax,
FORMAT ( 'Date'[Date], "mm/dd/yy" ),
", "
)
RETURN
IF (
NOT ISBLANK ( MaxSales ),
Result
)

07.30 Date with the highest sales


07.40 Moving average
The value of Sales Amount shows a very strong variance over time at the day
level. The line chart by day is very hard to read, with a lot of spikes and drops.
Create an AvgSales30 measure that computes the moving average over 30 days –
which is the average of the last 30 days for each day in the chart.
By applying the AvgSales30 measure to the line chart, you should obtain the
following result for CY 2009:

07.40
SOLUTIONS

--
-- AvgSales30
-- Measure in Sales
--
AvgSales30 :=
VAR LastVisibleDate = MAX ( 'Date'[Date] )
VAR NumberOfDays = 30
VAR DaysMovingAverage =
FILTER (
ALL ( 'Date'[Date] ),
'Date'[Date] > LastVisibleDate - NumberOfDays &&
'Date'[Date] <= LastVisibleDate
)
RETURN
AVERAGEX ( DaysMovingAverage, [Sales Amount] )

07.40 Moving average


08.10 Running total

Create a Running Total measure that computes the running total of Sales
Amount, aggregating the value of Sales Amount from the beginning of time up to
the last visible date in the current filter context.
The result should be as follows:

08.10
SOLUTIONS

--
-- Running Total
-- Measure in Sales
--
Running Total :=
VAR LastVisibleDate = MAX ( 'Date'[Date] )
VAR Result =
CALCULATE (
[Sales Amount],
'Date'[Date] <= LastVisibleDate
)
RETURN
Result

08.10 Running total


08.20 Comparison YOY%
BASIC EXERCISE

You need to create a report that compares Sales Amount in the current time period
with the same time period in the previous year, showing the difference as a
percentage.
Create a Sales PY measure that computes Sales Amount in the same period the year
before.

Create a YOY % measure that computes the difference between Sales PY and Sales
Amount as a percentage, showing blank whenever one of these two measures is
blank. The result should be as follows:

ADVANCED EXERCISE

The YOY % measure does not consider whether there are months with sales in one
year but not in another year. For example, January 2008 has a value for Sales
Amount, but in January 2007 the Sales Amount value is blank. Same happens in
December: there are sales in December 2007, but not in December 2008. The
requirement is to only compute the difference for the months where there are sales
in both years, also applying this logic to the calculation at the year level.
Create a YOY C% measure that only aggregates those months that have sales in the
year reported and in the previous year. Hint: you can use the Date[Month] or
Date[Month Number] columns to retrieve the month.

The result should be as follows:

08.20
SOLUTIONS

BASIC SOLUTION

--
-- Sales PY
-- Measure in Sales
--
Sales PY :=
CALCULATE (
[Sales Amount],
SAMEPERIODLASTYEAR ( 'Date'[Date] )
)

--
-- YOY%
-- Measure in Sales
--
YOY % :=
VAR CurrentSales = [Sales Amount]
VAR PreviousSales = [Sales PY]
VAR DeltaSales = CurrentSales - PreviousSales
VAR Result =
IF (
NOT ISBLANK ( CurrentSales ),
DIVIDE ( DeltaSales, PreviousSales )
)
RETURN
Result

(continued on next page)

08.20 Comparison YOY%


ADVANCED SOLUTION

--
-- YOY C%
-- Measure in Sales
--
YOY C% :=
VAR MonthsWithSales =
FILTER (
VALUES ( 'Date'[Month Number] ),
[Sales Amount] > 0 && [Sales PY] > 0
)
VAR CurrentSales = CALCULATE ( [Sales Amount], MonthsWithSales )
VAR PreviousSales = CALCULATE ( [Sales PY], MonthsWithSales )
VAR DeltaSales = CurrentSales - PreviousSales
VAR Result =
IF (
NOT ISBLANK ( CurrentSales ),
DIVIDE ( DeltaSales, PreviousSales )
)
RETURN
Result

08.20
(this page was intentionally left blank)

08.20 Comparison YOY%


08.30 Sales in first three months
BASIC EXERCISE

You are required to create a report showing the sales of each product over the first 3
months of sales.
Create a FirstSaleDate calculated column in the Product table computing the first
transaction date (from Sales[Order Date]) for each product.
Create a Sales 3M measure that computes Sales Amount in the first three months
starting from the FirstSaleDate of each product. Hints: the first three months can be
computed by using the DATESINPERIOD function, and the range might be different for
each product, even though the granularity can be optimized.

The result should be as follows:

ADVANCED EXERCISE Requires GENERATE

If the Sales 3M measure defined in the previous step is based on a SUMX iterator,
consider writing an optimized version that applies a filter in a single CALCULATE
expression – obtaining the desired result and removing the need for a SUMX with
multiple context transitions.
Create a Sales 3MO measure that produces the same result as the Sales 3M measure
by preparing a filter for a single CALCULATE execution without any context transition.
Hint: remove the iteration over the Product[FirstSale] column and consider using
GENERATE to prepare the filter.

The result should be as follows:

08.30
SOLUTIONS

BASIC SOLUTION

--
-- FirstSale
-- Calculated column in Product
--
FirstSaleDate = CALCULATE ( MIN ( Sales[Order Date] ) )

--
-- Sales 3M
-- Measure in Sales
--
Sales 3M :=
SUMX (
VALUES ( 'Product'[FirstSaleDate] ),
VAR Dates3Months =
DATESINPERIOD (
'Date'[Date],
'Product'[FirstSaleDate],
+3,
MONTH
)
VAR Sales3Months = CALCULATE ( [Sales Amount], Dates3Months )
RETURN
Sales3Months
)

(continued on next page)

08.30 Sales in first three months


ADVANCED SOLUTION

--
-- Sales 3MO
-- Measure in Sales
--
Sales 3MO :=
VAR FilterProdDates3Months =
GENERATE (
VALUES ( 'Product'[FirstSaleDate] ),
DATESINPERIOD (
'Date'[Date],
'Product'[FirstSaleDate],
+3,
MONTH
)
)
VAR Sales3M =
CALCULATE (
[Sales Amount],
FilterProdDates3Months
)
RETURN
Sales3M

08.30
(this page was intentionally left blank)

08.30 Sales in first three months


08.40 Semi-additive calculations
The Sum of Balance measure calculates the sum of the Balance[Balance] column.
However, that calculation cannot be additive over time. The requirement is to
create different measures that manage the semi-additive nature of this
calculation.
Create a Last Bal measure that computes the balance using the last date of the
current selection.
Create a Last Bal NB measure that computes the balance using the last date for
which there are transactions in the Balances table. Hint: there may be different
solutions to this problem.
Create a Last Bal NB Cust measure that computes the balance on the last date for
which there are transactions on a customer-by-customer basis. Hints: customers
are identified by the content of the Balances[Name] column and you can reuse
the Last Bal NB calculation for each customer.
The result should be as follows:

08.40
SOLUTIONS

--
-- Last Bal
-- Measure in Balances
--
Last Bal :=
CALCULATE (
[Sum of Balance],
LASTDATE ( 'Date'[Date] )
)

--
-- Last Bal NB
-- Measure in Balances
--
Last Bal NB :=
VAR LastDateWithBalances = MAX ( Balances[Date] )
VAR Result =
CALCULATE (
[Sum of Balance],
'Date'[Date] = LastDateWithBalances
)
RETURN
Result

ALTERNATIVE SOLUTION

--
-- Alternative solution using LASTNONBLANK (less efficient)
--
Last Bal NB :=
CALCULATE (
SUM ( Balances[Balance] ),
LASTNONBLANK (
'Date'[Date],
[Sum of Balance]
)
)

--
-- Last Bal NB Cust
-- Measure in Balances
--
Last Bal NB Cust :=
SUMX (
VALUES ( Balances[Name] ),
[Last Bal NB]
)

08.40 Semi-additive calculations


09.10 Time calculations
BASIC EXERCISE

The initial model contains two measures:


• # Sales counts the number of sales
• Sales Amount computes the sales amount
Create a calculation group named Time calc with four calculation items:
• Value returns the current measure, with no calculations
• PY computes the value of the selected measure for the same period in the
previous year
• YOY computes the difference between the current value and the value in
the same period the year before
• YOY% computes the growth, in percentage, between the current value
and the value in the same period the year before
OPTIONAL: use the Ordinal property to sort the calculation items in the report.

09.10
SOLUTIONS

BASIC SOLUTION

Calculation Group Time calc

Calculation Item Value

DAX Expression
SELECTEDMEASURE ()

Calculation Group Time calc

Calculation Item PY

DAX Expression
CALCULATE (
SELECTEDMEASURE (),
SAMEPERIODLASTYEAR ( 'Date'[Date] )
)

Calculation Group Time calc

Calculation Item YOY

DAX Expression
VAR CY =
SELECTEDMEASURE ()
VAR PY =
CALCULATE (
SELECTEDMEASURE (),
SAMEPERIODLASTYEAR ( 'Date'[Date] )
)
VAR Result = CY - PY
RETURN
Result

09.10 Time calculations


Calculation Group Time calc

Calculation Item YOY%

Format string "00.0%"

DAX Expression
VAR CY =
SELECTEDMEASURE ()
VAR PY =
CALCULATE (
SELECTEDMEASURE (),
SAMEPERIODLASTYEAR ( 'Date'[Date] )
)
VAR Result =
DIVIDE ( CY - PY, PY )
RETURN
Result ()

OPTIONAL: To control the sort order of the calculation items in the


report you can assign the Ordinal property of each calculation item.
By using Tabular Editor, the property is set automatically by dragging
and dropping calculation items.

09.10
09.10 Time calculations
09.20 Multiple calculation groups
BASIC EXERCISE

The model contains two complete calculation groups:


• The Time calc calculation group computes either the current value or the
year-to-date (YTD) of the selected measure.
• The Statistics calculation group computes monthly statistics (min, max,
average by month).
You must obtain this result, where YTD shows the maximum of the monthly measure
since the beginning of the year.

Instead, what you obtain in the starting model is totally unexpected.

Your task is to find and fix the problem, and then obtain the expected result.

09.20
SOLUTIONS

BASIC SOLUTION

Calculation Group Statistics

Calculation Group Precedence 0

Calculation Group Time calc

Calculation Group Precedence 10

The problem is the precedence of the calculation groups.


In the starting model, the precedence of Time calc is lower than that of
Statistics. Therefore, Statistics is applied earlier resulting in the
wrong calculation.
Look at the following expression:
CALCULATE (
[Sales Amount],
Statistics[Statistic] = "YTD",
'Time calc'[Time calc] = "Max"
)
It is transformed into this:
MAXX (
VALUES ( 'Date'[Year Month Number] ),
CALCULATE (
[Sales Amount],
DATESYTD ( 'Date'[Date] )
)
)
Whereas we want it to be transformed into that:
CALCULATE (
MAXX (
VALUES ( 'Date'[Year Month Number] ),
[Sales Amount]
),
DATESYTD ( 'Date'[Date] )
)
This is the reason why you need to use a lower precedence for Statistics,
so that it is applied later than Time calc.

09.20 Multiple calculation groups


09.30 Sold versus delivered
BASIC EXERCISE

The initial model contains several measures and a calculation group to choose time
intelligence calculations.
You need to create a new calculation group that changes all the calculations so that
they use the delivery date instead of the order date for the time intelligence
calculations.
The model already contains two relationships between Sales and Date, with the one
between Delivery Date and Date being inactive. You need to let the user choose
which relationship to use through a slicer.

09.30
SOLUTIONS

BASIC SOLUTION

Calculation Group Date to use

Calculation Item Order date

DAX Expression
SELECTEDMEASURE ()

Calculation Group Date to use

Calculation Item Delivery date

DAX Expression
CALCULATE (
SELECTEDMEASURE (),
USERELATIONSHIP ( Sales[Delivery Date], 'Date'[Date] )
)

09.30 Sold versus delivered


09.40 Min, Max and Avg calculation group
BASIC EXERCISE

You need to create a calculation group that shows monthly statistics about any
measure. You need to define four calculation items:
• Value: shows the value of the selected measure
• Min: shows the minimum monthly value of the selected measure
• Max: shows the maximum monthly value of the selected measure
• Avg: shows the monthly average of the selected measure
The report must look like the one below, when used with the Sales Amount measure.

09.40
SOLUTIONS

BASIC SOLUTION

Calculation Group Statistics

Calculation Item Value

DAX Expression
SELECTEDMEASURE ()

Calculation Group Statistics

Calculation Item Min

DAX Expression
MINX (
VALUES ( 'Date'[Year Month Number] ),
SELECTEDMEASURE ()
)

Calculation Group Statistics

Calculation Item Max

DAX Expression
MAXX (
VALUES ( 'Date'[Year Month Number] ),
SELECTEDMEASURE ()
)

Calculation Group Statistics

Calculation Item Avg

DAX Expression
AVERAGEX (
VALUES ( 'Date'[Year Month Number] ),
SELECTEDMEASURE ()
)

09.40 Min, Max and Avg calculation


group
09.50 Top and bottom products
BASIC EXERCISE

You need to categorize products in three buckets: top 10, middle and bottom 10. The
categorization must work with any measure the user chooses in the report.
Create a calculation group named TopBottom with three calculation items:
• Top 10 products: shows the value of the selected measure for only the top
10 products
• Bottom 10 products: same as Top 10 products, but related to the bottom
10 products
• Middle products: shows the value of the selected measure for all the
products that are neither in the top 10 nor in the bottom 10
When used in a matrix with Sales Amount as the selected measure, the report
shows the three rows (note that the Category selection affects the result).

Hint: you need to use the correct order for the calculation item to obtain the
same result (Top 10, Middle, Bottom 10).

09.50
SOLUTIONS

BASIC SOLUTION

Calculation Group TopBottom

Calculation Item Top 10 products

DAX Expression
VAR NumOfProducts = 10
VAR ProdsToConsider =
CALCULATETABLE (
VALUES ( Sales[ProductKey] ),
ALLSELECTED ()
)
VAR Top10Products =
CALCULATETABLE (
TOPN (
NumOfProducts,
ProdsToConsider,
SELECTEDMEASURE (), DESC
),
REMOVEFILTERS ( 'Product' )
)
VAR Result =
CALCULATE (
SELECTEDMEASURE (),
KEEPFILTERS ( Top10Products )
)
RETURN
Result

09.50 Top and bottom products


Calculation Group TopBottom

Calculation Item Bottom 10 products

DAX Expression
VAR NumOfProducts = 10
VAR ProdsToConsider =
CALCULATETABLE (
VALUES ( Sales[ProductKey] ),
ALLSELECTED ()
)
VAR Bottom10Products =
CALCULATETABLE (
TOPN (
NumOfProducts,
ProdsToConsider,
SELECTEDMEASURE (), ASC
),
REMOVEFILTERS ( 'Product' )
)
VAR Result =
CALCULATE (
SELECTEDMEASURE (),
KEEPFILTERS ( Bottom10Products )
)
RETURN
Result

09.50
Calculation Group TopBottom

Calculation Item Middle products

DAX Expression
VAR NumOfProducts = 10
VAR ProdsToConsider =
CALCULATETABLE (
VALUES ( Sales[ProductKey] ),
ALLSELECTED ()
)
VAR Bottom10Products =
CALCULATETABLE (
TOPN (
NumOfProducts,
ProdsToConsider,
SELECTEDMEASURE (),
ASC
),
REMOVEFILTERS ( 'Product' )
)
VAR Top10Products =
CALCULATETABLE (
TOPN (
NumOfProducts,
ProdsToConsider,
SELECTEDMEASURE (),
DESC
),
REMOVEFILTERS ( 'Product' )
)
VAR ProdsToIgnore =
UNION ( Top10Products, Bottom10Products )
VAR MiddleProducts =
EXCEPT ( ProdsToConsider, ProdsToIgnore )
VAR Result =
CALCULATE (
SELECTEDMEASURE (),
KEEPFILTERS ( MiddleProducts )
)
RETURN
Result

09.50 Top and bottom products


13.10 Sales by year
BASIC EXERCISE

Open the Contoso.pbix Power BI Desktop file, then connect DAX Studio to it.
Using DAX Studio, write a query that retrieves Sales Amount by Date[Year], only
listing years with sales (there are no sales in 2005, 2006, 2010 and 2011). Hint: you
can use SUMMARIZECOLUMNS.
The result should be as follows, using Year and Sales for the column names:

Then, add a Pct column to the query, representing the percentage of the Sales
column against the total of Sales Amount for all the years (which is not displayed).
You should obtain the following result:

ADVANCED EXERCISE

Add a Position column to the query, computing the ranking of the year by Sales
Amount.
The result should be as follows:

13.10
SOLUTIONS

BASIC SOLUTION

--
-- First query
--
EVALUATE
SUMMARIZECOLUMNS (
'Date'[Year],
"Sales", [Sales Amount]
)
ORDER BY 'Date'[Year]

--
-- Second query
--
EVALUATE
SUMMARIZECOLUMNS (
'Date'[Year],
"Sales", [Sales Amount],
"Pct", DIVIDE (
[Sales Amount],
CALCULATE (
[Sales Amount],
ALL ( 'Date'[Year] )
)
)
)
ORDER BY 'Date'[Year]

(continued on next page)

13.10 Sales by year


ADVANCED SOLUTION

EVALUATE
SUMMARIZECOLUMNS (
'Date'[Year],
"Sales", [Sales Amount],
"Pct", DIVIDE (
[Sales Amount],
CALCULATE (
[Sales Amount],
ALL ( 'Date'[Year] )
)
),
"Position", IF (
NOT ISBLANK ( [Sales amount] ),
RANKX (
ALLSELECTED ( 'Date'[Year] ),
[Sales Amount]
)
)
)
ORDER BY 'Date'[Year]

13.10
(this page was intentionally left blank)

13.10 Sales by year


13.20 Filtering and grouping sales
BASIC EXERCISE

Open the Contoso.pbix Power BI Desktop file, then connect DAX Studio to it.
Using DAX Studio, write a query that retrieves Sales Amount by Product[Color] and
Customer[Education], strictly for the products that have Product[Color] equal to Red
or Blue and Product[Brand] equal to Contoso or Fabrikam.
The result should be as follows using Color, Education, and Sales as column names:

ADVANCED EXERCISE Requires TREATAS

Compute the average value of Sales Amount by Product[Color] and


Customer[Education] still satisfying the filtering condition - Product[Color] must be
Red or Blue, and Product[Brand] must be Contoso or Fabrikam.
Write a query like the previous one that only shows the pairs of Product[Color] and
Customer[Education] where Sales Amount is strictly greater than the average
computed in the previous step.
The result should be as follows:

13.20
SOLUTIONS

BASIC SOLUTION

EVALUATE
VAR Colors = TREATAS ( { "Blue", "Red" }, 'Product'[Color] )
VAR Brands = TREATAS ( { "Contoso", "Fabrikam" }, 'Product'[Brand] )
VAR Result =
SUMMARIZECOLUMNS (
'Product'[Color],
Customer[Education],
Colors,
Brands,
"Sales", [Sales Amount]
)
RETURN
Result

ADVANCED SOLUTION

EVALUATE
VAR Colors = TREATAS ( { "Blue", "Red" }, 'Product'[Color] )
VAR Brands = TREATAS ( { "Contoso", "Fabrikam" }, 'Product'[Brand] )
VAR Res =
SUMMARIZECOLUMNS (
'Product'[Color],
Customer[Education],
Colors,
Brands,
"Sales", [Sales Amount]
)
VAR AvgAmt = AVERAGEX ( Res, [Sales] )
VAR Result =
FILTER ( Res, [Sales] > AvgAmt )
RETURN
Result

13.20 Filtering and grouping sales


13.30 Using TOPN and GENERATE
BASIC EXERCISE

Open the Contoso.pbix Power BI Desktop file, then connect DAX Studio to it.
Using DAX Studio, write a query that retrieves the top three Product[Product Name]
by Sales Amount for each year with data, showing the corresponding Sales Amount
value in an Amt column.

The result should be as follows using Year, Product Name, and Amt as column
names:

ADVANCED EXERCISE

Write a query like the previous one adding a row for each year containing Others as
Product Name, and computing in the Amt column the Sales Amount for all the other
products excluding the top three for that year.
As an optional challenge, add an IsOther column containing 0 for the rows containing
product names and 1 for the rows containing “Others”. This enables you to sort the
products in each year by Amt and to add the Others row at the end of each year
regardless of the Amt value.

The result should be as follows:

13.30
SOLUTIONS

BASIC SOLUTION

EVALUATE
GENERATE (
SUMMARIZE (
Sales,
'Date'[Year]
),
TOPN (
3,
ADDCOLUMNS (
VALUES ( 'Product'[Product Name] ),
"Amt", [Sales Amount]
),
[Amt]
)
)
ORDER BY 'Date'[Year] ASC, [Amt] DESC

ADVANCED SOLUTION

EVALUATE
GENERATE (
SUMMARIZE ( Sales, 'Date'[Year] ),
VAR TopThree =
TOPN (
3,
ADDCOLUMNS (
VALUES ( 'Product'[Product Name] ),
"Amt", [Sales Amount],
"IsOther", 0
),
[Amt]
)
VAR TopThreeSales = SUMX ( TopThree, [Amt] )
VAR AllSales = [Sales Amount]
VAR OtherSales = AllSales - TopThreeSales
VAR OtherRow =
ROW (
"Product Name", "Others",
"Amt", OtherSales,
"IsOther", 1
)
RETURN
UNION ( TopThree, OtherRow )
)
ORDER BY 'Date'[Year] ASC, [IsOther] ASC, [Amt] DESC

13.30 Using TOPN and GENERATE


13.40 Sales to top customers
BASIC EXERCISE

Open the Contoso.pbix Power BI Desktop file, then connect DAX Studio to it.
Using DAX Studio, write a query that retrieves the top 10 customers of all time and
returns the total Sales Amount of these 10 customers for each year.
The result should be as follows, using Year and Sales as column names:

ADVANCED EXERCISE

Write a query like the previous one adding a row for each year, containing Sales of
Others in the Name column and computing the Sales Amount for all the other
customers – excluding the top 10 – in the Sales column. The Name column must
contain Sales of Top 10 for the rows displaying the sales of the top 10 customers,
which are the rows returned by the previous query.
Finally, add a Perc % column that computes the percentage of the row against the
total of the year.
The result should be as follows:

13.40
SOLUTIONS

BASIC SOLUTION

EVALUATE
VAR Top10Customers =
TOPN (
10,
VALUES ( Customer[CustomerKey] ),
[Sales Amount]
)
VAR Result =
SUMMARIZECOLUMNS (
'Date'[Year],
Top10Customers,
"Sales", [Sales Amount]
)
RETURN
Result
ORDER BY 'Date'[Year]

ADVANCED SOLUTION

EVALUATE
VAR Top10Customers =
TOPN ( 10, VALUES ( Customer[CustomerKey] ), [Sales Amount] )
RETURN
GENERATE (
SUMMARIZE ( Sales, 'Date'[Year] ),
ADDCOLUMNS (
VAR SalesTop =
CALCULATE ( [Sales Amount], Top10Customers )
VAR TopTen =
ROW ( "Name", "Sales of Top 10", "Sales", SalesTop )
VAR Others =
ROW (
"Name", "Sales of Others",
"Sales", [Sales Amount] - SalesTop
)
RETURN
UNION ( TopTen, Others ),
"Perc %", DIVIDE ( [Sales], [Sales Amount] )
)
)
ORDER BY 'Date'[Year], [Name] DESC

(continued on next page)

13.40 Sales to top customers


ALTERNATIVE SOLUTION

--
-- Alternative solution without using GENERATE
--
EVALUATE
VAR Top10Customers =
TOPN ( 10, VALUES ( Customer[CustomerKey] ), [Sales Amount] )
VAR YearlySalesTop10 =
SUMMARIZECOLUMNS (
'Date'[Year],
Top10Customers,
"Sales", [Sales Amount]
)
VAR SalesTop10Perc =
SELECTCOLUMNS (
YearlySalesTop10,
"Year", 'Date'[Year],
"Name", "Sales of Top 10",
"Sales", [Sales],
"Perc %", DIVIDE (
[Sales],
[Sales Amount]
)
)
VAR SalesOthersPerc =
SELECTCOLUMNS (
YearlySalesTop10,
"Year", 'Date'[Year],
"Name", "Sales of Others",
"Sales", [Sales Amount] - [Sales],
"Perc %", DIVIDE (
[Sales Amount] - [Sales],
[Sales Amount]
)
)
VAR Result =
UNION (
SalesTop10Perc,
SalesOthersPerc
)
RETURN
Result
ORDER BY
[Year] ASC, [Name] DESC

13.40
(this page was intentionally left blank)

13.40 Sales to top customers


13.50 Sales of top three colors
Open the Contoso.pbix file with Power BI Desktop and then connect DAX Studio
to it. The following queries are included in the DAX file of the exercise.
The following query returns the sales amount of the top three colors:

EVALUATE
TOPN (
3,
SUMMARIZECOLUMNS (
'Product'[Color],
"Sales", [Sales Amount]
),
[Sales]
)

The following query includes a Top3 query measure that sums the result of the
Sales Amount measure of the top three colors. The report should display the Top3
measure for each brand. However, executing the following DAX query produces
an error.

DEFINE
MEASURE Sales[Top3] =
SUMX (
TOPN (
3,
SUMMARIZECOLUMNS (
'Product'[Color],
"Sales", [Sales Amount]
),
[Sales]
),
[Sales]
)
EVALUATE
SUMMARIZECOLUMNS (
'Product'[Brand],
"SalesTop3", [Top3]
)

Fix the code of the Top3 measure by replacing the SUMMARIZECOLUMNS


function with an equivalent DAX syntax that does not generate an error at query
time.

13.50
SOLUTIONS

SUMMARIZECOLUMNS cannot be executed in a filter context generated from a context


transition. Therefore, it cannot usually be used in a measure.
Replace SUMMARIZECOLUMNS with ADDCOLUMNS/SUMMARIZE or ADDCOLUMNS/VALUES.

DEFINE
MEASURE Sales[Top3] =
SUMX (
TOPN (
3,
ADDCOLUMNS (
SUMMARIZE (
Sales,
'Product'[Color]
),
"Sales", [Sales Amount]
),
[Sales]
),
[Sales]
)
EVALUATE
SUMMARIZECOLUMNS (
'Product'[Brand],
"SalesTop3", [Top3]
)

ALTERNATIVE SOLUTION

--
-- Alternative solution using ADDCOLUMNS/VALUES
--
DEFINE
MEASURE Sales[Top3] =
SUMX (
TOPN (
3,
ADDCOLUMNS (
VALUES ('Product'[Color] ),
"Sales", [Sales Amount]
),
[Sales]
),
[Sales]
)
EVALUATE
SUMMARIZECOLUMNS (
'Product'[Brand],
"SalesTop3", [Top3]
)

13.50 Sales of top three colors


14.10 Distinct count of countries
The monthly report displays the number of unique customers in the # Customers
measure.
Create several versions of the # Countries measure that compute the number of
unique countries where customers made at least one transaction. The result should
be as follows.
Hint: there are many possible solutions for the # Countries measure, but the simple
DISTINCTCOUNT used for # Customers is not enough. Find at least two different
solutions if possible – we include three different solutions, but there may be more.

14.10
SOLUTIONS

--
-- # Countries 1
-- Measure in Sales, version with CROSSFILTER
--
# Countries 1 :=
CALCULATE (
DISTINCTCOUNT ( Customer[CountryRegion] ),
CROSSFILTER ( Sales[CustomerKey], Customer[CustomerKey], BOTH )
)

ALTERNATIVE SOLUTION

--
-- # Countries 2
-- Measure in Sales, version with expanded tables
--
# Countries 2 :=
CALCULATE (
DISTINCTCOUNT ( Customer[CountryRegion] ),
Sales
)

ALTERNATIVE SOLUTION

--
-- # Countries 3
-- Measure in Sales, version with SUMMARIZE
--
# Countries 3 :=
COUNTROWS (
SUMMARIZE ( Sales, Customer[CountryRegion] )
)

14.10 Distinct count of countries


14.20 Sales quantity greater than two
Open the Power BI Desktop file and open the corresponding DAX file using DAX
Studio, creating a connection between DAX Studio and Power BI Desktop.
The DAX file contains the following query that returns the Sales Amount measure
for each Product[Brand] in the Sales column, and returns the corresponding Sales
Amount in the SalesGT2 column – only considering transactions where
Sales[Quantity] is greater than two, meaning transactions where more than two
items were purchased. However, the result for the SalesGT2 column in the report
is wrong because it shows the same value for every brand.

EVALUATE
ADDCOLUMNS (
VALUES ( 'Product'[Brand] ),
"Sales", [Sales Amount],
"SalesGT2", CALCULATE (
[Sales Amount],
FILTER (
Sales,
Sales[Quantity] > 2
)
)
)

You have two tasks:


• Describe why the result is wrong – why the number is the same for every
brand;
• Add a Sales GT2 Correct column computing the correct result, as shown
in the following figure.

14.20
SOLUTIONS

The FILTER function iterates over Sales and is evaluated in the original filter context, before
CALCULATE changes it. Therefore, it iterates over all the rows in Sales, only returning rows
where Sales[Quantity] is greater than two.
CALCULATE performs a context transition and filters the product brand. Nevertheless, the
context transition takes place before the filter of CALCULATE gets applied. Therefore, the result
of FILTER overrides the context transition.
FILTER returns the expanded version of Sales, which also contains Product[Brand]. The brands
returned by FILTER include all the brands that have at least one transaction in Sales where
more than two items were purchased.
Consequently, the result computed in the SalesGT2 column is the Sales Amount value
considering all the sales of all the brands that have at least one transaction in Sales with a
quantity sold greater than two.

EVALUATE
ADDCOLUMNS (
VALUES ( 'Product'[Brand] ),
"Sales", [Sales Amount],
"SalesGT2", CALCULATE (
[Sales Amount],
FILTER (
Sales,
Sales[Quantity] > 2
)
),
"SalesGT2 Correct", CALCULATE (
[Sales Amount],
Sales[Quantity] > 2
)
)

14.20 Sales quantity greater than two


14.30 Same product sales
BASIC EXERCISE

The requirement is to build a measure that computes the Sales Amounts of products sold within the
first year shown in the report. For example, if the user selects 2007-2009, the measure only reports
sales of products sold in 2007. If a product has sales in 2008 but not in 2007, then that product must
be ignored in the calculation.
Create a Comparables #1 measure that executes these steps:
1. Compute the first year within the selection.
2. Filter the products that have sales in the first year of the selection.
3. Compute Sales Amount for the products and periods in the report, only considering
products filtered at step 2.
The result should be as follows.
Hint: Sales Amount and Comparables #1 show the same value in 2007 because it is the first year in
the selection. In the following years, Comparables #1 is always smaller than Sales Amount, because
the Comparables #1 measure only considers products that sold in 2007.

(continued on next page)

14.30
ADVANCED EXERCISE

The requirement has changed. The comparison must be made only considering products that sold in
both the first and last year of the selection. For example, if the user selects 2007-2009, the measure
only reports sales of products sold in both the years 2007 and 2009, regardless of the presence of
sales in 2008 for that product. If a product only sold in 2007 and not in 2009 – or vice versa – it is to
be ignored by the calculation.
Create a Comparable #2 measure that executes these steps:
1. Compute the first year and the last year within the selection.
2. Filter the products that have sales in the first year of the selection.
3. Filter the products that have sales in the last year of the selection.
4. Compute Sales Amount for the products and time periods in the report, only considering
products filtered at step 2 and 3.
The result should be as follows.

14.30 Same product sales


(this page was intentionally left blank – solutions on next page)

14.30
SOLUTIONS

BASIC SOLUTION

--
-- Comparables #1
-- Measure in Sales
--
Comparables #1 :=
VAR FirstYear =
CALCULATE (
YEAR ( MIN ( Sales[Order Date] ) ),
ALLSELECTED ()
)
VAR ProductsFirstYear =
CALCULATETABLE (
VALUES ( Sales[ProductKey] ),
ALLSELECTED (),
'Date'[Year Number] = FirstYear
)
VAR Result =
CALCULATE (
[Sales Amount],
KEEPFILTERS ( ProductsFirstYear )
)
RETURN
Result

(continued on next page)

14.30 Same product sales


ADVANCED SOLUTION

--
-- Comparables #2
-- Measure in Sales
--
Comparables #2 :=
VAR FirstYear =
CALCULATE (
YEAR ( MIN ( Sales[Order Date] ) ),
ALLSELECTED ()
)
VAR LastYear =
CALCULATE (
YEAR ( MAX ( Sales[Order Date] ) ),
ALLSELECTED ()
)
VAR ProductsFirstYear =
CALCULATETABLE (
VALUES ( Sales[ProductKey] ),
ALLSELECTED (),
'Date'[Year Number] = FirstYear
)
VAR ProductsLastYear =
CALCULATETABLE (
VALUES ( Sales[ProductKey] ),
ALLSELECTED (),
'Date'[Year Number] = LastYear
)
VAR Result =
CALCULATE (
[Sales Amount],
KEEPFILTERS ( ProductsFirstYear ),
KEEPFILTERS ( ProductsLastYear )
)
RETURN
Result

(continued on next page)

14.30
ALTERNATIVE SOLUTION

There are several possible solutions to the advanced level. We include one alternative solution that
is more efficient only when you cannot assume that the presence of a row in Sales is enough to
consider the product valid in a year, and the measure must be evaluated to determine the presence
of sales.

--
-- Comparables #3
-- Measure in Sales
--
Comparables #3 :=
VAR FirstYear =
CALCULATE (
YEAR ( MIN ( Sales[Order Date] ) ),
ALLSELECTED ()
)
VAR LastYear =
CALCULATE (
YEAR ( MAX ( Sales[Order Date] ) ),
ALLSELECTED ()
)
VAR ProductsFirstLastYear =
CALCULATETABLE (
FILTER (
VALUES ( Sales[ProductKey] ),
VAR SalesFirstYear =
CALCULATE (
[Sales Amount],
'Date'[Year Number] = FirstYear
)
VAR SalesLastYear =
CALCULATE (
[Sales Amount],
'Date'[Year Number] = LastYear
)
RETURN
SalesFirstYear > 0
&& SalesLastYear > 0
),
ALLSELECTED ()
)
VAR Result =
CALCULATE (
[Sales Amount],
KEEPFILTERS ( ProductsFirstLastYear )
)
RETURN
Result

14.30 Same product sales


14.40 Commentary on report
The requirement is to build a measure that returns a commentary on the report,
describing the best city in the Customer table ranked by Sales Amount and
including the Sales Amount and # Customers measures computed for that city.
Create one measure named Commentary that provides this description and show
its content in the visual. The initial report has a placeholder string in the
Commentary measure that must be replaced with the proper dynamic DAX code.
Hints: It is possible to solve the problem using a single measure or multiple
measures referenced by the Commentary measure.
The result should be as follows – the highlighted boxes represent the parts of the
sentence returned by Commentary that must be computed dynamically. For
example, when the user changes their selection of the year, the sentence needs
to change according to the selection.

14.40
SOLUTIONS

--
-- Commentary 2
-- Measure in Sales
--
Commentary :=
VAR BestCity =
TOPN (
1,
DISTINCT ( Customer[City] ),
[Sales Amount], DESC,
Customer[City], ASC
)
VAR SalesBestCity =
CALCULATE (
[Sales Amount],
BestCity
)
VAR NumOfCustomers =
CALCULATE (
[# Customers],
BestCity
)
VAR Result =
"The best city was " & BestCity
& " with a total of " & FORMAT ( SalesBestCity, "$ #,#" )
& " in sales to " & FORMAT ( NumOfCustomers, "#,#" )
& " customers"
RETURN
Result

(continued on next page)

2
If you solved the exercise using multiple measures, check the alternative solution on the next page.

14.40 Commentary on report


ALTERNATIVE SOLUTION

An alternative solution can be to split the calculation into multiple measures used by the
Commentary measure.

--
-- Best City Name
-- Measure in Sales
--
Best City Name :=
TOPN (
1,
DISTINCT ( Customer[City] ),
[Sales Amount], DESC,
Customer[City], ASC
)

--
-- Sales Best City
-- Measure in Sales
--
Sales Best City :=
VAR BestCityName = [Best City Name]
VAR SalesBestCity =
CALCULATE (
[Sales Amount],
Customer[City] = BestCityName
)
RETURN
SalesBestCity

-- # Customers Best City


-- Measure in Sales
--
# Customers Best City :=
VAR BestCityName = [Best City Name]
VAR CustomersBestCity =
CALCULATE (
[# Customers],
Customer[City] = BestCityName
)
RETURN
CustomersBestCity

--
-- Commentary
-- Measure in Sales
--
Commentary :=
"The best city has been " & [Best City Name] & " with a total sales of
"
& FORMAT ( [Sales Best City], "#,#" ) & " performed by "
& FORMAT ( [# Customers Best City] , "#,#" ) & " customers"

14.40
(this page was intentionally left blank)

14.40 Commentary on report


15.10 Static segmentation
BASIC EXERCISE

The model contains a Segments table with three columns: SegmentKey, Segment, and
MinValue. This table does not contain the MaxValue column that you might have
seen in the lectures.

Your task is to produce a report that uses the Segments[Segment] column to slice
the Sales[Net Price] column for each transaction in Sales:

1. Create a Sales[SegmentKey] calculated column that computes the


SegmentKey corresponding to the Sales[Net Price] value for each row in
Sales.

2. Create a relationship between Sales[SegmentKey] and


Segments[SegmentKey].

The result should be as follows.

(continued on next page)

15.10
ADVANCED EXERCISE

Create a MaxValue calculated column in the Segments table that contains the
upper boundary of each segment. For the last row – which has no upper
boundary – use the maximum of Sales[Net Price] out of all the Sales
transactions, in order to include any price in the last segment.
You must obtain the following content in the Segments table.

15.10 Static segmentation


(this page was intentionally left blank – solutions on next page)

15.10
SOLUTIONS

BASIC SOLUTION

--
-- SegmentKey
-- Calculated column in Sales
--
SegmentKey =
MAXX (
FILTER (
Segments,
Segments[MinValue] < Sales[Net Price]
),
Segments[SegmentKey]
)
--
-- Once the column is computed, you build a relationship between
-- Sales[SegmentKey] and Segments[SegmentKey].
--

ALTERNATIVE SOLUTION

The solution proposed does not use CALCULATE to avoid context transitions and possible circular
references when creating the relationship. Here is another solution using CALCULATE with an
explicit ALLNOBLANKROW instead of an implicit ALL generated by a predicate passed as a filter
argument.

--
-- SegmentKey
-- Calculated column in Sales
--
SegmentKey =
CALCULATE (
MAX ( Segments[SegmentKey] ),
FILTER (
ALLNOBLANKROW ( Segments[MinValue] ),
Segments[MinValue] < Sales[Net Price]
),
ALL ( Sales )
)

(continued on next page)

15.10 Static segmentation


ADVANCED SOLUTION

--
-- MaxValue
-- Calculated column in Segments
--
MaxValue =
VAR CurrentMinValue = Segments[MinValue]
VAR LargerSegments =
FILTER ( Segments, Segments[MinValue] > CurrentMinValue )
VAR NextValue =
MINX ( LargerSegments, Segments[MinValue] )
VAR LargestPrice =
MAX ( Sales[Net Price] )
VAR Result =
IF ( ISBLANK ( NextValue ), LargestPrice, NextValue )
RETURN
Result

ALTERNATIVE SOLUTION

The solution proposed does not use CALCULATE to avoid context transitions and possible circular
references when creating the relationship. Here is another solution that does not generate circular
references using CALCULATE.

--
-- MaxValue
-- Calculated column in Segments
--
MaxValue =
VAR CurrentMinValue = Segments[MinValue]
VAR NextValue =
CALCULATE (
MIN ( Segments[MinValue] ),
Segments[MinValue] > CurrentMinValue,
ALL ( Segments )
)
VAR LargestPrice = MAX ( Sales[Net Price] )
VAR Result = IF ( ISBLANK( NextValue ), LargestPrice, NextValue )
RETURN
Result

15.10
(this page was intentionally left blank)

15.10 Static segmentation


15.20 New customers
BASIC EXERCISE

Create a # New C measure that computes the number of new customers in the
selected time period. A customer is considered “new” if they are a customer in the
current time selection and were never a customer before – in other words, they
never bought anything before the beginning of the selected period.
Hint: there are many possible solutions to this exercise, feel free to experiment. The
solution proposed uses a simple algorithm (not the best one) based on set functions.
The result should be as follows.

ADVANCED EXERCISE

Create a Sales New C measure that computes Sales Amount to new customers using
the same logic applied to the #New C measure.
The result should be as follows.

15.20
SOLUTIONS

BASIC SOLUTION

--
-- # New C
-- Measure in Sales
--
# New C :=
VAR CurrentCustomers = VALUES ( Sales[CustomerKey] )
VAR FirstSelectedDate = MIN ( 'Date'[Date] )
VAR OldCustomers =
CALCULATETABLE (
VALUES( Sales[CustomerKey] ),
'Date'[Date] < FirstSelectedDate
)
VAR NewCustomers = EXCEPT ( CurrentCustomers, OldCustomers )
VAR Result = COUNTROWS ( NewCustomers )
RETURN
Result

ADVANCED SOLUTION

--
-- Sales New C
-- Measure in Sales
--
Sales New C :=
VAR CurrentCustomers = VALUES ( Sales[CustomerKey] )
VAR FirstSelectedDate = MIN ( 'Date'[Date] )
VAR OldCustomers =
CALCULATETABLE (
VALUES( Sales[CustomerKey] ),
'Date'[Date] < FirstSelectedDate
)
VAR NewCustomers = EXCEPT ( CurrentCustomers, OldCustomers )
VAR Result = CALCULATE ( [Sales Amount], NewCustomers )
RETURN
Result

15.20 New customers


15.30 Many-to-many relationships
BASIC EXERCISE

The model contains four tables: Customers, AccountsCustomers, Accounts, and


Transactions. It is the same model you saw during the lectures. The Customers and
Accounts tables are related through the AccountsCustomers bridge table. The model
also includes a SumOfAmt measure, which is a simple sum of the
Transactions[Amount] column.
Create an Amount measure based on SumOfAmt so you can produce the following
report, placing Accounts[Account] on the rows and Customer[Customer] on the
columns. There are many possible solutions to this exercise. Find at least two
different ways of obtaining the result by just using a DAX expression without making
any changes to the data model.

ADVANCED EXERCISE

The model also includes the Categories table, which is related to Customers through
the CategoriesCustomers bridge table – similarly to the AccountsCustomers table used
to relate Customers and Accounts.
Create an Amount #4 measure to support the slicing of the transactions by Category;
then, create the following report slicing the Amount #4 measure by
Categories[Category] on the rows and Customers[Customer] on the columns. Hint:
the Categories table will filter Transactions through two many-to-many relationships.

15.30
SOLUTIONS

BASIC SOLUTION

Although the exercise required two different solutions for Amount, we include the three more
common alternatives.

--
-- Amount #1
-- Measure in Transactions – CROSSFILTER version
--
Amount #1 :=
CALCULATE (
[SumOfAmt],
CROSSFILTER ( AccountsCustomers[Account], Accounts[Account], BOTH )
)

--
-- Amount #2
-- Measure in Transactions – expanded table version
--
Amount #2 :=
CALCULATE (
[SumOfAmt],
AccountsCustomers
)

--
-- Amount #3
-- Measure in Transactions – SUMMARIZE version
--
Amount #3 :=
CALCULATE (
[SumOfAmt],
SUMMARIZE ( AccountsCustomers, Accounts[Account] )
)

(continued on next page)

15.30 Many-to-many relationships


ADVANCED SOLUTION

--
-- Amount #4
-- Measure in Transactions – two cascading many-to-many relationships
--
Amount #4 :=
CALCULATE (
[SumOfAmt],
CROSSFILTER ( AccountsCustomers[Account], Accounts[Account], BOTH ),
CROSSFILTER (
CategoriesCustomers[Customer],
Customers[Customer],
BOTH
)
)

15.30

You might also like