Filter a column using IF statement in powerquery - excel

I'm having trouble filtering my columns in powerquery. I'm using a parameter to filter my 'Island' NI or SI, however I am struggling to find a way to keep all the data when no parameter is inputted.
I would like to leave the column full (NI and SI) if no input is provided. I've added two pictures on imgur below. I'm fairly new here.
I've tried using an if statement but couldn't figure out the right piece of code or if it's even possible to do so.
Thanks
enter image description here. enter image description here

Note: see the very end for a version that does not need to hard-code values.
First let's test if the search filter is a valid value. If not, don't filter anything.
if IslandChoice is "NI" or "SI"
then filter using IslandChoice
else
show everything
The other route is
if IslandChoice is not "NI" and is not "SI"
show everything
else
filter using IslandChoice
It depends if you want invalid values to show nothing, or everything.
First check "is this filter a valid choice?
[Island] = "SI" or [Island] = "WI"
If either one is true, then it's good. If both are false, then it's an invalid key.
filter_if_valid = Table.SelectRows(
Source,
(row) =>
if IslandChoice = "SI" or IslandChoice = "NI" then
row[Island] = IslandChoice
else
true
)
How does it work, and where is each ?
each is a normal function. It's sugar, a shortcut that declares a function which has a single arugment. (If you need more, use a function declaration)
see more: Details on the grammar of each is in the docs
each implicitly creates the variable named _
Because each abstracts a couple of things, it's harder to tell what's happening.
each creates a variable named _ which references the current row.
each declares a function without the arguments part
Functions follow the form
(arguments) => return_expression
Written using each it removes the (arguments) => portion.
each return_expression
return_expression can be a simple [Column] = 10 test. (Or it can create many local variables using a let .. in expression inside the function).
These statements are all exactly equivelent functions
each [Island] = IslandChoice
each _[Island] = IslandChoice
(_) => _[Island] = IslandChoice
(row) => row[Island] = IslandChoice
They all
declare a function without a name
accept exactly 1 argument
read the column [Island] in the current table row
returns true if they are equal, else false
original filter
after replacing each you had:
always_filter_table = Table.SelectRows(
Source,
(row) =>
row[Island] = IslandChoice
),
new filter
filter_if_valid = Table.SelectRows(
Source,
(row) =>
if IslandChoice = "SI" or IslandChoice = "NI" then
row[Island] = IslandChoice
else
true
)
filter without hard-coding values
This will run alone without any requirements. Create a new -> blank query
let
Source = #table(
{"Island", "Point Of Connection"},
{
{"SI", "STK0331"},
{"SI", "TIM0111"},
{"NI", "ZEJ2395"},
{"NI", "XER9345"},
{"WI", "QXF9785"}
}
),
is_valid_filter = (value as any, valid_values as list) =>
List.Contains( valid_values, value ),
// use one or the other if you want all existing values to be valid
static_valid_filters = {"SI", "NI"},
dynamic_valid_filters = List.Distinct(
Source[Island]
),
IslandChoice = "NI",
// if filter is a valid value, filter using it.
// otherwise show all.
filtered_when_valid = Table.SelectRows(
Source,
(row) =>
if is_valid_filter(
IslandChoice, static_valid_filters
) then
row[Island] = IslandChoice
else
true
)
in
filtered_when_valid

Related

M (PowerQuery), set the value of a non-primitive variable in a let statement

I'm writing a custom M Language (PowerQuery in Excel) function to query a RESTful interface. This interface has a large number of optional parameters.
Starting with a simple case- I handle an optional limit passed as a simple (primitive) value as follows-
/*
* RESTful API Get from the named endpoint
*/
(endpoint as text, optional limit) =>
let
// query limit
// If limit is supplied as a number, it will be converted to text
// If limit is not supplied it will be set to the value "1000"
limit = if limit <> null then Text.From(limit) else "1000",
As the full API has many paramaters I wanted to use a Record to pass them to the function, but then I realised I don't know how to persuade M to write the default values into the parameter record.
I tried a couple of options.
Direct access-
(endpoint as text, optional params as record) =>
let
params[limit] = if (params[limit] = null) then "1000",
the result is a syntax error-'Token equal expected'
Merging the new value of limit as a Record with "&"
(endpoint as text, optional params as record) =>
let
params = params & if params[limit] = null then [limit = "1000"] else [],
result syntax error-'Token Literal expected'
I'm clearly missing something about the syntax rules for let statements, I know I need a variable = value assignment, and it looks as if putting anything other than a plain variable name on the LHS to write elements inside a structured value is not allowed, but i'm not sure how to acieve this otherwise?
Not sure exactly what you want here, but to create a List of Records where some Records have a default parameter and others do not, you could try something like:
(newParams as record) =>
let
default = [limit=1000, param2=2, param3=3],
final = Record.Combine({default, newParams})
in
final
With regard to Record.Combine, the beauty is that the right hand record will override the left hand record if both are present; and it will just add to it if nothing is present.
So something like:
let
Source = [limit=400, param3="x", param7=246],
conv = fnParams(Source)
in
conv
=>
Depending on the required format of your output string, you can build it using List.Accumulate. eg:
let
Source = [limit=400, param3="x", param7=246],
conv = fnParams(Source),
list = List.Accumulate(List.Zip({Record.FieldNames(conv), Record.ToList(conv)}), "",
(state,current) =>state & "&" & current{0} & "=" & Text.From(current{1}) )
in
list
=> &limit=400&param2=2&param3=x&param7=246

How to define two way for run code in filter method

I deploy a filter search in react that fetch API, that indicate names entered in the search input, but I want to filter in one of 2 ways.
1way that indicate of name,
2way that indicate through entered symbol
I don't know how I can define coin.symbol in filter method
const filterCoins = currentCoins.filter(coin =>
coin.name.toLowerCase().includes(search.toLowerCase())
);
Generally I want define below line code inside filter method beside coin.name that display the result when users search symbol or name.
coin.symbol.toLowerCase().includes(search.toLowerCase())
Return the result of both conditions.
const filterCoins = currentCoins.filter(({ name, symbol }) =>
name.toLowerCase().includes(search.toLowerCase()) ||
symbol.toLowerCase().includes(search.toLowerCase())
);
Or a bit more succinct and DRY, place both values into an array as check that at least one of them meets the conditional test.
const filterCoins = currentCoins.filter(({ name, symbol }) =>
[name, symbol].some(el => el.toLowerCase().includes(search.toLowerCase()))
);

Power Query (M) _ Dynamically update a column list for List.Sum function

I'm not sure if even possible but the goal is to dynamically update a query based on the user selecting a date. I have a table in my Excel file while updates a value which feeds to PeriodString variable (below)
/*Parameter name = PeriodString */
let
Source = Excel.CurrentWorkbook(){[Name="PeriodString"]}[Content],
StrPeriod = Source[Value]{0}
in
StrPeriod
The part of the code I want to update is the [ ..months selected ].
=List.Sum({[FYOpening],[January],[February],[March],[April],[May]})
With the below variable
=List.Sum({PeriodStr})
I tried using Table.Column as I realize I have to convert the value to a list of selectable columns but I cant' get it to work.
=List.Sum({Table.Column(PeriodString{0},PeriodString[0])})
Expression.Error: We cannot convert the value "[FY Opening],[Januar..." to type List.
Details:
Value=[FY Opening],[January],[February],[March],[April],[May]
Type=[Type]
Let me know if possible / alternatives.
If you need exactly value like "[Col1],[Col2],[Col3]" for PeriodString, then use such code:
let
Source = #table({"a".."e"},{{1..5}, {6..10}}),
PeriodString = "[b],[d],[e]",
sum = Table.AddColumn(Source, "sum", each List.Sum(Expression.Evaluate("{"&PeriodString&"}", [_=_])))
in
sum
I'd prefer to use PQ list instead:
let
Source = #table({"a".."e"},{{1..5}, {6..10}}),
list = {"b","d","e"},
sum = Table.AddColumn(Source, "sum", each List.Sum(Record.ToList(Record.SelectFields(_, list))))
in
sum

Power Query: Function to search a column for a list of keywords and return only rows with at least one match

I am making a simple Google-like search function in Power Query.
Let's say I have a column called Description in a table called Database. The user then inputs some search queries like "dog, cat, animals". I want to filter Database for rows that contain at least one of these keywords. They keywords can change each time, depending on what the user types in a named range in Excel.
I know you can filter a column in Power Query for multiple keywords, like this:
FilterRows = Table.SelectRows(LastStep, each Text.Contains([English], "dog") or Text.Contains([English], "cat")),
but those keywords are static, and the column is also static. I want to be able to control both the keywords and the column name as variables. I think I need to write a function but I am not sure how to start.
Your question requires several moving parts.
First, I would get the keywords from a named range "Keywords" into a table like this:
{KeywordTbl}
let
GetKeywords = if Excel.CurrentWorkbook(){[Name="Keywords"]}[Content]{0}[Column1] = null then null else Text.Split(Excel.CurrentWorkbook(){[Name="Keywords"]}[Content]{0}[Column1], ", "),
ConvertToTable = Table.FromList(GetKeywords,null,{"Keywords"})
in
ConvertToTable
Secondly, store the column name where you want to search in an Excel named range called "ColName". Then pull the named range into Power Query like this:
{ColName}
let
GetColName = Excel.CurrentWorkbook(){[Name="ColName"]}[Content]{0}[Column1]
in
GetColName
Then I would write a function that takes 4 variables, the table and column you want to look in, and the table and column containing the keywords:
{SearchColForKeywords}
(LookInTbl as table, KeywordTbl as table, LookInCol as text, KeywordCol as text) =>
let
RelativeMerge = Table.AddColumn(LookInTbl, "RelativeJoin",
(Earlier) => Table.SelectRows(KeywordTbl,
each Text.Contains(Record.Field(Earlier, LookInCol), Record.Field(_, KeywordCol), Comparer.OrdinalIgnoreCase))),
ExpandRelativeJoin = Table.ExpandTableColumn(RelativeMerge, "RelativeJoin", {KeywordCol}, {"Keywords found"}),
FilterRows = Table.SelectRows(ExpandRelativeJoin, each [Keywords found] <> null and [Keywords found] <> ""),
// Concatenate multiple keyword founds line into one line
GroupAllData = Table.Group(FilterRows, {"Word ID"}, {{"AllData", each _, type table [First column=text, Second column=text, ... your other columns=text]}}),
AddCol = Table.AddColumn(GroupAllData, "Keywords found", each [AllData][Keywords found]),
ExtractValues = Table.TransformColumns(AddCol, {"Keywords found", each Text.Combine(List.Transform(_, Text.From), ", "), type text}),
DeleteAllData = Table.RemoveColumns(ExtractValues,{"AllData"}),
MergeQueries = Table.NestedJoin(DeleteAllData, {"Word ID"}, FilterRows, {"Word ID"}, "DeleteAllData", JoinKind.LeftOuter),
ExpandCols = Table.ExpandTableColumn(MergeQueries, "DeleteAllData", {"First Col name", "Second col name", ... "Your Other column names here"}),
DeleteKeywordsFound = Table.RemoveColumns(ExpandCols,{"Keywords found"})
in
DeleteKeywordsFound
FYI, half of this function has been developed by a user named lmkeF on PowerBI community. The full discussion is here. I merely improved on his solution.
Finally, I will use that function in another query like this:
StepName = SearchColForKeywords(MainTbl, KeywordTbl, ColName, "Keywords"),
You may customize the 4 variable names.

How to use variable column name in a function for other functions?

I have written two functions which take date as input and I'm gonna use them on multiple queries. Instead of doing manual work every time (call both functions, filter rows where first function returns True, expand record of the second function to columns, delete first function column) I thought I'd write another function that takes names of the table and the column with dates as parameters to automatize that process. My current table-based function works if I include specific column's date in the code, but those names will be different between different queries(tables).
Here's the table function's code:
(t as table) =>
let
FunctionFilter = Table.AddColumn(t, "DateFilter", each DateFilter([myDate2])),
FunctionPeriods = Table.AddColumn(#"FunctionFilter", "TimePeriods", each TimePeriods([myDate2])),
ExpandPeriods= Table.ExpandRecordColumn(FunctionPeriods, "TimePeriods", {"Year", "Quarter", "Month", "WeekMon", "WeekTue", "Day"},
{"Year", "Quarter", "Month", "WeekMon", "WeekTue", "Day"}),
TrueDate = Table.SelectRows(ExpandPeriods, each ([DateFilter] = true)),
DeleteDateFilter = Table.RemoveColumns(TrueDate,{"DateFilter"})
in
DeleteDateFilter
My only problem is inserting a variable column name in place of [myDate2] here:
FunctionFilter = Table.AddColumn(t, "DateFilter", each DateFilter([myDate2])),
FunctionPeriods = Table.AddColumn(#"FunctionFilter", "TimePeriods", each TimePeriods([myDate2])),
Using Table.Column(t,[column name]) returns a list instead of a date, which causes called date functions to throw a type mismatch error.
You may use such technique:
// Table
let
Source = #table(3,List.Zip({{"a".."d"},{1..4},List.Numbers(10,4,10)})),
fn = fn(Source, "Column3")
in
fn
// fn
(tbl as table, col as text) =>
let
i = Table.AddIndexColumn(tbl, "i", 0, 1),
add = Table.AddColumn(i, "new", each Table.Column(i, col){[i]}*10),
del = Table.RemoveColumns(add, "i")
in
del

Resources