Last week’s post on allocation in Power Query caused quite a lot of interest, so I thought I would follow it up with a post that deals with a slightly more advanced (and more realistic) scenario: what happens if the contracts you are working with don’t all start on the same date?
Here’s the table of data that is the starting point for my examples:
I’ve made two changes:
- I’ve added a contract name to serve as a primary key so I can uniquely identify each contract in the table. Several people asked me why I added index columns to my tables after my last post and this is why: without a way of uniquely identifying contracts I might end up aggregating values for two different contracts that happen to have the same number of months, contract amount and start date.
- I’ve added a contract start date column which contains the date that the contract starts on, which is always the first day of a month.
Now let’s imagine that you want to make each monthly payment on the last day of the month. You need to take each contact and, for each monthly payment generate a row containing the date that is the last day of the month, containing the allocated payment amount.
Once again, having have opened the Query Editor the first step is to calculate the amount of the monthly payment using a custom column that divides Contract Amount by Months in Contract. This is shown in the Allocated Amount column:
Now to generate those monthly payment rows. Since this is reasonably complex I decided to declare a function to do this called EndsOfMonths inside the query, as follows:
= (StartDate, Months) =>
List.Transform(List.Numbers(1, Months), each Date.AddDays(Date.AddMonths(StartDate, _ ), -1))
This function takes the start date for contract and the number of months, and:
- Uses List.Numbers() to create a list containing numbers from 1 to the number of months in the contract. For example if there were three months in the contract, this would return the list {1,2,3}
- This list is then passed to List.Transform(), and for each item in the list it does the following:
- Adds the given number of months to the start date, then
- Subtracts one day from that date to get the payment date, which will be the last day of the month it is in
Calling this function on each row of the table in a new custom column (called Payment Date here) gives you a list of the payment dates for each contract:
All that you need to do then is to click on the Expand icon next to the Payment Date column header and make sure each column has the correct type, and you have your output for loading into the Excel Data Model:
Here’s the code for the query:
let
//Load source data from Excel table
Source = Excel.CurrentWorkbook(){[Name="Contract"]}[Content],
//Add custom column for Allocated Amount
InsertedCustom1 = Table.AddColumn(Source, "Allocated Amount",
each [Contract Amount]/[Months In Contract]),
//Declare function for returning a list of payment dates
EndsOfMonths = (StartDate, Months) =>
List.Transform(List.Numbers(1, Months),
each Date.AddDays(Date.AddMonths(StartDate, _ ), -1)),
//Call this function for each contract in a new custom column
InsertedCustom = Table.AddColumn(InsertedCustom1, "Payment Date",
each EndsOfMonths([Contract Start Date], [Months In Contract]) ),
//Expand the list
#"Expand Payment Date" = Table.ExpandListColumn(InsertedCustom, "Payment Date"),
//Set column data types
ChangedType = Table.TransformColumnTypes(#"Expand Payment Date",
{{"Contract Start Date", type date},
{"Payment Date", type date}, {"Allocated Amount", type number},
{"Contract Amount", type number}, {"Months In Contract", type number}})
in
ChangedType
let
//Aggregate the table to find the min contract start date
//and the max payment date
GroupedRows = Table.Group(Contract, {},
{{"Min Start Date", each List.Min([Contract Start Date]), type datetime},
{"Max Payment Date", each List.Max([Payment Date]), type datetime}}),
//Find the first day of the year of the min start date
#"Start Date" = DateTime.Date(Date.StartOfYear(GroupedRows{0}[Min Start Date])),
//Find the last day of the year of the max payment date
#"End Date" = DateTime.Date(Date.EndOfYear(GroupedRows{0}[Max Payment Date])),
//Call CreateDateTable with these parameters
DateTable = CreateDateTable(#"Start Date", #"End Date"),
//Change data types
ChangedType = Table.TransformColumnTypes(DateTable,{{"MonthNumberOfYear", type number}
, {"DayOfWeekNumber", type number}})
in
ChangedType
You can now build a PivotTable to show the payments allocated over the correct ranges:
The sample workbook can be downloaded here.
//Change data types
Unfortunately, here one always has to define all data types explicitly.
Otherwise, Day of Month, Year and IsToday are of the type text in Power Pivot.
The Power BI components do not collaborate very well.
This is something I’m learning about right now, actually – I think it should be possible to avoid having to explicitly in PQ and I don’t think this is a problem with PQ/PP interaction. There’s a special type called ‘any’ which is like a variant, and PQ seems to default to this type when it’s not sure which type should be used. I agree, though, in the scenarios in this post it should do a better job of detecting the types automatically.
Hi, great post. I have a similar situation where I have employee names with start and end dates in a table form and I need to expand data to account for start and end dates. The list, Transform approach works very well. However, my requirement goes further, as I also need to account for the various cost centres that are relevant to each person and ideally get a list which therefore would resemble
From
Employee x Start x End z Cost Centre Min 1 Cost Centre Max 5
To
Employee Date Cost Centre
x x 1
x x 2
x y 1
x y 2
y x 1
y x 2
y y 1……….
Is it possible to add a list column and tranform the list twice. When I try, it seems to replace one with the other.
Thanks,
It’s hard to say what’s happening here, but you should be able to do this. Have you tried duplicating the list column?