Author: steve campbell

  • Power Query API Considerations

    Power Query API Considerations

    This article is the second part in a series on API calls. It will look at some best practices and considerations when using API calls in Power Query. While it does not serve as a definitive list, it is an important start to consider performance when using such queries.

    The articles in the first part of the series can be read here:

    Historical Stock Price Function in Power Query

    Average Household Income Function in Power Query

    Performance Considerations

    Use Batch Queries where possible

    Batch queries refer to sending information in “batches”. Making an API call requires information to be sent to an external source, that information returned, then the information parsed and then loaded. Imagine we are doing this for thousands of different dimensions. Consequently, you will have the exact same process to be repeated this many times. Instead, check in the API documentation if batch queries are available. As a result, you may be able to send many of the dimensions in the same API call. This will drastically reduce the amount of times this process happens.

    Use Data Factory

    Data Factory is an Azure service that offers no/low code solution to Extract, Transform and Load (ETL) or ELT (Extract Load, Transform) processes. These are called “pipelines”. Pipelines are repeatable processes that allow you to copy and move data from one source to another (read the documentation here).

    Let’s say you’re trying to load a high level of stock data for thousands of stocks. By doing this in Power Query may put big stress on your gateway. Your gateway may get overloaded and cannot handle sending so many complex API calls. It may be a better idea to load the data into a separate table (such as Azure Synapse). Then your power BI report can read this file directly. Read this documentation for a good overview of this architecture.

    Shifting this to the Azure cloud can leverage Data Factory’s auto-scalability and ability to handle large volumes of data. This results in a more reliable and robust process.

    Consider the most efficient design

    Always consider the way that will send the least number of queries. For example, if you are using historical data that doesn’t change, think if you need to refresh this data every day. In addition, try and avoid sending the same information multiple times. Do queries off unique lists.

    Only return the correct data

    If you load data into Power Query from a source such as SQL Server and then remove columns, a process called Query Folding will take place. Essentially, the data isn’t even loaded into Power Query – it edits the SQL query to not include these columns.

    This can only be done on certain sources. Custom APIs will not do this. Therefore, make sure you send the correct queries. Don’t return extra bits of data that is not needed and make sure you only return columns you will use.

    Review

    While this is by no way a definitive list, it should serve as a starting point to acknowledge performance considerations. Pay attention to how many queries are sent out and try to limit duplication. Remember, Power Query is a powerful tool, but make sure you are using the right tool for the job. Very large and complex operations can be improved with the help of other tools, such as Data Factory.

  • Handle Errors in Data Refreshes with Power Automate

    Handle Errors in Data Refreshes with Power Automate

    This article examines using the advanced editor in Power Query to better handle when things go wrong. It will also allow custom actions to be triggered using Power Automate, allowing the rest of the data to be refreshed while alerting the owner by email of data source errors.

    Note that this article requires edit Power Query (M) code. If you want to review this, consider reading this article:
    Query Editor – Editing M Code

    Data Sources

    Data can be messy. Thankfully, Power Query has given us an excellent and efficient way to extract, transform and load the data to manipulate it to the way we want it. It is extremely versatile and can connect to a huge host of data sources. These can include third party sources as well as internal data.

    While beneficial, we need to ask what happens if the third-party source suddenly shuts down or changes its access policies. What if there is bad data recorded in the on-premise excel sheet or databases? An error in any Power Query step can stop the entire report refreshing. We can opt to receive alerts on the service, but these can be unspecific and require us to dig deep into the report.

    The technique laid out here allows to receive a specific alert of the exact step the error occurred. What’s more, we can ensure it won’t break our queries and continue to refresh the rest of the data.

    Step 1 – Base Query

    First we need to set up the query that we want to error handle. For this example, I’m going to send a web request to get some information about the Microsoft stock price. For more information on this API or to pull other stock data, check out this article.

    Open Power Query and Select the New Source > Web on the Home ribbon.

    Paste the following link:

    https://query1.finance.yahoo.com/v8/finance/chart/MSFT?range=5y&interval=1d

    This will automatically return a JSON object and parse it for us.

    Note: This link returns 5 years of daily daily historical stock price.

    For simplicity, I will just return the meta data to ensure the API call is working. The automatic parsing will return a table with clickable elements. To explore into the JSON we, click through the following steps:

    chart:Record > result:list > Record > Meta:Record

    Note: See sample of nested structure below for chart:Record

    Once we have expanded all the way down to the Meta level, Press the Convert Into Table Button found on the newly appeared Convert ribbon.

    Here is the final code, which you can see by Clicking the Advanced Editor button on the Home ribbon revealing the advanced editor:

    let 
        Source = Json.Document(Web.Contents("https://query1.finance.yahoo.com/v8/finance/chart/MSFT?range=5y&interval=1d")),
        chart = Source[chart],
        result = chart[result],
        result1 = result{0},
        #"meta" = result1[meta],
        #"Converted to Table" = Record.ToTable(#"meta")
    in
        #"Converted to Table
    

    Rename this “Stock Query” by editing the name in the properties pane on the right.

    Step 2 – Create the flow

    Next we create the Power Automate flow that will alert us something is wrong. Navigate to the Power Automate website. Once logged in, Click on the new Instant flow button.

    Give the flow an appropriate name. For the trigger, select the option When an HTTP request is received. Next press the button Create to make the flow.

    Once we save the flow will supply us with a URL. This URL will trigger the flow any time it is visited. You can use any action you want, but I will have the flow send me an email to let me know the refresh failed.

    I’ll search for the item Send an email (V2). Make sure you fill in the email address you want to send it to, and write an appropriate message and subject.

    That’s our flow! Hit Save. After you have saved, click on the When a HTTP request is received step to expand it. You’ll see that a URL has been generated. Copy the link using the Copy button on the right. You’ll need this to enter it into Power BI.

    Step 3 – Make the Flow Trigger Query

    Next, set up the query in Power BI to call this flow. In Power Query, make a new query by selecting New Source > Web. Paste in the Web URL you copied in the previous step. Open the advanced editor. Inside you see the code uses the Web.Contents() function. You’ll need to copy this code in a later step.

    This will send an email as it runs the query. For testing, if you press the Refresh Preview icon, you can trigger the actions to run again. If you don’t want to wait for the email or chose a different action, you can check if the flow ran by checking it on the power automate site. Click My Flows on the left, open the flow and scroll down to Runs.
    Press the refresh button on the runs section to check when the flow ran.

    Step 4 – Set up the Error Handling Code

    Now we need to add the logic to have the Power Automate run on error, which is going to take a little coding.

    Back in Power Query, start by adding a blank query by clicking New Source > Blank Query on the Home Ribbon.
    Next, open the Advanced Editor and the code should look like this:

    Now we will employ the try … otherwise statement. This is Power Query’s error handing statement. We can add a placeholder for now.
    Replace the step:

    Source = "" 

    with the following code:

    Source = try 1+1 otherwise “error”

    How this works

    Any code between the try and otherwise keywords will be attempted to be executed. If this is successful, the result will be returned and the next step will be evaluated, in this case the number 2. If this returns an error, however, the result will be discarded and the query after the word otherwise will be returned, in this case the word “error” .

    We can add this statement as the Source step. I’ll also wrap both statements in parenthesis as this will come in handy as we add more steps. It’s important to keep good syntax to make it readable, so here is my code:

    As 1+1 is valid, this will return the value 2. If you change the 1+1 to 1+“A”, this is not valid so it will return the word error.

    Now we can see its working, delete everything between the parenthesis in both the try and otherwise statement to set up for the next step. Do not worry if you get an error as we will be adding code to it.


    Step 5 – Update the Error Handling Code

    Now we’ve got the placeholder set up we can copy our previous code into each step.
    Open the Stock Query we made in steps 1 and 2.
    Click Advanced Editor and copy the entire code.
    Now, come back to the try..otherwise query.
    Open the advanced editor in this step and make sure you delete anything between the parenthesis in the try statement if you did not before.
    Paste the entire code you just copied.

    Next, go to the Flow Error Query, open the advanced editor and copy all the text.
    Go back to the try..otherwise and paste everything between the two parenthesis for the otherwise statement.

    Step 6 – Tidying the Code Up

    The URLs are prone to change so it is code practice to separate them as a variable. To do this, you can simply state them at the beginning of the query as a step.
    Here, add the URLs to variables called WebURL and FlowURL.
    To add WebURL, at the beginning of query after the let keyword, add the line:

    webURL = "https://query1.finance.yahoo.com/v8/finance/chart/MSFT?range=5y&interval=1d",

    Don’t forget the comma at the end. Do the same with the FlowURL. Then, replace the URLs with the variable name.

    Additionally, it will help to change the word Source outside the try..otherwise to Output. This makes it easier to follow, as we have several steps called source. Also update it after the keyword in.

    Output:

    Final code to copy (you will need to update to your correct URLS):

    let
        flowURL ="https://prod-68.westus.logic.azure.com:443/workflows/ RestofURL",
        webURL = "https://query1.finance.yahoo.com/v8/finance/chart/MSFaaT?range=5y&interval=1d",
        
        Output =   
        try 
        (
            let 
                Source = Json.Document(Web.Contents(webURL)),
                chart = Source[chart],
                result = chart[result],
                result1 = result{0},
                #"meta" = result1[meta],
                #"Converted to Table" = Record.ToTable(#"meta")
            in
                #"Converted to Table"
        )
        otherwise    
        (
            let
                Source =  Web.Contents(flowURL)         
            in
                Source
        ) 
    
    in
        Output
    

    Optional Step 7.1 – Adding POST Parameter – Flow

    Now we have set up our workflow, it may be useful to reuse this on multiple datasets. Instead of setting up a different flow for each dataset, we can allow the flow to accept inputs, such as the dataset name.

    Navigate back to Power Automate site and on the flow set up previously, click edit.

    Open the step and paste in the following code into Request body JSON.

    {
        "type": "object",
        "properties": {
            "datasetName": {
                "type": "string"
            }
        }
    }

    Next, expand advanced options and change it to POST.

    This will create a variable called datasetName. This is the name of the dataset we will pass from Power BI. We can use datasetName in the email to inform us which dataset had an error. The variable will appear in a list when you click to edit the email message – click on the name to add it.

    In addition, I added the expression utcNow(). You can find this by searching in the Expression tab of the pop up. This just displays the time it is sent. Thus we can see in the email when it failed. Notice the variable from the first step is green, while the expression is pink.
    Personalize this message as you wish.

    Optional Step 7.2 – Adding POST Parameter – Power BI

    The final step is to pass this in from Power BI. In our query, open the advanced editor and add three more parameters before webURL and flowURL:

        datasetName = "Stock Query",
        content = " { ""datasetName"": """ & datasetName & """}",
        headers = [ #"Content-Type"="application/json"],
    

    dataset name is the name of the datset we pass into the flow and ultimately appear in the email. Right now it’s set to Stock Query, but this is what we can edit for each dataset we use this query to pass a different name.

    content and headers build the correct format to pass into the API call. This will create a POST request and provide the correct format.

    Now we can edit the Web.Contents(webURL) step to include our parameters:

    Source =  Web.Contents(flowURL, [Headers = headers, Content=Text.ToBinary(content)])  

    Final output:

    Final code to copy (you will need to update to your correct URLS):

    let
        datasetName = "Stock Query",
        content = " { ""datasetName"": """ & datasetName & """}",
        headers = [ #"Content-Type"="application/json"],
        flowURL ="https://prod-68.westus.logic.azure.com:443/workflows/RestofURL",
        webURL = "https://query1.finance.yahoo.com/v8/finance/chart/MSFaaT?range=5y&interval=1d",
        
        Output =   
        try 
        (
            let 
                Source = Json.Document(Web.Contents(webURL)),
                chart = Source[chart],
                result = chart[result],
                result1 = result{0},
                #"meta" = result1[meta],
                #"Converted to Table" = Record.ToTable(#"meta")
            in
                #"Converted to Table"
        )
        otherwise    
        (
            let
                Source =  Web.Contents(flowURL, [Headers = headers, Content=Text.ToBinary(content)])         
            in
                Source
        ) 
    
    in
        Output
    

    Limitations and Considerations

    This technique uses premium Power Automate features and a valid license is required. However, only one service account license is needed.

    This query has been designed to return blank data if the query fails. This could break your model if this data is required.

    This blog does not examine securing the Power Automate endpoint, so be aware if the URL was discovered people could execute the end action (in this case sending an email).

    If you like the content from PowerBI.Tips please follow us on all the social outlets to stay up to date on all the latest features and free tutorials.  Subscribe to our YouTube Channel.  Or follow us on the social channels, Twitter and LinkedIn where we will post all the announcements for new tutorials and content.

    Introducing our PowerBI.tips SWAG store. Check out all the fun PowerBI.tips clothing and products:
    Store Merchandise

  • Average Household Income Function in Power Query

    Average Household Income Function in Power Query

    This post will walk through how to pull an estimated household income from a US address. It will be completed all in the Power Query Editor. We will convert this to a function to reuse on any address we want.

    This is the second part in a series on free API calls. The first part demonstrated how to pull daily stock price from Yahoo! Finance, which is available here.

    Note: The content in this blog was first presented at the Power Platform Summit North America on October 18th, 2019.

    Introduction to the Data

    We cannot get exact income of a US address, but the US census releases data which is aggregated at different groups of households (called Geography Hierarchies).
    There are different levels of Geography Hierarchies, explained on the census website. The lowest level of granularity available for this data is Block Groups. This is a small group of blocks, and usually consists of about a few hundred to over 1000 people. A block group is expressed as a code.

    It is unlikely we will have the actual bock group code, but rather a street address. In order to retrieve information, we need to find which block group the address falls into. The first step is to convert the address into geographic coordinate, a process called Geocoding. You are probably familiar with using this feature – for example when you enter an address into a map app in order to get directions.

    The census.gov website offer a free service to do this (US Census TIGER). However, in my experience the match rate (percentage of addresses geocoded) is not as good as some other services – this means that some of the addresses you enter will not be found. There are many other companies that offer Geocoding services, such as Bing, who often provide better match rates. However, these tend to come at a cost. These can be used instead, but for this example we will focus on the free US Census TIGER service.

    Create an API Key

    The first step will be to sign up for an API key from the census website. API keys allow organizations to monitor usage and can stop overloading their databases with requests. Some can be used to charge for paid API calls, however the census API is free and only requires an email address. If you do not work for an organization, you can write “none”.

    Sign up here:
    https://api.census.gov/data/key_signup.html

    Storing the API Key

    Back in Power BI, on the home tab in Power Query, click Home, New Source, Blank Query.

    In the formula bar, Paste in the API key value you received. Rename the query to P_APIKEY.
    This will store the API key so it can be changed, but will not set it as a parameter and require it to be entered by the user every time they use the function.

    Setting up Parameters

    In the power query window, under the Home ribbon, Click the bottom half of the Manage Parameters button. From the drop down Select the option New Parameter.

    Name the parameter P_Address. Change the Type field to Text. Enter 15010 NE 36th St in the Current Value input box.

    Repeat this step 3 more times, so you will have 4 parameters in total. Use the below table for the names and default values:

    NameDefault Value
    P_Address15010 NE 36th St
    P_CityRedmond
    P_StateWA
    P_ZIP 98052

    Function Part 1: Geocoding

    On the home tab, Click New Source > Web. Switch to the advanced tab, Then in the open dialogue box, first Click the button Add part. This will add a new box. Repeat this to add 9 web parts. Afterwards, locate the first window and Enter part 1 of the URL. In the second box, Change the abc symbol to a parameter.  Fill in the boxes like below:

    1https://geocoding.geo.census.gov/geocoder/geographies/address?street=
    2P_Address 
    3&city=
    4 P_City
    5&state=
    6 P_State 
    7&zip=
    8 P_ZIP 
    9&benchmark=Public_AR_Census2010&vintage=Census2010_Census2010&layers=13&format=json

    Note: Do not enter the rows that begin with  P_ directly (rows 2,4,6,8). Switch the type to parameter and pick from the list.

    This will return a result: Record. Click on the Record value to drill into it.
    In the convert tab, click To Table to transform to a table.
    We have extra information here, but we are only interested in the blockgroup data. Filter the column Name to include the following rows only:
    “BLKGRP” , “COUNTY”, “STATE” , “TRACT”
    (make sure you keep the correct columns with these exact names).

    Now we have the correct columns, but for our function to waork we want them in one row. Highlight the Name column, navigate to the transform tabe and click Pivot Column. The symbol is:

    Expand the Advanced Options, and change the Aggregate Value Function to Don’t Aggregate.

    If you are following with the Microsoft head office, your data should look like this:

    Now that we have the address geocoded, we can find out census information.

    Function Part 2: Returning Household Income

    To add the second API call, we can take advantage of the custom column. It is actually possible to use Power Query (M) code in the custom column.
    Click Add Column then Custom Column.

    The code needed for the column is below. You can copy and paste this directly into the “Custom column formula” :

    Json.Document(Web.Contents("https://api.census.gov/data/2018/pdb/blockgroup?get=Tot_Population_CEN_2010,avg_Agg_HH_INC_ACS_12_16&for=block%20group:" & [BLKGRP] & "&in=state:" & [STATE] & "%20county:" & [COUNTY] & "%20tract:" & [TRACT] & "&key=" & P_API))

    Breaking this code down:
    Json.Document tells Power BI the value being returned is in JSON format, and to decode this to a list
    Web.Contents will tell Power BI we are going to be sending a web (api) query
    https://api.census.gov/data/2018/pdb/blockgroup?get=Tot_Population_CEN_2010,avg_Agg_HH_INC_ACS_12_16 is our base URL will some parameters. The parameters we’re giving are to return population and average household income
    -The second half of the URL takes in the location from the current row. anything in [Square Brackets] is a column. P_APIKEY is the parameter we set up earlier, that holds our API key

    This will add a new column holding a list of lists. Click on the word list to drill into it

    This will bring to the second level, two lists. Afterwards, Transform to a table by clicking on the “To Table” button in the ribbon, under list tools > Transform.
    After it is in table format, we expand the values to take them out the list. The data has two lists, the first is the column headers and the second is the values, so we need to transform a little to see the data in a nice format.
    Firstly, expand the values by clicking the expand button and select Extract Values.

    This will expand to one column, separated by a character of our choice. I’m going to use the carat symbol (^), as I’m confident this will not be a character already in the data. To do this, change the first box to –Custom– then type in the carat symbol.

    After extracting the data from the lists, we can split it into columns. We will split on the delimiter we added, the carat symbol (^). Click on Home Tab, Split Column, By Delimiter.

    Same as before, change the first box to –Custom– then type in the carat symbol. Leave the default setting of “At each occurrence of the delimiter”.

    Now it is split, promote the first row to headers by going to the Transform tab and use the “Use first row as headers”.


    Finalizing the Function

    As a result, this returns lots of columns. Highlight the Tot_Population_CEN_2010 and avg_Agg_HH_INC_ACS_12_16 column, right click and select “Remove other columns”.
    Rename the two columns to “Population” and “Household Income” by double clicking the column header and typing the new name.
    Finally, highlight both columns, enter the Transform tab and click “Detect Data Type” to convert the data type.

    Optional: If you want to add the original address in, Click Add Column then Custom Column. Enter the below code, which will concatenate the address to one value. You can name the column Address.

    P_Address & ", " & P_City & ", " & P_State & ", " & P_ZIP  

    Creating the Function

    This query uses a parameter which enables us to can convert it to a function. To do this, right click on the query in the Queries pane on the left and select make function.

    Now we have a function where we can input any address and return the estimated household income, as well as population of this average is taken from(n value). To check multiple addresses, you can add your function to any list. This can be found in Add Column ribbon, then Clicking the button Invoke Custom Function. This will return a table for each row. Before expanding, it is important to handle errors, otherwise it could break the query. One option is to Right Click the column header, and select the Replace Errors option, and Type the text null.

    Final Function

    For those who like M code, here is the final function. You can copy and paste this directly into the advanced editor (See this article on how to do this).

    let
        Source = (P_Address as any, P_City as any, P_State as any, P_ZIP as text) => let
        
                Source = Json.Document(Web.Contents("https://geocoding.geo.census.gov/geocoder/geographies/address?street=" & P_Address & "&city=" & P_City & "&state=" & P_State & "&zip=" & P_ZIP & "&benchmark=Public_AR_Census2010&vintage=Census2010_Census2010&layers=13&format=json")),
                result = Source[result],
                addressMatches = result[addressMatches],
                addressMatches1 = addressMatches{0},
                geographies = addressMatches1[geographies],
                #"Census Blocks" = geographies[Census Blocks],
                #"Census Blocks1" = #"Census Blocks"{0},
                #"Converted to Table" = Record.ToTable(#"Census Blocks1"),
            #"Filtered Rows1" = Table.SelectRows(#"Converted to Table", each ([Name] = "BLKGRP" or [Name] = "COUNTY" or [Name] = "STATE" or [Name] = "TRACT")),
                #"Filtered Rows" = Table.SelectRows(#"Filtered Rows1", each ([Name] = "BLKGRP" or [Name] = "COUNTY" or [Name] = "STATE" or [Name] = "TRACT")),
                #"Pivoted Column" = Table.Pivot(#"Filtered Rows", List.Distinct(#"Filtered Rows"[Name]), "Name", "Value"),
            #"Added Custom" = Table.AddColumn(#"Pivoted Column", "Custom", each Json.Document(Web.Contents("https://api.census.gov/data/2018/pdb/blockgroup?get=State_name,County_name,Tot_Population_CEN_2010,avg_Agg_HH_INC_ACS_12_16&for=block%20group:" & [BLKGRP] & "&in=state:" & [STATE] & "%20county:" & [COUNTY] & "%20tract:" & [TRACT] & "&key=" & P_APIKEY))),
            Custom = #"Added Custom"{0}[Custom],
            #"Converted to Table1" = Table.FromList(Custom, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
            #"Extracted Values" = Table.TransformColumns(#"Converted to Table1", {"Column1", each Text.Combine(List.Transform(_, Text.From), "^"), type text}),
            #"Split Column by Delimiter" = Table.SplitColumn(#"Extracted Values", "Column1", Splitter.SplitTextByDelimiter("^", QuoteStyle.Csv), {"Column1.1", "Column1.2", "Column1.3", "Column1.4", "Column1.5", "Column1.6", "Column1.7", "Column1.8"}),
            #"Changed Type" = Table.TransformColumnTypes(#"Split Column by Delimiter",{{"Column1.1", type text}, {"Column1.2", type text}, {"Column1.3", type text}, {"Column1.4", type text}, {"Column1.5", type text}, {"Column1.6", type text}, {"Column1.7", type text}, {"Column1.8", type text}}),
            #"Promoted Headers" = Table.PromoteHeaders(#"Changed Type", [PromoteAllScalars=true]),
            #"Changed Type1" = Table.TransformColumnTypes(#"Promoted Headers",{{"State_name", type text}, {"County_name", type text}, {"Tot_Population_CEN_2010", Int64.Type}, {"avg_Agg_HH_INC_ACS_12_16", Currency.Type}, {"state", Int64.Type}, {"county", Int64.Type}, {"tract", Int64.Type}, {"block group", Int64.Type}}),
            #"Removed Other Columns" = Table.SelectColumns(#"Changed Type1",{"Tot_Population_CEN_2010", "avg_Agg_HH_INC_ACS_12_16"}),
            #"Renamed Columns" = Table.RenameColumns(#"Removed Other Columns",{{"Tot_Population_CEN_2010", "Population"}, {"avg_Agg_HH_INC_ACS_12_16", "Houshold Income"}})
        in
            #"Renamed Columns"
    in
        Source

    If you like the content from PowerBI.Tips please follow us on all the social outlets to stay up to date on all the latest features and free tutorials.  Subscribe to our YouTube Channel.  Or follow us on the social channels, Twitter and LinkedIn where we will post all the announcements for new tutorials and content.

    Introducing our PowerBI.tips SWAG store. Check out all the fun PowerBI.tips clothing and products:
    Store Merchandise

  • Historical Stock Price Function in Power Query

    Historical Stock Price Function in Power Query

    This post will walk through how to pull daily stock price from Yahoo! Finance, then transform the data using a technique called a query branch. It will be completed all in the Power Query Editor. We will convert this to a function to reuse on any stock we want.

    There are many API to pull stock information that get historical stock prices. Many come with a cost to get this information in a decent format. The technique described here is free but will require some data transformations to get the data in a usable format. The purpose is to explore parameters, web URLs and query branches to design a usable function. If you’re just interested in pulling stock information, skip to the end to grab the M code – just make sure you read the performance considerations.

    Note: The content in this blog was first presented at the Power Platform Summit North America on October 18th, 2019.

    Getting Started

    This blog will use parameters to create functions in Power Query. Some experience using Power Query editor may be helpful, specifically:
    – Knowledge of tools such as merge and append queries
    – Familiar with query steps and the formula bar

    For a detailed look at parameters or if you need to brush up, check out this post on parameters.

    Before starting, you will need to ensure the formula bar in the query editor is open.

    Open the Power Query Editor by Clicking the Edit Queries on the Home ribbon of Power BI desktop. Then, go to the View ribbon in the Query Editor and make sure the check box for Formula Bar is turned on.

    Create the Parameter

    First, Create a Parameter. This is a value that we can change and feed into our query, in this case the stock symbol.

    In the power query window, under the Home ribbon, Click the bottom half of the Manage Parameters button. From the drop down Select the option New Parameter.

    In the Name section, enter the text StockSymbol (without spaces – this makes it much easier to reference later). Give it a description if you like. If you share this report other people can read the description to understand what the parameter is being used for. Change the Type field to Text. Enter MSFT in the Current Value input box. By the way, MSFT is the stock symbol for Microsoft.

    Making the Query

    Now we have set up a parameter, we can use it to pull in some data. The data source is going to be a web URL. In Power Query editor window, Click the Home ribbon and the button Get Data. Select the item Web in the drop down. In the popup dialogue, Click on the button labeled Advanced.

    You’ll notice this brings up a dialog called URL Parts. This allows us to break down the URL into parts. We can easily change individual parts of the URL using this dialog. It will then concatenate it all back together in the order it is entered. Our URL to connect to Yahoo! for a single stock will be split into three parts.

    1. The base URL, which points us to Yahoo! Finance website and the correct data
    2. The stock symbol, in this case will be our parameter
    3. Our other settings (range and interval). These could also be changed in Power BI with a parameter, but we do not want to for this example

    The actual URL parts:

    1 https://query1.finance.yahoo.com/v8/finance/chart/
    2 {StockSymbol}
    3 ?range=5y&interval=1d

    In the open dialogue box, first Click the button Add part. This will add a new box. Locate the first window and Enter part 1 of the URL. In the second box, Change the abc symbol to a parameter. Make sure Stock Symbol is selected. In the third box, enter part 3 of the URL. We’re setting the range to 5y (5 years of data) and the interval to 1d (daily). You can change these if you want at a later time.

    Note: It is important to remember that Stock Symbol is a parameter – change the symbol to parameter and select from the drop down. Do not type Stock Symbol into the box.

    Now Hit the button labeled OK. The request will be sent and returned to us in a JSON format.

    Rename the query Stock Value. You can edit the name above the Applied Steps section on the right.

    Making the Query Branch

    The data returned is a little messy and not in the best format. We need to drill down and pull out the appropriate bits of information. Start by drilling down to the correct information. To drill down, Click the underlined result part in the following order:
    Chart: Record
    Result: List
    1: Record

    Your screen should look like the image below. If it doesn’t, simply delete the navigation step and start again.

    Here, we are presented with three options of paths to drill down further:

    Meta: holds some info about the stock, as well as the timeframe and granularity we chose
    Timestamp: a list of the dates in the range we selected
    Indicators: this holds the price information of stock

    Right now, the dates and the price are in two different lists. The price information is another layer down than the dates which makes this more complicated. Extracting these individually would result in a list of random prices and a big list of dates – not helpful if these two pieces of information are not together.

    To solve, we will create a Query Branch. The branch will split our query at this step into two paths. One will retrieve the dates, the other the prices. Then we will merge these branches back together to get the dates and prices in the same table.

    To start this branch Right Click on the Navigation Step, then Select the option in the drop-down menu Insert Step After. This will reference the previous step and show the same data. Our newly created set is the start of the branch. Rename this new step StartBranch.

    Note: the reason for this reference is that the “Navigation” step is not really a step at all. It is actually a collection of steps that Power Query editor groups together. You cannot reference “Navigation”, which will be needed later. You’ll see you cannot rename the Navigation step and if you open the advanced editor you can see the breakdown of individual steps. Another option is two perform any action after the Source step, before you drill down. This will cause Power Query to list each drill down step individually.

    Branch 1: Dates

    Our first branch we will pull the dates.

    Click on timestamp: List.
    This will drill down to a list of dates, but they are stored in a UNIX format. UNIX date format is the number of seconds past January 1, 1970 (midnight UTC/GMT), not counting leap seconds. Converting this is quite easy but will take a couple of steps.

    First convert the list to a table so we can perform transformations. Click on Transform ribbon. Select the button To Table. Next, under the Add Column ribbon Click the button Custom Column. Change the name to Date and use the following formula in the formula window:

    25569 + ( [Column1]/60/60/24 )

    Then Select the Date column. Click the Transform ribbon. Under the Data section, Select the Date format. Note: do not select the Date/Time.

    Now we have the date but need to preserve its order. This can be solved by adding an index. Go to the Add Column ribbon, Click the little Drop down on the right half of the Index Column button. Select the option From 0 from the drop down menu. Remove the column labeled Column1, as it is not needed anymore. To do this, Right Click on Column1 and select the option Remove from the drop down menu.

    This finishes the branch for the dates. Rename this step EndBranchDate by Right Clicking the step in the APPLIED STEPS and Clicking rename.

    Branch 2: Prices

    Now we need to get the information for the stock prices. Right Click EndDateBranch and Click the option Insert Step After to add the start of the branch. By checking the formula, we can see it says

    =EndBranchDate

    This step is doing is referencing the step before it, EndBranchDate. It is duplicating the output of this step. We need to get back to the StartBranch step in order to start the second branch. Change the value in the formula bar from = EndBranchDate to = StartBranch.

    This now loads us back to this step to drill down to the stock prices. We will use the adjusted close – this is the stock price at the end of the day after adjusting for dividends. Here we need to drill down to this information, by drilling in the following order:

    Indicators: Record
    adjclose: List
    1: Record
    adjclose: List

    Next, Covert our list to a Table. see above for this step. Here we have the list of prices and again need to preserve the order with an index column. Go to the ribbon labeled Add Column. Click the Index Column and select From 0 in the drop down.

    This is the end of this step, so Rename it EndBranchPrice.

    To summarize the query so far:

    • Pulled the information for a MSFT stock for 5 years on a daily basis.
    • Drilled down to the dates, converted them to a better format and added an index to preserve order.
    • Revert to an earlier step.
    • Drilled down to the daily prices and added an index column.

    Merging the Branches

    This leaves two separate tables, but it is only possible to output one of these results. We will need to add a final step to merge these two branches into one table.

    On the Home Ribbon, Click the drop down arrow on the Merge Queries button. Then Select the option Merge Queries. This brings up the merge screen. Merge the query with itself. On the bottom half of the merge, Select StockValue (current). Click on the Index column for both top and bottom.

    Clicking OK, will merge the data to itself. This is the formula in the formula bar:

    = Table.NestedJoin(EndBranchPrice, {"Index"}, EndBranchPrice, {"Index"}, "EndBranchPrice", JoinKind.Inner)

    This step uses the Table.NestedJoin formula, which has 6 arguments filled in:

    Table.NestedJoin(table1 as table, key1 as any, table2 as any, key2 as any, newColumnName as text, optional joinKind )

    In our example, table1 and table2 is the same (EndBranchPrice). This makes sense as we joined it to itself. You will notice that when joining from the same query, the table argument references a step in that query (EndBranchPrice). We really want to join EndBranchPrice to EndBranchDate. We can simply change the second table in the formula bar to EndBranchDate:

    Change:

    To:

    Now, we are joining the EndBranchPrice to the step EndBranchDate. These both have a column named index that we added, which will join our data in the correct order.

    Expand the merged table by Clicking the Expand button on the column name. The settings will just Select the Date and Deselect the option to Use original column name as prefix.

    Remove the index column as it is not need this anymore. That completes our query with the branch.

    Enabling Publish to the Service

    If we want to publish this to the service (app.powerbi.com), we will need to make a small edit to our URL. The service will not allow parameters in the base URL. To get around this, we can split our URL using an option in Web.Contents called RelativePath. After Clicking on the Source in the applied steps window, Edit the URL as follows:

    From:

    To:

    Make sure the brackets are correct. Here is the code you can copy and paste into the formula bar:

    = Json.Document(
    Web.Contents("https://query1.finance.yahoo.com/v8/finance/chart/"
    ,[RelativePath = StockSymbol  & "?range=5y&interval=1d"]
    ))

    Now we have changed the URL, we need to make a change to the settings. This is because Power BI will try and check the base URL (https://query1.finance.yahoo.com/v8/finance/chart) before it runs the query and adds the second part in RelativePath. This isn’t a valid URL on its own, so it will fail.

    To do this, publish the report to the service, and Navigate to the dataset settings. This is found in the service by Clicking the ellipsis in the top right, then the option called Settings in the drop down.

    You should be in the tab called Datasets. Then Navigate to the published dataset. Under the option titled Data source credentials, next to Web, Click the option to Edit Credentials. Make sure to check the option to Skip connection test.

    If you are interested in learning more, check out Chris Webb’s blog on this here: Using The “Skip Test Connection” Option For Power BI Data Sources To Avoid Scheduled Refresh Failures

    Creating the Function

    This query uses a parameter which enables us to can convert it to a function. To do this, right click on the query in the Queries pane on the left and select make function.

    Now we have a function where we can input any stock symbol and return a list of daily prices. To check multiple stocks, you can add your function to any list of stock symbols. This can be found in Add Column ribbon. Then Clicking the button Invoke Custom Function. This will return a table for each row. Before expanding, it is important to handle errors, otherwise it could break the query. One option is to Right Click the column header, and select the Replace Errors option, and Type the text null.

    Performance Considerations

    While this query will quickly return single stocks, adding multiple stock will send a different query for each stock. Make sure you design the correct solution to what you are trying to achieve, and check out this article on API considerations.

    Final Result

    For those who like M code, here is the final function. You can copy and paste this directly into the advanced editor (See this article on how to do this).

    let
        Source = Json.Document(Web.Contents("https://query1.finance.yahoo.com/v8/finance/chart/"
    
    ,[RelativePath = #"Stock Symbol"  &"?range=5y&interval=1d"]
    
    )),
        chart = Source[chart],
        result = chart[result],
        result1 = result{0},
        Branch = result1,
        timestamp = Branch[timestamp],
        #"Converted to Table" = Table.FromList(timestamp, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
        #"Added Index" = Table.AddIndexColumn(#"Converted to Table", "Index", 0, 1),
        #"Added Custom" = Table.AddColumn(#"Added Index", "Date", each 25569 + ( [Column1]/60/60/24 )),
        #"Removed Columns" = Table.RemoveColumns(#"Added Custom",{"Column1"}),
        EndBranchDate = Table.Buffer(Table.TransformColumnTypes(#"Removed Columns",{{"Date", type date}}) ),
        Custom1 = Branch,
        indicators = Branch[indicators],
        adjclose = indicators[adjclose],
        adjclose1 = adjclose{0},
        adjclose2 = adjclose1[adjclose],
        #"Converted to Table1" = Table.FromList(adjclose2, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
        EndBranchPrice = Table.Buffer(Table.AddIndexColumn(#"Converted to Table1", "Index", 0, 1) ),
        #"Merged Queries" = Table.NestedJoin(EndBranchPrice, {"Index"}, EndBranchDate, {"Index"}, "EndBranchPrice", JoinKind.Inner),
        #"Expanded EndBranchPrice" = Table.ExpandTableColumn(#"Merged Queries", "EndBranchPrice", {"Date"}, {"Date"}),
        #"Removed Columns1" = Table.RemoveColumns(#"Expanded EndBranchPrice",{"Index"})
    in
        #"Removed Columns1"

    Visually splitting the steps, the query can be visualized like this:

    If you like the content from PowerBI.Tips please follow us on all the social outlets to stay up to date on all the latest features and free tutorials.  Subscribe to our YouTube Channel.  Or follow us on the social channels, Twitter and LinkedIn where we will post all the announcements for new tutorials and content.

    Introducing our PowerBI.tips SWAG store. Check out all the fun PowerBI.tips clothing and products:
    Store Merchandise

  • Custom Sort Order Within a DAX Measure

    Custom Sort Order Within a DAX Measure

    This post will answer how to sort a measure that returns text values to a custom order, without affecting other columns. It will utilize the DAX functions of REPT() and UNICHAR(8203) – a Zero width space.

    The requirements

    I’ve been working at a florist! In this example, I have been in charge of looking after four plants, named A, B, C and D. The florist owner is a big Power BI fan, and asked me to measure how much water I have been giving them a day to put in a report. They need at least 20ml to survive, but over 50ml will stop them growing as well.

    Create a table with the flowing:
    The flowers get under 20 ml, label as Bad.
    When the flowers get 20 – 50 ml, label as Good.
    Finally, if the flowers receive over 50 ml, label as Warning.
    I’ve been asked to show them in order of Bad, Warning then Good. This is vital so the plants needing attention are at the top of the table.

    Creating the table

    Here is the measure I create:

    Adding this to a table:

    Now comes the question, how can I order this to put Bad and Warning together? If I order by Water Target measure, this will be alphabetical. Sorting by WaterIntake can not give me the correct order either.
    One option would be to make a conditional column and use the “Sort by Column”. However, this may be a complicated calculation, especially on more complex measures. In addition it will sort every visual by this column, when I only want to sort in this one table.

    Creating the custom sort

    My solution? Make use of the UNICHAR() function. For those unaware of this function, UNICHAR() can return characters based on their UNICODE number. This can include more text characters not included on the standard keyboard.

    A character that can help is UNICHAR(8203). This is a “Zero width space”. This is a space that has not width, so it is essentially invisible and will not be visible in the measure. The Zero width space is still recognized as a character by DAX. Spaces come before any letter in the alphabet. Two spaces comes before one, and so on.

    The second function I will utilize is REPT(). REPT() or replicate, simply repeats text multiple times. It takes two arguments, the text and the times to repeat.

    For example: REPT( "Hi", 3 ) will return the text "HiHiHi"

    To change the sort order, I will repeat the Zero width space in front of the text. The text I want to appear first will have the space repeated the most amount of times. This will put it first in an alphabetical list. I will use the & symbol to concatenate the Zero width spaces and the text.

    Now, “Bad” has the Zero width space repeated three times in front of it. This now puts it first in an alphabetical list. Warning has the Zero width space repeated twice, putting it second. “Good” has it once putting it third.

    Applying the sort

    Now I can arrange my table by Water Target (alphabetical), in an ascending order:

    And success! I’ve added a custom sort to my text measure, without making any other measures or columns.

    If you like the content from PowerBI.Tips please follow us on all the social outlets to stay up to date on all the latest features and free tutorials.  Subscribe to our YouTube Channel.  Or follow us on the social channels, Twitter and LinkedIn where we will post all the announcements for new tutorials and content.

    Introducing our PowerBI.tips SWAG store. Check out all the fun PowerBI.tips clothing and products:
    Store Merchandise

  • Dynamic Visuals using Buttons

    Dynamic Visuals using Buttons

    Sometimes, we want the users to see different metrics, but do not want to take up too much space on our page. The scenario we are going to walk through is how to build just one visual (in this case a bar graph). It will include a toggle that allows the user to select their desired calculation, either the sum of Volume, Dollars or Margin.

    Final Solution

    With buttons, we can change specific visuals on a page. Recently, with the release of conditional formatting on titles and backgrounds, we have some new methods to make this easier for the report author and cleaner for the report consumer.

    The Build

    Before we start, turn on the selection pane and bookmark pane. They can be turned on by clicking on the View ribbon and checking the correct boxes.

    First, we’re going to create our control table. This will be a disassociated table. This table should not have any relationships to any of the other tables in our model. We just need to enter a numeric ID and a description of what we want.  Click on the Enter Data button found on the Home ribbon. Enter the following data as shown. Click the OK button to close the Create Table dialog box.

    Now that’s set up, we can write our measure. This measure will see what is selected in the Number_ID column of our control table, then return the appropriate calculation. Use a switch statement to select the correct calculation. Create the following measure:

    Selected Calculation = 
    SWITCH(
      SELECTEDVALUE(Control[Number_ID])
       ,1,SUM(Sales[Volume])
       ,2,SUM(Sales[Dollars])
       ,3,Sum(Sales[Margin])
       ,SUM(Sales[Volume])
    )

    Note: See there is a default value listed in the switch statement. The default calculation means that if nothing is selected, SUM( Sales[Volume] ) will be returned. The default value is represented by the last property in the switch statement.

    Time to set up our visual. Add a bar graph with Category on the axis and the new measure, Selected Calculation, in the values fields. Then add a slicer for the Number_ID column. The Number_ID column comes from the control table we added earlier.            

    Switching the slicer can now change the graph to show the different calculations.

    The next stage is to add three buttons to the top of the graph. In the Home tab of the ribbon, click Buttons and select Blank. Make sure the outline colors and outline width match on all objects, Buttons and chart outline.

    Tip: Make sure you label your buttons in the Selection Pane. The selection pane can be turned on by clicking on the View ribbon and checking the box labeled Selection Pane. To Change the name of the button, double click the name listed in the Selection Pane. Giving a title (such as Button_Volume) will make it easily to see what visual items are on the page.

    After this, it’s time to add the bookmarks.

    The bookmark pane can be turned on by clicking on the View ribbon and checking the box labeled Bookmark Pane.

    Step 1:

    • Select a value of 1 in the Number_ID slicer.
    • Select the slicer (and only the slicer) in the Selection pane.
    • Click “Add Bookmark” in the Bookmarks pane.

    Step 2:

    • In the Bookmarks pane, right click the bookmark and rename it to Select 1.
    • Right click again, and untick “Display” and “Current Page”. Select “Selected Visuals”.

    Now repeat step 1 and step 2, but do so with the values of 2 and 3 from Number_ID slicer. Name these bookmarks Select 2 and Select 3. You should finish with three bookmarks, each that filters Number_ID to a different value. You can test the bookmarks by clicking on them once in the bookmark pane.

    On Button_Volume, assign the Select 1 bookmark (as Number_ID 1 refers to volume). To do this, click on Button_Volume in the selection pane. In the visualizations pane for this button, go to the property named “Action”. Turn it on, change the type to bookmark, and choose Select 1 in the dropdown.

    Repeat for Button_Dollars and assign Select 2. Then for Button_Margin and assign Select 3. Now the buttons can change the graph, but it’s a bit hard to see what is selected.

    Add Conditional Formatting

    This is where conditional formatting can help us! Select Button_Volume in the selection pane. Then in the visualizations pane, turn on the background property, select the ellipsis and click conditional formatting

    Here’s the settings we want:

    This is going to apply a rule if the Number_ID selected is 1, to give the button a blue background. As there are no other rules, any other number selected will default to the white.

    Now, apply the same steps to the other two buttons, but make the rule “If value is 2” for Dollars, and “If value is 3” for Margin.

    To tidy up, hide the slicer and turn the visual headers of all buttons off. You can click on the eye next to the slicer in the selection pane to hide it.

    Turn the visual headers off by clicking the button, then in the visualizations pane.

    Great! Now the tab shows the selected button and correct measure:

    To make it even clearer, apply conditional formatting to the title of the graph. On the graph, open conditional formatting. Set it to field value and use the type field in the control panel.

    Using this control table allows for greater flexibility. We can add more calculations, easily edit them or even sync across pages, all without having to re-record any bookmarks.

    If you like the content from PowerBI.Tips please follow us on all the social outlets to stay up to date on all the latest features and free tutorials.  Subscribe to our YouTube Channel.  Or follow us on the social channels, Twitter and LinkedIn where we will post all the announcements for new tutorials and content.

    Introducing our PowerBI.tips SWAG store. Check out all the fun PowerBI.tips clothing and products:
    Store Merchandise