Satalyst Brilliance on 19 May 2015

Power Query: Creating a parameterized function in M

This will be part two in my on-going series about Power BI, Power Query, and the general joys of data. In my last post I outlined how to iterate over a list in Power Query. But what if I want to iterate over a list and need to parameterize it with today’s date? What if I want to issue multiple web requests based on a list? This is exactly the scenario I had when trying to fetch crime data from the WA Police web site.

First, I have to thank my colleague and web wizard John Chillemi who authored the azure website that I’m using. The WA Police site has all the data, but not in a nice HTML table, which is what Power Query needs. So, thanks to John, I can execute a single web request and get a webpage that looks like this:

Results

 

The web request takes five parameters – Suburb, Start Month, Start Year, End Month, and End Year, as seen below:

web request

I’d like to execute that web request for every suburb in Western Australia (there are over 1300 by my count) so obviously this is a case for automation, which means I need an M parameterized function.

To create my function, I’m going to let Power Query do most of the work for me. I’ll do my transformations for a single web call, then go back and parameterize it. So first, I’ll create a new query based on a web source, pointing at my web request…

CrimeFunction1stStep

 

and I’ll select the HTML table and choose Edit:

CrimeWebSource

 

I then fix up the data the way I want – use the first row as headers and remove the bottom summary row. Note that I didn’t change any data types – typing of the columns is lost when you execute the function, so there’s no point in doing it here. I also changed the name to fCrimeStats make it clear that this is function code. Once the data is looking good, it’s time to edit the M, which is the code behind all the steps I’ve just done. You can find the code on the View ribbon, in the Advanced Editor. Note that this is the only time that you have to use the Advanced Editor – unless you like to.

 

CrimeAdvancedEditor

 

At this point my M looks like this:


let
    Source = Web.Page(Web.Contents("http://crimestatsearch.azurewebsites.net/CrimeStatSearch?suburb=Como&startMonth=1&startYear=1999&endMonth=4&endYear=2015")),
    Data0 = Source{0}[Data],
    #"First Row as Header" = Table.PromoteHeaders(Data0),
    #"Removed Bottom Rows" = Table.RemoveLastN(#"First Row as Header",1),
in
    #"Removed Bottom Rows"

To change it to a function definition, I add the first line (the function definition) and the last two lines, and concatenate in the parameters from the definition into the URL. it looks slightly heinous because the concatenor in M is the ampersand (&), and there are also ampersands in the URL to separate the parameters.


let LoadWebCrimeStats = (suburb as text, startMonth as number, startYear as number, endMonth as number, endYear as number) =>
    let
        Source = Web.Page(Web.Contents("http://crimestatsearch.azurewebsites.net/CrimeStatSearch?suburb="&suburb&"&startMonth="&Text.From(startMonth)&"&startYear="&Text.From(startYear)&"&endMonth="&Text.From(endMonth)&"&endYear="&Text.From(endYear))),
        Data0 = Source{0}[Data],
        #"First Row as Header" = Table.PromoteHeaders(Data0),
        #"Removed Bottom Rows" = Table.RemoveLastN(#"First Row as Header",1)
    in
        #"Removed Bottom Rows"
in
    LoadWebCrimeStats

When I hit OK, I get a new ribbon on my Query Editor menu just for the function, and instead of seeing data, I now see the function definition:
CrimeFunction2ndStep

 

You can test your function by hitting the Invoke button, which will pop up a dialog so that you can enter all the parameters. Please note though that you will get an extra step called “Invoked FunctionLoadWebCrimeStats”. Make sure you delete that step, or you no longer have a function that you can call.

 

Now that the function is done, it’s time to apply it to a list of suburbs. Any time you want to iterate over a list in M, the list has to exist inside of Power Query. So I created a new query with data from the DimSuburb table in my local database and then removed the columns that I didn’t want, leaving only the suburb column. The suburbs sometimes have spaces in them, so I created a custom column to URI encode the name:

 

CrimeURISuburb

 

For bonus points I added a column for the current month and the current year, so that when I refresh the data, it will always be up to date. DateTime.LocalNow returns the current DateTime based on local settings. I convert it to a date with the DateTime.Date function, because Date.Year expects a date.

CrimeCurrentDate

 

At this point, I was finally ready to execute the query that I wrote above for each row in this table. So, I added a custom column, and called the function:

CrimeFunctionCall

 

After my 1300+ web requests were done executing, I expanded the nested table data in the WebData column and removed the URISuburb, CurrentMonth, and CurrentYear columns, leaving me only with the suburb and the crime statistics per month that I was interested it.

CrimeSampleData

 

So, as you can see, you do have to use the Advanced Editor to create a parameterized function in Power Query, but only for the final step that actually creates the function. Otherwise, the process is relatively straight forward.

 

Thanks for reading,

Leah

Categories:
Tags: