Improve DAX Measure Performance - powerbi

in my data set I have a performance issue. I want to speed up my measure and using DAX Studio I made some queries. If anyone has better way to write this measure I will be thankful. "ms" means milliseconds. Both filters are measures.
Original measure:
--original measure - 417 ms
define measure 'BI Booking'[WCX VRS Deals] =
CALCULATE([# Opportunities Booking],
FILTER('Shared Dim Opportunity',([Ent GTMs]="Americas" && [WCX VRS Amount]>=75000) || [Ent GTMs]<>"Americas" && [WCX VRS Amount]>=50000)
)
EVALUATE {[WCX VRS Deals]}
option 1 - using var filter table before the calculation
//option 1 - 358 ms
DEFINE
MEASURE 'BI Booking'[WCX VRS Deals] =
VAR _tbl =
FILTER (
'Shared Dim Opportunity',
( [Ent GTMs] = "Americas"
&& [CXOne VRS Amount] > 75000 )
|| ( [Ent GTMs] <> "Americas"
&& [CXOne VRS Amount] > 50000 )
)
RETURN
CALCULATE (
[# Opportunities Booking],
_tbl
)
EVALUATE
{[WCX VRS Deals]}
option 2 - using 2 tables and union
//option 2 - 505 ms
DEFINE
MEASURE 'BI Booking'[WCX VRS Deals] =
VAR _tbl_1 =
FILTER (
'Shared Dim Opportunity',
( [Ent GTMs] = "Americas" )
&& [CXOne VRS Amount] > 75000
)
VAR _tbl_2 =
FILTER (
'Shared Dim Opportunity',
( NOT ( [Ent GTMs] = "Americas" )
&& [CXOne VRS Amount] > 50000 )
)
VAR _full_tbl =
UNION (
_tbl_1,
_tbl_2
)
RETURN
CALCULATE (
[# Opportunities Booking],
_full_tbl
)
EVALUATE
{[WCX VRS Deals]}
option 3 - using CALCULATETABLE
//option 3 - 379 ms
DEFINE
MEASURE 'BI Booking'[CXOne VRS Deals] =
VAR _tbl =
CALCULATETABLE (
'Shared Dim Opportunity',
FILTER (
'Shared Dim Opportunity',
( [CXOne GTMs] = "Americas"
&& [CXOne VRS Amount] > 75000 )
|| ( [CXOne GTMs] <> "Americas"
&& [CXOne VRS Amount] > 50000 )
)
)
RETURN
CALCULATE (
'BI Booking'[# Opportunities Booking],
_tbl
)
EVALUATE
{[CXOne VRS Deals]}

Related

Intermittent error: DAX expression with SSAS on prem server

I am currently working at updating Tabular model hosted on-prem on a SASS server version 13.0. The tabular model is version 1200.
We have complex logic created in our measures that allow us to define counting rules or to filter depending on parameters selected in disconnected tables.
The model was created using snowflake ‘like’ schema: It can be simplified as below
Link to image: https://ibb.co/WfkmNPV
The error returned by DAX Studio or PowerBI (on an intermittent basis) is the following:
MdxScript(Model) (5174, 18) Calculation error in measure 'Admission'[Number of Clients]: Function 'CONTAINS' does not support comparing values of type Text with values of type Integer. Consider using the VALUE or FORMAT function to convert one of the values.
Sometimes error is resolving by itself without any doing from our end or (sometimes) by reprocessing the model.
In some instances, all our measures are returning this error. In other instances, it’s only a few of them.
The DAX expression for 'Admission'[Number of Clients] is as below:
Admission[Number of Clients] :=
VAR MinDate = MIN ( 'Date'[Full Date] )
VAR MaxDate = MAX ( 'Date'[Full Date] )
VAR AdmissionDate =
FILTER (
Admission,
SWITCH (
TRUE (),
VALUES ( 'Counting Rules'[Counting Rule] ) = "Starts",
Admission[Start Date] >= MinDate && Admission[Start Date] <= MaxDate,
VALUES ( 'Counting Rules'[Counting Rule] ) = "Ends",
Admission[End Date] >= MinDate && Admission[End Date] <= MaxDate,
VALUES ( 'Counting Rules'[Counting Rule] ) = "Active",
Admission[Start Date] <= MaxDate && Admission[End Date] >= MinDate,
0
)
)
RETURN
IF (
COUNTROWS ( VALUES ( 'Counting Rules'[Counting Rule] ) ) = 1,
CALCULATE (
DISTINCTCOUNT ( 'Admission'[ClientKey] ),
FILTER (
AdmissionDate,
IF ( RELATED ( 'Clients'[Date Of Birth] ) <= MAX ( 'Date'[Full Date] )
&& ( ISFILTERED ( 'Client Age'[Age Band] ) || ISFILTERED ( 'Client Age'[Age] ) ),
IF ( COUNTROWS ( VALUES ( 'Age Counting Rule'[Age Counting Rule] ) ) = 1,
INTERSECT (
VALUES ( 'Clients Age'[Age] ),
SELECTCOLUMNS (
ADDCOLUMNS (
VALUES ( Admission[ClientKey] ),
"Age",
ROUNDDOWN (
DATEDIFF (
CALCULATE ( MAX ( 'Clients'[Date Of Birth] ) ),
VAR AdmissionDateFiltered =
IF (
VALUES ( 'Age Counting Rule'[Age Counting Rule] ) = "Age Min",
CALCULATE ( MIN ( Admission[Start Date] ), AdmissionDate, EARLIER ( Admission[ClientKey] ) = Admission[ClientKey] ),
CALCULATE ( MAX ( Admission[End Date] ), AdmissionDate, EARLIER ( Admission[ClientKey] ) = Admission[ClientKey] )
)
RETURN
IF ( VALUES ( 'Age Counting Rule'[Age Counting Rule] ) = "Age Min",
IF ( AdmissionDateFiltered < MIN ( 'Date'[Full Date] ) && NOT ISBLANK ( AdmissionDateFiltered ),
MIN ( 'Date'[Full Date] ),
AdmissionDateFiltered
),
IF ( AdmissionDateFiltered > MAX ( 'Date'[Full Date] ) && NOT ISBLANK ( AdmissionDateFiltered ),
MAX ( 'Date'[Full Date] ),
AdmissionDateFiltered
)
),
DAY
) / 365.25,
0
)
),
"Age", [Age]
)
),
BLANK ()
),
TRUE ()
) //GOM Status
&& IF (
ISFILTERED ( 'Guardianship Status'[Guardianship Type] ) || ISFILTERED ( 'Guardianship Status'[Guardianship Type Description] ) || ISFILTERED ( 'Guardianship Status'[Under Guardianship] ),
CONTAINS (
VALUES ( 'Guardianship Status'[Guardianship Type] ),
'Guardianship Status'[Guardianship Type],
[Child Protection Order Active]
),
TRUE ()
)
)
),
BLANK ()
)
The measure [Child Protection Order Active] in the GOM part of the expression is returning a string value.
The modification I made to the logic is how the Age is calculated.
Have any of you encountered this error?
How would you go by debugging something like that?
Thank you for you time in helping me.
I have resolved my issue by adding a condition around [Child Protection Order Active] to check that it doesn't return blanks.
Measure is always returning a string (at least I assume considering the code) but for some odd reason, the engine think it may return blank (another assumption).
Blank value is considered as integer and therefore returns an error with the above initial DAX expression.

Power BI measure how to determine which date range applies if there is overlap?

I have a measure as follows:
Measure =
VAR CurrentContractDate = SELECTEDVALUE( sales[Date] )
RETURN
IF(
NOT ISBLANK( CurrentContractDate ),
CALCULATE(
SELECTEDVALUE( 'Employees'[Office Name] ),
FILTER( 'Employees', 'Employees'[Contract Start] <= CurrentContractDate && 'Employees'[Contract End] >= CurrentContractDate )
)
)
which takes the date found in my contracts table (sales[Date]) and determines whether that date fits between a date range in my employees table (Employees[Contract Start and Employees[Contract End]) and then will extract the corresponding city name (Employees[Office Name]).
The above works for situations where there only one contract for given sale date but returns empty results if multiple ranges are available or if the sale is made outside of an available date range for a given employee.
Any ideas?
You need to create a calculated table, which will be used to select the location that you want. I don't exactly understand what you mean by "use the date range which has the least amount of days between them", so I will assume it's number of dates between start date and end date. If it's something else, you can always change the formula for calculating days.
This is just off the top of my head, I'm sure it can be optimised.
MyLocation =
VAR CurrentContractDate =
SELECTEDVALUE( Contracts[Date] )
RETURN
IF (
NOT ISBLANK ( CurrentContractDate ),
VAR MyLocals = //This gets all corresponding locations
FILTER (
Locals,
Locals[Date Start] <= CurrentContractDate
&& Locals[Date End] >= CurrentContractDate
)
VAR MyLocalsRanked = //This ranks the locations (in ascending order), based on
ADDCOLUMNS ( //number of days between start and end dates
MyLocals,
"Days", DATEDIFF ( [Date Start], [Date End], DAY ),
"Rank", RANKX ( MyLocals, DATEDIFF ( [Date Start], [Date End], DAY ),, 1 )
)
VAR MyLocal = //This selects the one with the highest rank
TOPN ( 1, MyLocalsRanked, [Rank], 1 )
RETURN
SELECTCOLUMNS ( MyLocal, "Location", [Location] ) //This returns the name of the location
)
As usual, when you look again, you see that there are some superfluous steps...
MyLocation =
VAR CurrentContractDate =
SELECTEDVALUE( Contracts[Date] )
RETURN
IF (
NOT ISBLANK ( CurrentContractDate ),
VAR MyLocals = //This gets all corresponding locations
FILTER (
Locals,
Locals[Date Start] <= CurrentContractDate
&& Locals[Date End] >= CurrentContractDate
)
VAR MyLocal = //This selects the one with the lowest number of days
TOPN ( 1, MyLocals, DATEDIFF ( [Date Start], [Date End], DAY ), 1 )
RETURN
SELECTCOLUMNS ( MyLocal, "Location", [Location] ) //This returns the name of the location
)
EDIT
The modification to select contracts even when there is no matching date is fairly simple. The logic is basically the same - use ranking system to select best location. But first, you have to remove filtering based on dates and then you need to rank results and come up with a rank formula that will prefer matching locations and select non-matching as a last resort. For instance:
MyLocation =
VAR CurrentContractDate =
SELECTEDVALUE ( Contracts[Date] )
RETURN
IF (
NOT ISBLANK ( CurrentContractDate ),
VAR MyLocal =
TOPN (
1,
Locals,
IF (
CurrentContractDate <= Locals[Date End]
&& CurrentContractDate >= Locals[Date End], //Check, whether there is matching date
DATEDIFF ( Locals[Date Start], Locals[Date End], DAY ), //If so, rank matching locations (you may want to employ a different formula)
MIN ( //If the location is not matching, calculate how close it is (from both start and end date)
ABS ( DATEDIFF ( CurrentContractDate, Locals[Date Start], DAY ) ),
ABS ( DATEDIFF ( CurrentContractDate, Locals[Date End], DAY ) )
) + 1000000 //Add a discriminating factor in case there are matching rows that should be favoured over non-matching.
), 1
)
RETURN
FIRSTNONBLANK(SELECTCOLUMNS ( MyLocal, "Location", [City] ) )//This returns the name of the location. In case of tie on the top spot, it returns the first value.
)
How about this?
ContractLocation =
VAR CurrentContractDate = SELECTEDVALUE ( Contracts[Date] )
VAR ValidLocals =
ADDCOLUMNS (
FILTER (
Locals,
Locals[Date Start] <= CurrentContractDate
&& Locals[Date End] >= CurrentContractDate
),
"#Length", Locals[Date End] - Locals[Date Start]
)
VAR MinLength = MINX ( ValidLocals, [#Length] )
RETURN
MAXX ( FILTER ( ValidLocals, [#Length] = MinLength ), [Location] )
This finds the minimal length contract and then returns the top location with that length.

DAX Measure to calculate average with Parameters inside it

I have data like this,
App_Num Days Price
A1 10 100
A1 11 150
A2 11 200
A3 12 250
A3 12 300
A4 20 350
A4 21 400
The average of the days is displayed on a card visual as 13.857.
Now, there are two parameters that are set for user to adjust the values and see.
Total Value (Min and Max Range)
Days
For example, if the user selects 0-280- it is expected to list A1 (100 + 150 = 250 less than 280) and A2 (200 being less than 280).
I used a DAX like this and built a table like this,
Apps_in_scope =
Var min_amount = Min('Total Value'[Total Value])
Var max_amount = Max('Total Value'[Total Value])
var required_app_num = SELECTEDVALUE(Table1[App_Num])
Var required_amount = CALCULATE(sum(Table1[Price]),FILTER(Table1,Table1[App_Num] = required_app_num))
var in_scope = if(And(required_amount <= max_amount, required_amount >= min_amount),1,0)
return in_scope
And I was able to produce a Visual like this,
App_Num Apps_in_scope
A1 1
A2 1
A3 0
A4 0
Now after selecting the total price range, if the user selects the days parameter manually to be 15 then my average will shift as per this logic.
A1 has 2 transactions and with in the selected price range of 280 will become (15*2)
A2 has 1 transaction and with in the selected price range of 280 become (15*1)
A3 has 2 transaction and will remain unchanged (12+12)
A4 has 2 transactions and will remain unchanged (20+21)
So my new measure which I want to place on the card is expected to show now (15+15+15+12+12+20+21)/7 = 15.714
How can I write this measure. Kindly help me with this
I'd tweak your measure slightly so that it works better for taking the average:
Apps_in_scope_2 =
VAR min_amount = MIN ( 'Total Value'[Total Value] )
VAR max_amount = MAX ( 'Total Value'[Total Value] )
VAR required_amount =
CALCULATE ( SUM ( Table1[Price] ), ALLEXCEPT ( Table1, Table1[App_Num] ) )
VAR in_scope =
IF ( AND ( required_amount <= max_amount, required_amount >= min_amount ), 1, 0 )
RETURN
in_scope
With this tweak the average is fairly simple:
AvgMeasure =
VAR DaysParam = SELECTEDVALUE ( DaysSlicer[Days] )
RETURN
AVERAGEX( Table1, IF( [Apps_in_scope_2] = 1, DaysParam, Table1[Days] ) )
Edit:
Here's an alternative version that doesn't use the first measure but should scale better to large data tables.
AvgAlternate =
VAR min_amount = MIN ( 'Total Value'[Total Value] )
VAR max_amount = MAX ( 'Total Value'[Total Value] )
VAR DaysParam = SELECTEDVALUE ( DaysSlicer[Days] )
VAR apps =
ADDCOLUMNS (
SUMMARIZE (
Table1,
Table1[App_Num],
"#Price", SUM ( Table1[Price] ),
"#Rows", COUNT ( Table1[Price] )
),
"#Days",
IF (
AND ( [#Price] <= max_amount, [#Price] >= min_amount ),
DaysParam * [#Rows],
CALCULATE ( SUM ( Table1[Days] ) )
)
)
RETURN
DIVIDE ( SUMX ( apps, [#Days] ), SUMX ( apps, [#Rows] ) )
This is assuming that you have separate tables for your Price range and Days selection (as in what-if parameter tables).
My measure =
VAR apps =
SELECTCOLUMNS (
FILTER (
SUMMARIZE ( Table1, Table1[App_Num], "Total Price", SUM ( Table1[Price] ) ),
[Total Price] >= MIN ( 'Total Value'[Total Value] )
&& [Total Price] <= MAX ( 'Total Value'[Total Value] )
),
"App_Num", [App_Num]
)
RETURN
AVERAGEX (
Table1,
IF ( Table1[App_Num] IN apps, SELECTEDVALUE ( Days[Days] ), Table1[Days] )
)

Tool recommendation for data transform

I have large amounts of raw fault data in Power BI.
code time status
x123 2019-04-22T23:57:00 ok
x123 2019-04-23T01:00:00 faulty
x123 2019-04-23T02:00:00 ok
x123 2019-04-23T23:00:00 faulty
x123 2019-04-24T01:00:00 ok
I need to transform this to show how long an item has been in a faulty state on a given day. So on the 23rd, the item was in a faulty state between 1 and 2a.m and then again between 11pm until past midnight.
code day % of day faulty
x123 23/04/2019 8.30% (2 hours)
Can I do this easily in Power BI or should I use another tool such as Azure Data Factory?
Add the following Calculated Columns to your table:
Report Date = Table1[time].[Date]
Fault Duration =
VAR CurrentTime = Table1[time]
VAR CurrentCode = Table1[code]
VAR PreviousTime =
CALCULATE (
MAX ( Table1[time] ),
FILTER (
Table1,
Table1[time] < CurrentTime &&
Table1[code] = CurrentCode
)
)
VAR NextTime =
CALCULATE (
MIN ( Table1[time] ),
FILTER (
Table1,
Table1[time] > CurrentTime &&
Table1[code] = CurrentCode
)
)
VAR FaultyFrom =
IF(
Table1[status] = "faulty",
Table1[time],
IF (
DAY(PreviousTime) = DAY(Table1[time]),
BLANK(),
Table1[time].[Date]
)
)
VAR FaultyTo =
IF (
Table1[status] = "ok",
Table1[time],
IF (
DAY(NextTime) = DAY(Table1[time]),
NextTime,
Table1[time].[Date] + 1
)
)
RETURN
IF(
ISBLANK ( PreviousTime ) || ISBLANK ( NextTime ) || ISBLANK ( FaultyFrom ),
BLANK(),
FaultyTo - FaultyFrom
)
Now create measures:
Faulty Hours = SUM ( Table1[Fault Duration] )
Faulty % Day =
IF (
HASONEVALUE ( Table1[Report Date] ),
DIVIDE (
[Faulty Hours],
DISTINCTCOUNT ( Table1[code] ),
BLANK()
),
BLANK()
)
Output:
See https://pwrbi.com/so_55825688/ for a worked example PBIX file

PowerBI - Rolling Monthly OTD Calculated Column

I am pretty new to PowerBI and I am trying to calculate a rolling "Monthly On-Time Delivery" calculated column.
ERROR IMAGE
ADDED DAILY SHIPMENT COUNT (Disregard the incorrect MTD Shipment Count title)
Each row represents a single day in the Matrix
For each day I want to get the MonthShippedTotal / (MonthShippedTotal-MonthPastDue), this should give me my rolling on time delivery percentage.
This is what I have so far but the results are way off:
> MonthlyOTD = VAR RowDate = opr_shipments[SHIP_DATE] RETURN
> CALCULATE (
> DIVIDE((COUNT(opr_shipments[CREATE_TIMESTAMP])-COUNTAX(opr_shipments,opr_shipments[PAST_DUE]="1")),COUNT(opr_shipments[CREATE_TIMESTAMP]),0),
> FILTER (
> opr_shipments,
> opr_shipments[SHIP_DATE] <= RowDate
> && MONTH ( opr_shipments[SHIP_DATE] ) = MONTH ( RowDate ) && YEAR ( opr_shipments[SHIP_DATE] ) = YEAR ( RowDate )
> )
> )
MTD Shipments Calc:
MTD Shipments =
VAR RowDate = opr_shipments[SHIP_DATE]
RETURN
CALCULATE (
SUM ( opr_shipments[Daily Shipments] ),
FILTER (
opr_shipments,
opr_shipments[SHIP_DATE] <= RowDate
&& MONTH ( opr_shipments[SHIP_DATE] ) = MONTH ( RowDate ) && YEAR ( opr_shipments[SHIP_DATE] ) = YEAR ( RowDate )
)
)
Past Due Total Calc:
PastDueTotal =
VAR RowDate = opr_shipments[SHIP_DATE]
RETURN
CALCULATE (
SUM(opr_shipments[Daily Shipments]),
FILTER (
opr_shipments,
opr_shipments[SHIP_DATE] <= RowDate
&& DAY ( opr_shipments[SHIP_DATE] ) = DAY ( RowDate ) && opr_shipments[PAST_DUE] = "1"
)
)
For each month this calc would start over.
My desired output is: Jan 1 | 98% Jan 2 | 98.3% Jan 3 | 95% ... Jan 31
| 94.5% Feb 1 | 100%
I think something like this could work for the MonthlyOTD calculated column.
MonthlyOTD =
VAR RowDate = opr_shipments[SHIP_DATE]
RETURN
SUMX (
FILTER (
opr_shipments,
[SHIP_DATE] <= RowDate
&& MONTH ( [SHIP_DATE] ) = MONTH ( RowDate )
&& YEAR ( opr_shipments[SHIP_DATE] ) = YEAR ( RowDate )
),
DIVIDE ( [MTD Shipments], [MTD Shipments] - [PastDueTotal] )
)
I'd create those calculations as measures instead of calculated columns, note measures can be calculated in any context and are affected by filters which usually is desired.
UPDATE: It seems OP doesn't require a monthly cumulative total so the final expression was:
MonthlyOTD = DIVIDE ( ([MTD Shipment Count] - [Lines]), [MTD Shipment Count] )