DAX too slow - scalar value materialization problem - powerbi

I have the following measure which calculates a node size for a graph chart table:
MEASURE tMeasures[m_ONA_nodeSize_flex] =
IF(
ISBLANK([m_ONA_rankFilter_nodes]),
BLANK(),
var empCnt = ROW(
"data",
CALCULATE(
SUMX( DISTINCT(tONAAppend[id]), 1),
FILTER(
ALL(tONAAppend),
NOT( ISBLANK([m_ONA_rankFilter_nodes]))
)
)
)
RETURN
IF(
empCnt > 25, ROUND( 1500 / empCnt, 0),
60
)
)
[m_ONA_rankFilter_nodes] is used to filter those nodes which exist in edges in the same table. These edges are also filtered using multiple conditions with a measure m_ONA_edgeValue_afterRank, which returns BLANK() if a row doesn't match filters and edge_value if it does.
The script before using tMeasures[m_ONA_nodeSize_flex] with included measures [m_ONA_rankFilter_nodes] and m_ONA_edgeValue_afterRank works relatively fast:
EVALUATE
TOPN(
501,
SUMMARIZECOLUMNS(
'tONAAppend'[tableName],
'tONAAppend'[name],
'tONAAppend'[nameFrom],
'tONAAppend'[nameTo],
'tONAAppend'[id],
'tONAAppend'[idFrom],
'tONAAppend'[idTo],
'tONAAppend'[photoSrc],
__DS0FilterTable,
__DS0FilterTable2,
__DS0FilterTable3,
__DS0FilterTable4,
"m_ONA_edgeValue_afterRank", 'tMeasures'[m_ONA_edgeValue_afterRank],
"m_ONA_rankFilter_nodes", 'tMeasures'[m_ONA_rankFilter_nodes]
),
'tONAAppend'[tableName],
1,
'tONAAppend'[name],
1,
'tONAAppend'[nameFrom],
1,
'tONAAppend'[nameTo],
1,
'tONAAppend'[id],
1,
'tONAAppend'[idFrom],
1,
'tONAAppend'[idTo],
1,
'tONAAppend'[photoSrc],
1
)
However, when I replace 'tMeasures'[m_ONA_rankFilter_nodes] with a 'tMeasures'[m_ONA_nodeSize_flex] it starts working dramatically slower:
EVALUATE
TOPN(
501,
SUMMARIZECOLUMNS(
'tONAAppend'[tableName],
'tONAAppend'[name],
'tONAAppend'[nameFrom],
'tONAAppend'[nameTo],
'tONAAppend'[id],
'tONAAppend'[idFrom],
'tONAAppend'[idTo],
'tONAAppend'[photoSrc],
__DS0FilterTable,
__DS0FilterTable2,
__DS0FilterTable3,
__DS0FilterTable4,
"m_ONA_edgeValue_afterRank", 'tMeasures'[m_ONA_edgeValue_afterRank],
"m_ONA_nodeSize_flex", 'tMeasures'[m_ONA_nodeSize_flex]
),
'tONAAppend'[tableName],
1,
'tONAAppend'[name],
1,
'tONAAppend'[nameFrom],
1,
'tONAAppend'[nameTo],
1,
'tONAAppend'[id],
1,
'tONAAppend'[idFrom],
1,
'tONAAppend'[idTo],
1,
'tONAAppend'[photoSrc],
1
)
As I understand, the problem is in DAX engine working: it tries to calculate the value of this measure for each row. I think the fastest algo could be calculate once, then send to storage, and then populate all rows with it.
How can I optimize and force DAX to work more efficient?

I found good articles which relate to this topic and then implemented approach with variables written there:
https://www.sqlbi.com/articles/optimizing-conditions-involving-blank-values-in-dax/
https://www.sqlbi.com/articles/understanding-eager-vs-strict-evaluation-in-dax/
It worked as expected - all measures put in special variables were materialized in a Storage Engine. The performance has increased dramatically.

Related

DAX How to return a table based on a condition - workaround for IF to return a table

How to return DAX table based on a condition? The IF function cannot return a table in DAX.
IF( 1=1, table_1, table_2 )
It raises an error: The expression refers to multiple columns. Multiple columns cannot be converted to a scalar value.
I would like to use a slicer to choose between multiple tables, which table later can be used for the alternating dynamic filter context in CALCULATE.
CALCULATE( [Measure],
IF( Condition,
table_1,
table_2
)
)
To make the problem more challenging I would like the table_1 and table_2 to have different set of columns. So combination of UNION and FILTER function won't do.
UNION(
FILTER( table_1, condition ),
FILTER( table_2, NOT condition)
)
As a possible approach, we might pick up a measure based on a slicer choice:
IF( Slicer_Selection_Condition,
[M1], // Measure with filter set 1
[M2] // Measure with filter set 2
)
But I do not want to go this way because it multiplies the number of required measures for each slicer combination.
If we could surpass the IF limitation, we could very usefully apply it. Imagine, we have a slicer to choose a measure among [Quantity], [Value], [Cost]. And we also have another slicer to choose the filter set. We could handle it with a one measure:
CALCULATE(
SWITCH( Measure_Slicer, 1, [Quantity], 2, [Value], [Cost] ), // measure choice
SWITCH( Filter_Slicer, 1, table_1, 2, table_2, table_3 ) // filter choice
)
Here is a table to recreate problem:
Table =
DATATABLE (
"Color", STRING,
"Shape", STRING,
"Quantity", INTEGER,
"Value", INTEGER,
{
{ "Red" , "Circle" , 1, 10 },
{ "Red" , "Triangle", 1, 10 },
{ "Blue" , "Circle" , 1, 10 },
{ "Blue" , "Triangle", 1, 10 },
{ "Yellow", "Square" , 1, 10 }
}
)
And measures:
M_Quantity = SUM( 'Table'[Quantity] )
M_Value = SUM( 'Table'[Value] )
Desired Measure =
VAR Measure_Slicer = 1 // Either 1 OR 2
VAR Filter_Slicer = 1 // Either 1 OR 2
VAR table_1 = SUMMARIZE( 'Table', 'Table'[Color] )
VAR table_2 = SUMMARIZE( 'Table', 'Table'[Color], 'Table'[Shape] )
RETURN
CALCULATE(
SWITCH( Measure_Slicer, 1, [M_Quantity], [M_Value]), // Measure choice
SWITCH( Filter_Slicer, 1, table_1 , table_2 ) // Filter choice
)
Here, a solution for one slicer which lets the user choose between different measures. How to select different data tables, is something I am also curious about :)
Based on your table Table above and the two measures M_Quantity and M_Value, the only things you need to add are:
A table which includes the possibilities for the selection:
choice_measures =
DATATABLE (
"Id", INTEGER,
"Use", STRING,
{
{ 1, "Quantity"},
{ 2, "Value" }
}
)
One more measure:
M_Measure =
VAR MySelection = SELECTEDVALUE(choice_measures[Id], "Show all") RETURN
SWITCH(
TRUE(),
MySelection = 1, [M_Quantity],
MySelection = 2, [M_Value],
""
)
A slicer for choice_measures[Id] where the user can choose between the two measures and, finally, a card where the chosen measure is displayed via M_Measure. Then, it will look like this:
Good luck! Looking forward to the answers of other users on the second part of your question learning how to switch between different data tables!
After a lot of testing, I have arrived at the following conclusions:
Using SWITCH or similar to declare a measure to be used within CALCULATE is not valid syntax. For these types of applications, calculation groups exist, where you code your measures with a placeholder SELECTEDMEASURE.
Arbitrarily shaped filters are allowed in DAX, but apparently not conditionally. It appears passing filter tables to CALCULATE conditionally, either by IF or SWITCH throws errors. In dax.do this error points to a lack of column declaration, but this is not needed for a filter table passed outside of a conditional construct. I suspect this is due to data lineage not being preserved through a such a construct.

Power BI M or DAX - Combine results of subquery (or measure) in aggregate with each row of a table, then roll up by date

I am looking for a way to roll up aggregates over time of a subquery and a time series in either DAX or M. The solutions I can think of are a measure applied per row in an aggregate in DAX, or join on a subquery expression in M, but I cannot find the functions to do this in the documentation.
An example SQL Solution:
SELECT
t1.Date,
t1.Month,
DailyOccupancy - Capacity 'Availability',
r.Rate,
(DailyOccupancy - Capacity) * r.Rate 'OverUnderCost'
FROM
Rate r
INNER JOIN
(SELECT
d.Date,
d.Month,
SUM(c.Capacity) 'DailyCapacity',
SUM(o.Occupancy) 'DailyOccupancy'
FROM
DimDate d
INNER JOIN Capacity c ON d.Date > c.StartDate AND d.Date < c.EndDate
INNER JOIN Occupancy o ON d.Date = o.Date
GROUP BY
d.Date,
d.Month,
r.Rate
) t1 ON o.Date = t1.Date
DAX Measures (works at the individual time step, but when using Capacity measure and a month worth of Occupancy, I get a single Capacity value and 30 Occupancy values summed)
DailyCapacity =
VAR currentDate = MAX ( DimDate[Date] )
RETURN
CALCULATE ( SUM('Capacity'[Capacity]),
FILTER ( 'Capacity',
( 'Capacity'[StartDate] <= currentDate
&& 'Capacity'[EndDate] >= currentDate )))
Available = CALCULATE(SUM('Occupancy'[Occupancy]) - ('Capacity'[DailyCapacity ]))
(Works for each individual time step, doesn't roll up over time)
Is there a DAX method to sum the Capacity measure minus the Available aggregate for each timestep?
Another approach is to use M to create a "DailyCapacity" table. In SQL:
SELECT
d.Date,
SUM(c.Capacity) 'DailyCapacity',
FROM
DimDate d
INNER JOIN Capacity c ON d.Date > c.StartDate AND d.Date < c.EndDate
GROUP BY
d.Date
but I can't find a way to join on a boolean expression in M, only keys. Is there a way to join on a boolean expression, not keys in M?
My tables:
DimDate (Date, day, Month, Year, Billing Month, FY)
----------
1/1/2019, 1, 1, 2019, 1, 2019
1/2/2019, 2, 1, 2019, 1, 2019
... Every time step possible
Capacity (StartDate, EndDate, Capacity, Notes)
----------
1/1/2019, 12/31/2019, 40, "Annual Cap"
6/1/2019, 9/15/2019, 30, "Summer Peak"
Occupancy (Date, Occupancy, Location)
----------
1/1/2019, 20, 1
1/1/2019, 17, 2
1/2/2019, 30, 1
1/2/2019, 9, 2
1/3/2019, 22, 1
1/3/2019, 20, 2
Rate (Date, Rate)
----------
1/1/2019, $49.99
1/2/2019, $64.99
... etc.
Needed Output
Available Space:
----------
1/1/2019, 3
1/2/2019, 1
1/3/2019, -2
1/4/2019, 0
And aggregates on rolled up time steps:
SUM(Available Space * Current Day's Rate) per month, quarter, year, etc. Including over and under capacity totals as negative and positive.
To do this in DAX, let's write a DailyCapacity measure (very similar to yours) that we'll use in another measure. (I use SELECTEDVALUE here so it's more obvious if I'm dealing with more than one value but your MAX and SUM should work too.)
DailyCapacity =
VAR currentDate = SELECTEDVALUE ( DimDate[Date] )
RETURN
CALCULATE (
SELECTEDVALUE ( Capacity[Capacity] ),
FILTER (
Capacity,
Capacity[StartDate] <= currentDate &&
Capacity[EndDate] >= currentDate
)
)
Now to calculated available space, we'll iterate through each row of Occupancy and call the measure we just defined (assuming your DimDate table has a relationship that filters Occupancy).
AvailableSpace = SUMX ( Occupancy, [DailyCapacity] - Occupancy[Occupancy] )
This measure should roll up as expected since it's computing the daily capacity for each row, not just once.
Similarly, if you write a measure for DailyRate like you did for DailyCapacity, then you can write an opportunity cost measure SUMX ( DimDate, [AvailableSpace] * [DailyRate] ).

Power BI - Line Chart with changing color for trend

Just wondering if it's possible to create a chart like below.
I have created the following measures in DAX:
1. [Total Sales]
2. [PM Sale] =
CALCULATE([TotalSales], PARALLELPERIOD('Date'[Date], -1, MONTH)) // Previous month Sales
3. [Indicator] = IF([TotalSales] - [PM Sale] >=0, 1, 0)
4. [IndicatorText] = IF([TotalSales] - [PM Sale] >=0, "UP", "DOWN")
I thought adding the [Indicator] or [IndicatorText] to "Legend" property of line chart would be possible and then be able to change the color, but it is not.
Any ideas?
Thanks,
Shiv
This isn't exactly what you are requesting, but a waterfall chart works pretty well here.
Create a measure to be the difference from the last month and use that for the waterfall chart's y-axis with the date in the category section.
Diff = [Total Sales] - CALCULATE([Total Sales], PARALLELPERIOD('Date'[Date], -1, MONTH))
You can also use a regular bar chart with two series:
Up = IF([Diff] >= 0, [Total Sales], BLANK())
Down = IF([Diff] < 0, [Total Sales], BLANK())
If you convert this to a line chart, it would look like this (you need to set the x-axis to categorical instead of continuous):
It's possible to tweak the measures a bit by looking at the next month in addition to the previous month and get what you want for this particular case, but it won't work correctly in general:
You can't tell from the image, but the first red line segment is covering a green line segment. If red and green segments alternate, then this methodology breaks down.
Here are the adjusted measured for the chart above:
Forward = IF(ISBLANK(PARALLELPERIOD('Date'[Date] , 1, MONTH)),
BLANK(),
CALCULATE([Total Sales]),
PARALLELPERIOD('Date'[Date], 1, MONTH))
- [Total Sales])
Up = IF([Diff] >= 0 || [Forward] >= 0, [Total Sales], BLANK())
Down = IF([Diff] < 0 || [Forward] < 0, [Total Sales], BLANK())

DAX: How to construct a "piecewise" measure?

I have a time series that looks very different before and after a certain date.
I have one measure that calculates the average value of the time series BEFORE that date, and another measure for the average value AFTER that date.
How do I melge them into 1 measure so that when I make a line chart in my front end tool, I see the one average before, and the other average after?
Got it! Trick was using HASONEVALUE and VALUES in tandem to get "data type" agreement. The "combined" measure now displays the piecewise average I was after.
EventCount:= DISTINCTCOUNT([Event_Id])
Avg Daily Event Count Pre:= calculate([EventCount] / DISTINCTCOUNT([EventDate]), Event[EventDate] <= date(2018, 3, 1))
Avg Daily Event Count Post:= calculate([EventCount] / DISTINCTCOUNT([EventDate]), Event[EventDate] >= date(2018, 4, 1))
Avg Daily Event Count Combined:= if (
HASONEVALUE(Event[EventDate]),
if(
values(Event[EventDate]) <= date(2018, 3, 1),
[Avg Daily Event Count Pre],
if(
values(Event[EventDate]) >= date(2018, 4, 1),
[Avg Daily Event Count Post],
blank()
)
)
)

How to check if current start and end dates are in previous period (start and end dates)

I have a tricky problem I am working on. I've made several attempts at capturing the data but have been unsuccessful.
I have a table in Power Bi that looks like this:
The key is in ascending order as well as the StartDate field. I would like to achieve the results in the "Period Overlap Delta" field but have had trouble trying to figure it out.
Basically, I'd like to assign a value of zero to any period (startdate-enddate combination) that is IN a previous period and take the date difference of those "super" periods.
Here is the DAX to produce the table:
Cases = DATATABLE("Key", integer, "StartDate", datetime, "EndDate", datetime
,{
{1, "01/01/2018", "01/10/2018"}
, {2, "01/03/2018","01/03/2018"}
, {3, "01/05/2018","01/07/2018"}
, {4, "01/15/2018","01/16/2018"}
, {5, "01/21/2018","01/24/2018"}
, {6, "01/25/2018", "01/27/2018"}
, {7, "01/25/2018","01/27/2018"}
})
Thanks so much in advance!!!
We need to know whether or not a certain row is overlapped by a previous row. By previous we mean the key is smaller than current row. By overlapped we mean the StartDate is earlier or equal to current row and EndDate is later or equal to current row, hence:
Overlapped =
COUNTROWS(
FILTER(
'Cases',
'Cases'[StartDate] <= EARLIER('Cases'[StartDate]) &&
'Cases'[EndDate] >= EARLIER('Cases'[EndDate]) &&
'Cases'[Key] < EARLIER('Cases'[Key])
)
)
And with this we just need to wrap it up and calculate the number of days with the DATEDIFF function:
Period Overlap Delta =
VAR Overlapped =
COUNTROWS(
FILTER(
'Cases',
'Cases'[StartDate] <= EARLIER('Cases'[StartDate]) &&
'Cases'[EndDate] >= EARLIER('Cases'[EndDate]) &&
'Cases'[Key] < EARLIER('Cases'[Key])
)
)
RETURN
IF(Overlapped, 0, DATEDIFF('Cases'[StartDate], 'Cases'[EndDate], DAY) + 1)
P.S. The use of DATATABLE to provide sample data is golden and should be promoted more often!