Quantcast
Channel: Chris Webb's BI Blog: Power Query
Viewing all articles
Browse latest Browse all 248

Multiselect, Filtering And Functions In Power Query

$
0
0

If you’re a Power Query enthusiast you’re probably already comfortable with creating functions and passing values to them. However in some scenarios you don’t want to pass just a single value to a parameter, you want to pass multiple values – for example if you are filtering a table by multiple criteria. What’s the best way of handling this in Power Query?

Imagine that you wanted to import data from the DimDate table in the SQL Server Adventure Works DW database. It’s a pretty straightforward date dimension table as you can see:

image

Imagine also that you didn’t want to import all the rows from the table but just those for certain days of the week that the user selects (filtering on the EnglishDayNameOfWeek column).

The first problem is, then, how do you allow the user to make this selection in a friendly way? I’ve already blogged about how the function parameter dialog can be made to show ‘allowed’ selections (here and here) but this only allows selection of single values. One solution I’ve used is to create an Excel table – sourced from a Power Query query of course – and then let users select from there.

In this case, the following query can be used to get all the distinct day names:

let
    Source = Sql.Database("localhost", "adventure works dw"),
    dbo_DimDate = Source{[Schema="dbo",Item="DimDate"]}[Data],
    #"Removed Other Columns" = Table.SelectColumns(dbo_DimDate,
																							{"DayNumberOfWeek", "EnglishDayNameOfWeek"}),
    #"Sorted Rows" = Table.Sort(#"Removed Other Columns",
                                    {{"DayNumberOfWeek", Order.Ascending}}),
    #"Removed Duplicates" = Table.Distinct(#"Sorted Rows"),
    #"Removed Columns" = Table.RemoveColumns(#"Removed Duplicates",
																			{"DayNumberOfWeek"}),
    #"Added Custom" = Table.AddColumn(#"Removed Columns", "Selected", each "No")
in
    #"Added Custom"

Nothing much interesting to say about this apart from that it was all created in the UI, it shows the day names in the correct order, and it has an extra column called Selected that always contains the value “No”. The output table in Excel looks like this:

image

The Selected column is going to allow the end user to choose which days of the week they want to filter the main table by. Since “Yes” and “No” are going to be the only valid values in this column you can use Excel’s Data Validation functionality to show a dropdown box in all of the cells in this column that allows the user from selecting one of those two values and nothing else.

image

image

Once the user has selected “Yes” against all of the day names they want to filter by in the Excel table, the next step is to use this table as the source for another Power Query query. To be clear, we’ve used Power Query to load a table containing day names into an Excel table, where the user can select which days they want to filter by, and we then load this data back into Power Query. This second query (called SelectedDays in this example) then just needs to filter the table so it only returns the rows where Selected is “Yes” and then removes the Selected column once it has done that:

let
    Source = Excel.CurrentWorkbook(){[Name="DistinctDates"]}[Content],
    #"Filtered Rows" = Table.SelectRows(Source, each ([Selected] = "Yes")),
    #"Removed Columns" = Table.RemoveColumns(#"Filtered Rows",{"Selected"})
in
    #"Removed Columns"

image

This query doesn’t need to be loaded anywhere – but it will be referenced later.

With that done, you need to create a function to filter the DimDate table. Here’s the M code:

(SelectedDays as list) =>
let
    Source = Sql.Database("localhost", "adventure works dw"),
    dbo_DimDate = Source{[Schema="dbo",Item="DimDate"]}[Data],
    #"Filtered Rows" = Table.SelectRows(dbo_DimDate,
                             each List.Contains(SelectedDays,[EnglishDayNameOfWeek]) )
in
    #"Filtered Rows"

The thing to notice here is the condition used in the Table.SelectRows() function, where List.Contains() is used to check whether the day name of the current row is present in the list passed in through the SelectedDays parameter.

The final step is to invoke this function and pass the column from the query containing the selected days to it. There is a bit of UI sugar when you invoke a function with a parameter of type list that I blogged about recently. In this case when you invoke the function you just have to pass the pass it the EnglishDayNameOfWeek column from the SelectedDays query.

image

Here’s what the code for the query that invokes the function looks like:

let
    Source = DimDate(SelectedDays[EnglishDayNameOfWeek])
in
    Source

And of course, when you run the query and output the results to a table, you get the DimDate table filtered by all of the days of the week you have selected:

image

To change the output the user just needs to change the selected days and then refresh this last query.

In case you’re wondering, this query does get folded back to SQL Server too. Here’s the SQL generated by Power Query:

select [$Ordered].[DateKey],
    [$Ordered].[FullDateAlternateKey],
    [$Ordered].[DayNumberOfWeek],
    [$Ordered].[EnglishDayNameOfWeek],
    [$Ordered].[SpanishDayNameOfWeek],
    [$Ordered].[FrenchDayNameOfWeek],
    [$Ordered].[DayNumberOfMonth],
    [$Ordered].[DayNumberOfYear],
    [$Ordered].[WeekNumberOfYear],
    [$Ordered].[EnglishMonthName],
    [$Ordered].[SpanishMonthName],
    [$Ordered].[FrenchMonthName],
    [$Ordered].[MonthNumberOfYear],
    [$Ordered].[CalendarQuarter],
    [$Ordered].[CalendarYear],
    [$Ordered].[CalendarSemester],
    [$Ordered].[FiscalQuarter],
    [$Ordered].[FiscalYear],
    [$Ordered].[FiscalSemester]
from
(
    select [_].[DateKey],
        [_].[FullDateAlternateKey],
        [_].[DayNumberOfWeek],
        [_].[EnglishDayNameOfWeek],
        [_].[SpanishDayNameOfWeek],
        [_].[FrenchDayNameOfWeek],
        [_].[DayNumberOfMonth],
        [_].[DayNumberOfYear],
        [_].[WeekNumberOfYear],
        [_].[EnglishMonthName],
        [_].[SpanishMonthName],
        [_].[FrenchMonthName],
        [_].[MonthNumberOfYear],
        [_].[CalendarQuarter],
        [_].[CalendarYear],
        [_].[CalendarSemester],
        [_].[FiscalQuarter],
        [_].[FiscalYear],
        [_].[FiscalSemester]
    from [dbo].[DimDate] as [_]
    where [_].[EnglishDayNameOfWeek] in ('Monday', 'Wednesday', 'Friday')
) as [$Ordered]
order by [$Ordered].[DateKey]

Notice that the Where clause contains an IN condition with all of the selected days.

You can download the example workbook for this post here.



Viewing all articles
Browse latest Browse all 248

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>