PowerBI: Use filter but avoid propoagation effect - filter

I'm trying to calculate how much an employee is spending time on actual projects:
'billability' = [total hours worked on projects] / [total hours that the employee was available]
I'm given a schema like:
Tornado = (employee_id, date, project_id, hours, ..)
'public employee_schedule' = (employee_id, date, hours, ..)
'Tornado' is the table that records hours spent. 'public employee_schedule' is the table that records available hours per day per employee.
So to calculate billability, I have a measure in Tornado table:
billability= Sum(Tornado.hours)/Sum('public employee_schedule'.hours)
In powerBi desktop, I have a page where I do a range of analysis on Tornado, and it includes a date filter (Timeline control) that is tied to Tornado.date
The problem: after a date range is selected, rows on Tornado are filtered and if there is no work done by the employee in those dates, nothing is selected (0 hours worked), but the filter also propagates to 'public employee_schedule' and I get also 0 for hours available.
I should be using ALL with FILTER in the denominator, but how to access the dates chosen in Timeline control?
See screenshot below for data model.

I created a new AllDates(id, date) table with 1:* relationship with Tornado and 'public employee_schedule' on date column.
In my page, I use AllDates.date for filtering, which propagates to both Tornado and public 'public employee_schedule'.
Finally I use Format > Edit Interactions to avoid any other unwanted filters/slicers affecting my measure (that is shown in a KPI card).
Another solution could be something along this line (WorkDone is Tornado and HoursAvailable is 'public employee_schedule') :
billability =
VAR MinDate =
CALCULATE ( MIN ( WorkDone[date] ), ALLSELECTED ( WorkDone ) )
VAR MaxDate =
CALCULATE ( MAX ( WorkDone[date] ), ALLSELECTED ( WorkDone ) )
RETURN
(
SUM ( WorkDone[hours] )
/ CALCULATE (
SUM ( HoursAvailable[available_hours] ),
FILTER (
ALL ( HoursAvailable ),
HoursAvailable[date] >= MinDate
&& HoursAvailable[date] <= MaxDate
)
)
)

Related

DAX calculation with date range is performing bad

I have a DAX formula that is performing really bad and hopefully someone here can suggest a solution.
I have a table that contains about 400000 rows of data. ProductID's (example field), startdate, enddate and an IsActive flag field. The data out of this table should be reported in several ways. In some reports I want to see all of the active products within a selected period of time and in other reports, I only want to see the number of products that were active on the last day of the month.
So, I have created two DAX queries to calculate this.
First I calculate the active products:
_Calc_Count Fields :=
CALCULATE (
DISTINCTCOUNT ( MyFactTable[ProductID] ),
FILTER (
MyFactTable,
MyFactTable[StartDate] <= CALCULATE ( MAX ( 'Date'[Date] ) )
&& MyFactTable[EndDate] >= CALCULATE ( MIN ( 'Date'[Date] ) )
),
MyFactTable[IsActive] = 1
)
Please be aware of the fact that the report this calculation is used in can also contain a date range (even a whole year (or multiple years) can be selected with a startdate and enddate selected in the filter). The report also slices on other filters like Client Group.
Then I have a second calculation that uses the first one and applies the LASTNONBLANK function:
Last Non Blank Value :=
CALCULATE (
[_Calc_Count Fields],
LASTNONBLANK ( 'Date'[Date], [_Calc_Count Fields] )
)
Both calculations are very, very slow.
Can anyone suggest a better approach? Can the DAX formula be optimized or should it completely be rewritten?
ps. I am using Analysis Services Tabular Model.
Thank you all in advance for your responses!
there are many points to consider for optimizing.
First of all, you need to understand where is the bottleneck.
I would do three separate preliminary tests:
A) change the DISTINCTCOUNT with a simple COUNT
B) Remove the FILTER
C) Remove the IsActive
Then you can understand where to prioritize your effort, however there are some very simple general optimization you can do anyway:
1.Make use of variables, therefore the formula becomes:
_Calc_Count Fields 3:=
VAR _startdate = CALCULATE ( MAX ( 'Date'[Date] ) )
VAR _enddate = CALCULATE ( MIN ( 'Date'[Date] ) )
RETURN
CALCULATE (
DISTINCTCOUNT ( MyFactTable[ProductID] ),
FILTER (
MyFactTable,
MyFactTable[StartDate] <= _startdate
&& MyFactTable[EndDate] >= _enddate
),
MyFactTable[IsActive] = 1
)
2.If you use as first parameter of FILTER an entire Fact Table, Storage Engine will load in memory the Expanded Table which is very expensive. Therefore, as a second step the formula should become:
_Calc_Count Fields 2:=
VAR _startdate = CALCULATE ( MAX ( 'Date'[Date] ) )
VAR _enddate = CALCULATE ( MIN ( 'Date'[Date] ) )
RETURN
CALCULATE (
DISTINCTCOUNT ( MyFactTable[ProductID] ),
MyFactTable[StartDate] <= _startdate && MyFactTable[EndDate] >= _enddate,
MyFactTable[IsActive] = 1
)
Next, based on the preliminary test you can decide where to invest your effort.
The issue is the DISTINCTCOUNT:
- explore some alternative algorithms for approximating DISTINCTCOUNT (HIGH EFFORT)
- try to sort in the data source (back-end) the table by ProductId to allow better compression in AAS
- make sure ProductId is a Integer Data type with Encoding Hint: Value
The issue is in the FILTER:
- Try to change the "&&" with "," (LOW EFFORT)
- Investigate the cardinality of StartDate and EndDate. If they are DateTime, remove the Time part. (LOW EFFORT)
- Try to change the datasource in the back-end and sort by useful fields (for example, StartDate asc, so when AAS will read the table might perform better compression (LOW EFFORT)
- Make sure StartDate and Date are Whole Number data types, with Encoding Hint: Value (LOW EFFORT)

How to calculate a measure based on a decreasing/increasing column's value in DAX

I have a Sales table and related dimension tables. MySales table contains columns : Week, StoreID, SalesSeasonID, ProductKey and metrics. My dimensions are related to sales table (Date,SalesSeason,Store,Product tables).
I need to find Sales Quantity (LastYear and LastSeason), as a measure
You can find a sample below:
My purpose is when user selected SaleseasonID[4] then it will return 2 as SalesQuantity.
How can I calculate this measure by DAX formula?
Try:
PYSales =
SUMX (
VALUES ( Table1[YearWeek] ),
CALCULATE (
SUM ( Table1[SalesQuantity] ),
ALL ( Table1[SalesSeasonID] ),
FILTER (
ALL ( Table1[YearWeek] ),
Table1[YearWeek] = EARLIER ( Table1[YearWeek] ) - 100
)
)
)
Worked example PBIX file, using your sample data: https://pwrbi.com/so_55703551/

DAX code change from calculated column to a measure

I have a fact table with settlement_date, product_id, service_id, location_id, and ticket_id and srv_adjusted_earning columns.
I have determined the DAX query to generate a calculated column that sums the srv_adjusted_earning column over the date range: settlement date and settlement date - 27 days (i.e. a 4 week window) as:
=CALCULATE(
SUM(factService[SRV_ADJUSTED_EARNING]),
DATESBETWEEN
(
factService[SETTLEMENT_DATE],
DATEADD(factService[SETTLEMENT_DATE], -27, DAY),
factService[SETTLEMENT_DATE]
),
FILTER(factService, factService[PRO_ID] = EARLIER(factService[PRO_ID])),
FILTER(factService, factService[SER_ID] = EARLIER(factService[SER_ID])),
FILTER(factService, factService[LOC_ID_SELLING] =
EARLIER(factService[LOC_ID_SELLING])),
FILTER(factService, factService[TIS_ID] = EARLIER(factService[TIS_ID]))
)
I am trying to convert this DAX calculated column to a measure and I tried the following:
blob:=CALCULATE
(
SUM(factService[SRV_ADJUSTED_EARNING]),
DATESBETWEEN
(
factService[SETTLEMENT_DATE],
DATEADD(factService[SETTLEMENT_DATE], -27, DAY),
factService[SETTLEMENT_DATE]
),
ALLEXCEPT(factService, factService[PRO_ID]),
ALLEXCEPT(factService, factService[SER_ID]),
ALLEXCEPT(factService, factService[LOC_ID_SELLING]),
ALLEXCEPT(factService, factService[TIS_ID])
)
But I get:
Error: Calculation error in measure 'factService'[blob]: A single value for column 'SETTLEMENT_DATE' in table 'factService' cannot be determined. This can happen when a measure formula refers to a column that contains many values without specifying an aggregation such as min, max, count, or sum to get a single result.
Anybody know how I fix this?
As the error mentions, the issue is with factService[SETTLEMENT_DATE]. In the measure, there is no row context so that it knows which date you are talking about, so you need to specify it somehow. I'd suggest using a variable along these lines:
blob :=
VAR SettleDate = MAX ( factService[SETTLEMENT_DATE] )
RETURN
CALCULATE (
SUM ( factService[SRV_ADJUSTED_EARNING] ),
DATESBETWEEN (
factService[SETTLEMENT_DATE],
SettleDate - 27,
SettleDate
),
ALLEXCEPT (
factService,
factService[PRO_ID],
factService[SER_ID],
factService[LOC_ID_SELLING],
factService[TIS_ID]
)
)
Here the variable picks the maximal settlement date in the current filter context. If that's not exactly what you need, adjust the definition accordingly.

Filter Dax Calculation based upon YTD Sum

In a Tabular SSAS Model, I'm trying to count the number of distinct customers that purchased a given product wtihin a YTD Timeframe. The table contains measures that aren't explicit sums, so I get the Cartesian Product of all products for each customer, regardless of no sales. I'm attempting to limit the count by filtering out customer / product combinations with YTD Sales = 0. However, I cannot get the FILTER to recognize the DATESYTD context. It only ever filters based upon Sales existing within the chosen calendar month. I've tried inserting the ALL function every which way.
This is what I have so far.
Measure:
CALCULATE (
DISTINCTCOUNT ( Fact[Customer] ),
DATESYTD ( Calendar[Date] ),
FILTER ( Fact,
CALCULATE ( [Sum of Sales], DATESYTD ( Calendar[Date] ) ) <> 0
)
)
This measure will, for example, count distinct customers purchasing a product in Month #5 if Month #5 is explicitly chosen. It will not, however, include a customer that purchased that item in Month #2 of the same year.
I think the following DAX should do the trick:
COUNTROWS(
FILTER(
VALUES(Fact[Customer]),
CALCULATE ( [Sum of Sales], DATESYTD ( Calendar[Date] ) ) <> 0
)
)
Also, make sure your 'Calendar' table has been marked as a date table. If, for some reason, you prefer not to mark it as a date table, rewrite the above DAX to:
COUNTROWS(
FILTER(
VALUES(Fact[Customer]),
CALCULATE ( [Sum of Sales], DATESYTD ( Calendar[Date] ), ALL('Calendar') ) <> 0
)
)
Edit: Do you have records in your fact table where [Sum of Sales] is 0? If not, then you could simplify and improve the performance considerably by writing:
CALCULATE(
DISTINCTCOUNT(Fact[Customer]),
DATESYTD( Calendar[Date] )
)
Again, if you haven't marked your 'Calendar' table as a date table, add ALL(Calendar) to remove the filter on specific calendar columns.

DAX | To override active filter with Inactive (Userelationship)

I need a help in creating DAX measures. Here is the relationship in Tabular cube.
I have one Fact sales, Initial Date, final Date and product table.
Relationship : Product -> Fact Sales (1 to Many, active)
Initial Date -> Fact Sales (1 to many, inactive relation)
Final Date - > Fact Sales(1 to many, active)
Cube is used as source for Power Bi tools. Users can select any initial and final date for comparison of sales.
Actual data
Product|Date|Sales
Product1|20160101|100
Product1|20160102|110
Product1|20160131|200
Product2|20160101|78
.....
Expected output
Filters :
product : Product1
Initial date : 20160101
Final Date : 20160131
Product | Initial Sales | Final Sales
Product1|100 |200
I have created DAX measure AS
Initial Sales := CALCULATE(SUM(SALES),USERELATIONSHIP('Fact Sales'[Date],'Initial Date'[Date]))
But this measure is not giving me initial sales as it is also filtered by final date.
Thanks in advance
You can relate tables without an explicit relationship by using FILTER function in DAX.
Since Initial Date relationship with Fact Sales is disabled, you need to FILTER the Fact Sales rows that match the Initial Date selected in the filter. You can use EARLIER to compare the context being evaluated against each row in Fact Sales and filter the right values for the calculation.
Use these measures:
Initial Sales :=
IF (
ISFILTERED ( InitialDate[InitialDate] ),
CALCULATE (
SUM ( FactSales[Sales] ),
FILTER (
ALL ( FactSales ),
COUNTROWS (
FILTER (
FactSales,
EARLIER ( FactSales[Date] ) = MAX ( InitialDate[InitialDate] )
&& EARLIER ( FactSales[Product] ) = [Product]
)
)
)
),
BLANK ()
)
Final Sales :=
IF (
ISFILTERED ( FinalDate[FinalDate] ),
CALCULATE (
SUM ( FactSales[Sales] ),
FILTER ( FactSales, [Date] = MAX ( FinalDate[FinalDate] ) )
),
BLANK ()
)
This expression works as follows, it sums all values in Sales column which Date is equal to the Final Date selected in the filter. However this can be calculated easily as you know with the relationship you have set between Final Date and Fact Sales.
The ISFILTERED functions tell us if the filter have a value selected or not, if a value is selected it returns the value otherwise returns BLANK.
This is an example in Power BI since I don't have access to Excel right now.

Resources