Sometimes, what should be easy code to write has unexpected pitfalls resulting in opportunities for new learning. I needed to generate a one or two-tiered stream of date values with multi-frequency period cycles, monthly vs annual.
For example, after installing a piece of equipment I wanted to schedule 12 monthly calibrations, beginning with the month after installation, followed by 3 annual calibrations, beginning 6 months after the last monthly calibration.
This is a table of schedule parameters that defines a few sample scenarios:
Create a Simple List using List.Generate()
My plan was to create generic schedule scenarios, based on specific types of equipment and use period parameters relative to the installation month.
My first thought was to use Power Query’s List.Dates() function to create the sequence of dates, but List.Dates() only increments by combinations of days/hours/minutes/seconds. Not helpful when you need to increment by months. Consequently, I turned to the List.Generate() function to generate the list of periods.
First I wrote some M-Code to test the concept using hard-coded parameters:
Query: JustCreateList
M Code:
And the results were this:
Great! That was easy. If I can make a list and add it to each row of a table, then I can expand each list and build cycle dates.
Use List Values to Create Dates
Query: BuildDates_HardcodedParams
M-Code:
And the results are:
So far, so good. (I may leave early today.)
Use Column Values as List.Generate() Parameters
Next, let’s use column values to drive the Lists. What could be easier, right? Here’s one of my many failed attempts:
Query: BuildDates_ColValParams_V1
M-Code:
And the results are:
Wait! What??? Where are my lists? And, what “field access”?
I Discover “Internal Context” and “External Context”
I won’t go into all of the iterations I tried to get that darn code to work. Suffice it to say that I tried every conceivable variation of “this”, “each”, “_” and anything else I could think of. All to no avail… Same error message.
Now, I could have built a separate function (which I did, and it worked):
Query: fnCreateList
M-Code:
I also could have embedded a function within the M-Code (which I did, and it worked):
Query: SingleTier_EmbeddedFunction
M-Code:
BUT, I wanted a concise, elegant solution. I wanted to just put the List.Generate() function in my code and reference the table columns, just like I would with an Excel function (which I did and…well…you saw the results). I needed help.
So, I posted on Ken’s forum. And who do you think volunteered to assist me? Miguel! (Thank you, Miguel.) Once he established that my interest in referencing column values in List.Generate() had gone way past idle curiosity and quest…all the way to “mission from god”, he gave me a working example and a concise explanation.
Handling Internal and External Context
Because the List.Generate() function is essentially recursive, it has to look at its own sequential results and test them against its internal limits. It needed explicit code to indicate when it should use external context (table references) and when it should use internal context (intermediate function results). I won’t pretend that I understand the “why” of the required syntax, but I quickly picked up on the “how”.
Here’s the working M-Code that uses column values in the List.Generate() function to create the first date sequence:
Query: SingleTier_EmbeddedInnerContext
M-Code:
By simply creating a dummy parameter (listval), the List.Generate() function automatically populated it with its own sequential results and it understood that the column references pointed to the Table! I could have named that parameter “moonchild” or “Sue” or anything else. All that mattered is that it had something to populate.
Now, my results were valid:
Over the first major hurdle and it was a BIG one!
Combining Lists to Generate Two-Tiered Multi-Frequency Period Cycles
The rest of the solution was relatively easy after that. I needed to allow for a second tier of cycle dates.The basic steps were:
- If Tier1 begins after the Install Date, make a placeholder for the Install Date… a period zero.
- Create the Tier1 date sequence.
- If there’s a Tier2 sequence, create it.
- Sequentially append the constructed cycle date sequences.
Since that involves M-Code not covered in this blog post, I’ll just post the final solution:
Query: TwoTier_EmbeddedInnerContext
M-Code:
And here are some of the results of my table containing the desired two-tiered, multi-frequency period cycles:
Power Query/Get and Transform has steered my career into new, creative, challenging, and rewarding directions. I’d like to thank Ken for giving me the opportunity to share what I learned about internal/external context and my learning process. I welcome any and all questions and comments.
-Ron Coderre