0

Background: I'm trying to create query that incorporates dynamic column names such that if a user changes the name of a column within a table, the logic still follows for any transformations.

The problem is quite straightforward but difficult to explain in just a few sentences. I have tried to break down everything I've done to easily show the problem I am having. If obvious please just skip to the problem at the end.

Demo of Dynamic Columns: To begin making the column Names dynamic, I use:

DynamicNameHeader = Table.ColumnNames(Source)

This creates a list of the column names from the original table. If the Table name is altered in the spreadsheet, this list is dynamically updated. This list can be used to indirectly refer to columns within your M code.

As an example to show this, Here I have changed Column1 in Table1 to Hello and after refreshing the data the outputted table updates the column to read Hello accordingly.

enter image description here

This works, by referring to the DynamicNameHeader list and indexing for the desired column. The code also demonstrates simple transformations that can be achieved in this by changing the text to uppercase and reordering the columns.

M Code:

let
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
   
DynamicHeaderNames =Table.ColumnNames(Source),

    #"Uppercased Text" = Table.TransformColumns(#"Source",{{DynamicHeaderNames{0}, Text.Upper, type text}, {DynamicHeaderNames{1}, Text.Upper, type text}}),
    #"Reordered Columns" = Table.ReorderColumns(#"Uppercased Text",{DynamicHeaderNames{1}, DynamicHeaderNames{0}})
in
    #"Reordered Columns"

This is just an easy example to show how I'm trying to integrate Dynamic Columns in this way.

PROBLEM Here is a simple version of the actual data that has been sorted in the outputted table such that the data in Column 1 is Prioritised over values in Column2. The numbers in column 3 just correspond to values in column2.

enter image description here

Working M code to achieve this output:

let
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],

    //combine the columns
    #"Added Custom" = Table.AddColumn(Source, "Custom", each
        let 
            L1 = if [Column2] = "-" then {[Column1]}
                    else List.Combine({{[Column1]},Text.Split([Column2],"#(lf)")}),
            L2 = if [Column2] = "-" then {[Column3]}
                    else List.Combine({{"-"},Text.Split([Column3],"#(lf)")})
        in 
            List.Zip({L1,L2})),
    #"Removed Columns1" = Table.RemoveColumns(#"Added Custom",{"Column1", "Column2", "Column3"}),

    //split the combined columns
    #"Expanded Custom" = Table.ExpandListColumn(#"Removed Columns1", "Custom"),
    #"Extracted Values" = Table.TransformColumns(#"Expanded Custom", 
        {"Custom", each Text.Combine(List.Transform(_, Text.From), ";"), type text}),
    #"Split Column by Delimiter" = Table.SplitColumn(#"Extracted Values", 
        "Custom", Splitter.SplitTextByDelimiter(";", QuoteStyle.Csv), {"Column1", "Column3"})
in
    #"Split Column by Delimiter"

I wish to make this code Dynamic as in the simplified example, however I am running into lots of syntax issues when I add the Custom Column step.

In essence I wish to reference columns 1, 2 and 3 indirectly by indexing the DynamicNameHeader list as before. Is this possible? Importantly, this isn't just to allow the column names to be altered by the user, but so that transformations to the data also can refer to the relevant columns dynamically too. This Custom Column Transformation is pretty much the only step proving difficult because it uses [] which dont appear to be compatible with DynamicNameHeader{x}.

I hope this explanation is clear enough to understand what I am trying to achieve and if anyone has any solutions to this problem it would be really appreciated.

5
  • if a user changes the name of a column within a table?! Commented Apr 23, 2021 at 15:05
  • Hi, Yeah I think that makes sense? So for example when I create a new query from empty cells a table is created with Column1, Column2 etc. Normally if you were to change Column1 to a different name the query would break. Currently I have the outputted table automatically change the column name if the source table does. I just wish to enable this transformation in the same step. Commented Apr 23, 2021 at 15:14
  • I suggest you edit your question by posting an example of the data you wish to process (preferable as text and not as a screenshot), along with an explanation of how you want it sorted, and an example of your desired result. Commented Apr 23, 2021 at 19:34
  • @RonRosenfeld Hi there, I have updated the question. Its quite lengthy as I didnt feel I was explaining the problem clearly enough. Also because I wasnt sure how to post data without it becoming very confusing as text. Instead I have made the examples really simple. The desired result has been achieved, I just want to see if it can be done in a dynamic way. Commented Apr 23, 2021 at 22:54
  • Edited my answer to also show code that will work on your data. Commented Apr 24, 2021 at 14:47

1 Answer 1

1

Your posted M Code doesn't really return the results you show, but here is an example of how to use undefined column headers in your custom column.

It involves adding an Index column to sort things out.

Note that I also replaced nulls which were in my sample data, as the Text.Split function will fail with a null

    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],

allCols=Table.ColumnNames(Source),
    col1 = allCols{0},
    col2 = allCols{1},
    col3 = allCols{2},

IDX = Table.AddIndexColumn(Source,"IDX",0,1),
    #"Replaced Value" = Table.ReplaceValue(IDX,null,"",Replacer.ReplaceValue,allCols),
    #"Added Custom" = Table.AddColumn(#"Replaced Value", "lists", each let 
            col2List = Text.Split(
                Table.Column(#"Replaced Value",col2){[IDX]},
                 "#(lf)"),
            col3List = Text.Split(
                Text.From(Table.Column(#"Replaced Value",col3){[IDX]}),
                "#(lf)")
        in 
            List.Zip({col2List,col3List}))

Edit

Here is an example of M-Code that is insensitive to the actual column names, and will produce the output you show from the input you show:

let
    Source = Excel.CurrentWorkbook(){[Name="Table10"]}[Content],

allCols=Table.ColumnNames(Source),
    col1 = allCols{0},
    col2 = allCols{1},
    col3 = allCols{2},

IDX = Table.AddIndexColumn(Source,"IDX",0,1),
#"Added Custom" = Table.AddColumn(IDX, "Custom", each if Table.Column(IDX,col2){[IDX]} = "-" 
    then 
       List.Zip({{Table.Column(IDX,col1){[IDX]}},{Text.From(Table.Column(IDX,col3){[IDX]})}}) 
    else 
      List.InsertRange(
        List.Zip({
        Text.Split(Table.Column(IDX,col2){[IDX]},"#(lf)"),
        Text.Split(Table.Column(IDX,col3){[IDX]},"#(lf)")}),
            0,{{Table.Column(IDX,col1){[IDX]},"-"}})),

    #"Removed Columns" = Table.RemoveColumns(#"Added Custom",List.Combine({{"IDX"}, allCols})),
    #"Expanded Custom" = Table.ExpandListColumn(#"Removed Columns", "Custom"),
    #"Extracted Values" = Table.TransformColumns(#"Expanded Custom", 
        {"Custom", each Text.Combine(List.Transform(_, Text.From), ";"), type text}),
    #"Split Column by Delimiter" = Table.SplitColumn(#"Extracted Values", "Custom", 
        Splitter.SplitTextByDelimiter(";", QuoteStyle.Csv), {col1, col3}),
    #"Changed Type" = Table.TransformColumnTypes(#"Split Column by Delimiter",{{col1, type text}, {col3, type text}})
in
    #"Changed Type"

enter image description here

Sign up to request clarification or add additional context in comments.

5 Comments

Hi Again, Are you sure the code I posted doesn't work for you? I just copied it from here and it has produced the desired table in green exactly the same. Also thanks for the answer however I'm not sure if its producing the same output?
Ahh I've just seen your edit come though and will take a look.
@Nick I think the reason is that in my first use of your posted code, there were hyphens instead of empty cells. Anyway, the last code I posted should be useable.
Seems to work! Thanks again :) I Will need to study it to properly understand what's going on. Out of interest would you mind explaining what I was doing wrong? I.e. why I couldnt just insert DynamicHeaderName{x} into [] Also, how is the Index column is working here?
@Nick In adding a Custom Column, the Index column ensures that you are only looking at the cells in the same row, for splitting and then combining. As to entering in the [], I'm not quite sure. But it is the case that the col1 is a string. So maybe the program is seeing this as ["col1"] and not, as we would prefer [col1]. But Table.Column(tbl,col1){[Index]} will return the contents of the column in that particular row, which is what we want usually when adding a Custom column. Maybe someone more knowledgeable will have a better solution or explanation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.