We are trying to perform a Utilization % calculation in DAX and I cannot figure out the forumula. Here's the setup:
Table images are here: http://imgh.us/dax.png
Table 1: There is a [timesheet] Table for our resources (R1, R2, R3, R4). Each timesheet record has a date and number of hours charged.
Table 2: There is a [resource] table with resource [hire date] and [termination date].
Table 3: There is a [calendar] table with available hours for each date. Weekdays have 8 h/day, weekends have 0 h/day
For any given filter context we need to calculate:
Utilization % = (hours charged) / (hours available)
This is not so hard, except for the fact that the (hours available) must only include dates between the [hire date] and [termination date] for each employee. Getting this to work at the aggregate level has been very difficult for us since the calculation must consider each individual employees date range.
The closest we have gotten is:
[hours available] := SUMX(DISTINCT(timesheet[resource_key]), SUM(calendar[utility_hours]))
[hours charged] := SUM(timesheet[bill_hours])
[Utilization %] := [hours charged] / [hours available]
However, this does not perform the resource hire/term date range filtering that is required.
Thanks for any help you can offer!
Your measure for [hours available] needs to be revised so that instead of summing all the utility hours in the calendar, it only sums over a filtered set of rows where the calendar date falls between the start date and the termination date of the respective resource.
[hours available]:=SUMX(DISTINCT(timesheet[resource_key]),CALCULATE(SUM(calendar[utility_hours]),FILTER('calendar',calendar[date_key]>=FIRSTDATE(resources[hire_date_key])),FILTER('calendar',calendar[date_key]<=LASTDATE(resources[termination_date_key]))))
You may want to amend the ">=" and "<=" depending on whether you wish to include the start and finish dates in the calculation.
EDIT: Revised version to pick up where resources are not used in the month, but are 'active'
[hours available]:=SUMX(resources,CALCULATE(SUM(calendar[utility_hours]),FILTER('calendar',calendar[date_key]>=FIRSTDATE(resources[hire_date_key])),FILTER('calendar',calendar[date_key]<=LASTDATE(resources[termination_date_key]))))
But you also need to change your [hours charged] to give zeroes, rather than blanks by adding a zero:
[hours charged]:=SUM(timesheet[bill_hours])+0
Related
Having a lot of trouble solving this (I looked through other posts with the same problem but unfortunately I didn't have much luck in applying proposed solutions to my situation).
I have a single (very large) table with transactional information. One of the columns is Transaction Execution Time (field type Time). Execution Time is usually <1 sec but it can go up to a couple of minutes.
Daily, Weekly, Monthly and Yearly transaction reports need to be available and they need to contain average transaction time. Due to large number of entries I am facing overflow when doing average/sum.
Here is a (simplified) sample I am using to test:
SELECT
DATEPART (YEAR, TimeStamp) as 'Year',
COUNT(*) as 'Transaction count',
AVG((DATEDIFF(MILLISECOND, '0:00:00',ExecutionTime))) as 'Average execution time',
SUM((DATEDIFF(MILLISECOND, '0:00:00',ExecutionTime))) as 'Total execution time'
FROM RecordedTransactions
GROUP BY
DATEPART (YEAR, TimeStamp)
What would be the best approach to solve the overflow?
You need to use DATEDIFF_BIG instead of DATEDIFF:
DATEDIFF_BIG ( datepart , startdate , enddate )
This function returns the count (as a signed big integer value) of the specified datepart boundaries crossed between the specified startdate and enddate.
Try instead ensuring that all of your datatypes are treated as a BIGINT rather than an int:
SELECT DATEPART(YEAR, TimeStamp) AS Year,
COUNT_BIG(*) AS [Transaction count],
AVG((DATEDIFF_BIG(MILLISECOND, '0:00:00', ExecutionTime))) AS [Average execution time],
SUM((DATEDIFF_BIG(MILLISECOND, '0:00:00', ExecutionTime))) AS [Total execution time]
FROM RecordedTransactions
GROUP BY DATEPART(YEAR, TimeStamp);
SUM and AVG return the same datatype they were passed. COUNT returns an int, and COUNT_BIG returns a bigint. For SUM, this means that (as your query stands) if it surpasses 2,147,483,647, it'll fail. Using DATEDIFF_BIG, means that the value returned is a bigint, thus your SUM can return a value of up to 9,223,372,036,854,775,807.
My situation is as following:
This is a Powerpivot solution developed in Excel 2013 (32-bit).
I got a transaction table containing transactions with an amount, a category and a posting date. What I would like to to is to present a number of different calculations depending on the time frame.
Sum of amount of current day of import (all the transations with the latest posting date available).
Sum of amount Month-to-date (the current month of the latest transaction)
Sum of amount same period last month (Month-to-date minus one month)
Sum of amount last month (the totals for whole last month)
So, idea is to create a "Current day" measure as a stand point for all the other measures.
[Current day] = LASTDATE('TransactionTable'[Posting Date])
Before summarizing things I wanted to create measures that would represent the start and end date for each period (to display in the report and to make easier measures), this is where I run into trouble.
[First day of current month] = STARTOFMONTH([Current Day])
Gives me the error: "A function 'CALCULATE' has been used in a True/False expression that is used as a table filter expression. This is not allowed."
And with this I would like to end up with something like this for current month:
[Sum of amount current month] = CALCULATE(SUM('Transactiontable'[Amount]);DATESBETWEEN('DateTable'[Date]; [First day of current month];[Current day]))
And this for previous month total:
[First day of previous month] = DATEADD([First day of current month];-1;MONTH)
[Last day of previous month] = EOMONTH([Current day];-1)
CALCULATE(SUM('Transactiontable'[Amount]);DATESBETWEEN('DateTable'[Date]; [First day of previous month]; [Last day of previous month]))
It feels like I am not using the measures the "right" way... Basically I want to create dynamic measures that will change the timeframe depending on what the latest posting date is in the transaction table. Is this the way to go at all?
Thanks guys,
First you need to use ALL()
You were pretty close - the trick to getting [CurrentDate] right is use of the ALL() function. This handy function overrides any filter conditions you have.
So let's look at your [CurrentDate] measure that uses LASTDATE('TransactionTable'[Posting Date]). If your pivot table has months as row labels, this will happen:
The row context alters the output of [CurrentDate]. Not what we want. So you need to override the filter condition, like this.
[CurrentDate] = CALCULATE(LASTDATE(TransactionTable[Posting Date])
,ALL(TransactionTable)
)
Then you need FILTER()
Then to sum the amount for [CurrentDay] we do this:
[SumAmountCurrentDay] = CALCULATE([SumAmount]
,FILTER(TransactionTable
,TransactionTable[Posting Date]=[CurrentDay]
)
)
We need to use FILTER() because it's a more complicated criteria than CALCULATE can handle by default. FILTER() explicitly tells CALCULATE() which table it needs to filter on - although it might be obvious to us, it isn't to PowerPivot.
Here are the rest of the formulas you need, of varying complexity but mostly reusing functions you've listed above, plus ALL() and FILTER().
[FirstDayOfCurrentMonth]
=CALCULATE(STARTOFMONTH(TransactionTable[Posting Date])
,ALL(TransactionTable)
,FILTER(TransactionTable
,TransactionTable[Posting Date]=[CurrentDay]
)
)
[SumAmountCurrentMonth]
=CALCULATE([SumAmount]
,DATESBETWEEN(DateTable[Date]
,[FirstDayOfCurrentMonth]
,[CurrentDay]
)
)
[FirstDayOfPrevMonth]
=CALCULATE(STARTOFMONTH(TransactionTable[Posting Date])
,ALL(TransactionTable)
,FILTER(TransactionTable
,TransactionTable[Posting Date]=
CALCULATE(dateadd(LASTDATE(TransactionTable[Posting Date])
,-1
,month
)
,ALL(TransactionTable)
)
)
)
[LastDayOfPrevMonth]
=CALCULATE(ENDOFMONTH(TransactionTable[Posting Date])
,ALL(TransactionTable)
,FILTER(TransactionTable
,TransactionTable[Posting Date]=
CALCULATE(dateadd(LASTDATE(TransactionTable[Posting Date])
,-1
,month
)
,ALL(TransactionTable)
)
)
)
SumAmountPrevMonth
=CALCULATE([SumAmount]
,DATESBETWEEN(DateTable[Date]
,[FirstDayOfPrevMonth]
,[LastDayOfPrevMonth]
)
)
I looked at this topic, Calculating the number of days in a time dimension node - with Grand Total, but can't seem to get it.
I have a Time Dimension; [Invoice Date].
I want to count the number of Work Days in that dimension for a specified time period. I'm new to MDX.
Here's what I have.
Count(
Descendants(
[Invoice Date].CurrentMember,
[Invoice Date].[Work Date].[Work Date]
)
)
I'm getting a cube error now.
An easy way to implement this reliably would be to create a physical measure "Day Count". To do this, create a new measure group on the Date dimension table, and define "Day Count" as the Count. On the dimension usage tab, make sure you set a relationship from this measure group to the Invoice Date cube dimension and not the other dimensions.
I have a fact table which stores for each record an init_year and end_year as integers, which refer to the year range in which the record is valid.
In which way I can design a MDX query to select my measures (count) for each year, in order to have a trend of my measures over year?
THANKS
I'm not sure this should be done in MDX.
This sort of thing is usually calculated in the fact tables (linked to a dimension table of all available years), and a new measure is created. No calculation would be done in MDX; you'd just display the new measure.
Having said that, I've just Googled "MDX count start end date" and found www.purplefrogsystems.com/blog/2013/04/mdx-between-start-date-and-end-date which suggests you use the LINKMEMBER function. Their example code was...
AGGREGATE(
{NULL:LINKMEMBER([DATE].[Calendar].CURRENTMEMBER
,[START DATE].[Calendar])}
* {LINKMEMBER([DATE].[Calendar].CURRENTMEMBER
, [END DATE].[Calendar]):NULL}
, [Measures].[Project COUNT])
...or...
AGGREGATE({NULL:[DATE].[Calendar].CURRENTMEMBER}
, [Measures].[Project COUNT])
...but it needs careful reading!
A simple cube has 1 measure and three time dimensions:
[Measures].[Amount Paid]
[Date Paid]
[Cover Start Date]
[Cover End Date]
Earned Premium =
0% if Cover Start Date is before the period in question
100% if Cover End Date has passed
else [Cover End - Cover Start] * Days Since Start
For any given cell, how do I traverse all the start and end dates and determine what the amount earned for a period is?
I assume other dimension are missing, e.g. one like [ContractId], if no what comes after doesn't make real sense.
The problem here is that your actual measure,earned premium, is a function (Amount Paid, Cover Start Date, Cover End Date, date) and this for each deal. You can not aggregate over a set of deals at once as the function is not associative - or something like this :-).
So I would feed my cube with the premium for each deal over the period [Cover Start Date], [Cover End Date] with the daily premium for this contract. Once you've this you can easily aggregate this measure over your dimensions. -> Now daily premium is not anymore a function of Cover dates..
MDX is not a real calculation engine, so you're pushing the system out of it's limit. Solving this with scopes, calculated measures can produce an amazingly slow cube...