Mastering DAX Exercises
Mastering DAX Exercises
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 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"
)
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.
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"
)
ADVANCED EXERCISE
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
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] )
--
-- 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.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
)
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
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
--
-- % 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)
ADVANCED EXERCISE
03.30
SOLUTIONS
BASIC SOLUTION
--
-- # CustWithChildren
-- Measure in Sales table
--
# CustWithChildren :=
COUNTROWS (
FILTER (
Customer,
Customer[Total Children] > 0
)
)
--
-- # 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)
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]
)
)
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] ) )
)
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:
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" )
)
Create a Red/Blue Sales measure that only computes Sales Amount for the Red and
Blue colors.
ADVANCED EXERCISE
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" )
}
)
)
)
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
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
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' ) )
)
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
Product[Category] = "Audio"
Product[Subcategory] = "Bluetooth Headphones"
Product[Category] = "Audio"
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' )
)
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" )
)
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.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"
)
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.
ADVANCED EXERCISE
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
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
--
-- 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.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.
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.
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
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
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]
)
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
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.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.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] )
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
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.
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
--
-- 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)
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.
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.
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
)
--
-- 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.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]
)
09.10
SOLUTIONS
BASIC SOLUTION
DAX Expression
SELECTEDMEASURE ()
Calculation Item PY
DAX Expression
CALCULATE (
SELECTEDMEASURE (),
SAMEPERIODLASTYEAR ( 'Date'[Date] )
)
DAX Expression
VAR CY =
SELECTEDMEASURE ()
VAR PY =
CALCULATE (
SELECTEDMEASURE (),
SAMEPERIODLASTYEAR ( 'Date'[Date] )
)
VAR Result = CY - PY
RETURN
Result
DAX Expression
VAR CY =
SELECTEDMEASURE ()
VAR PY =
CALCULATE (
SELECTEDMEASURE (),
SAMEPERIODLASTYEAR ( 'Date'[Date] )
)
VAR Result =
DIVIDE ( CY - PY, PY )
RETURN
Result ()
09.10
09.10 Time calculations
09.20 Multiple calculation groups
BASIC EXERCISE
Your task is to find and fix the problem, and then obtain the expected result.
09.20
SOLUTIONS
BASIC SOLUTION
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
DAX Expression
SELECTEDMEASURE ()
DAX Expression
CALCULATE (
SELECTEDMEASURE (),
USERELATIONSHIP ( Sales[Delivery Date], 'Date'[Date] )
)
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
DAX Expression
SELECTEDMEASURE ()
DAX Expression
MINX (
VALUES ( 'Date'[Year Month Number] ),
SELECTEDMEASURE ()
)
DAX Expression
MAXX (
VALUES ( 'Date'[Year Month Number] ),
SELECTEDMEASURE ()
)
DAX Expression
AVERAGEX (
VALUES ( 'Date'[Year Month Number] ),
SELECTEDMEASURE ()
)
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
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
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
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
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]
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)
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:
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
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.
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
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
--
-- 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)
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]
)
13.50
SOLUTIONS
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]
)
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] )
)
EVALUATE
ADDCOLUMNS (
VALUES ( 'Product'[Brand] ),
"Sales", [Sales Amount],
"SalesGT2", CALCULATE (
[Sales Amount],
FILTER (
Sales,
Sales[Quantity] > 2
)
)
)
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
)
)
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.
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
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
--
-- 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
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.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
2
If you solved the exercise using multiple measures, check the alternative solution on the next page.
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
--
-- 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)
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:
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
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 )
)
--
-- 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)
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
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] )
)
--
-- 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