Forum Discussion
Advanced unpivoting multiple columns
- 1 year ago
Starting with data formatted like...
You can end up with...
with the following code...
let Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("jY4/C8IwEMW/SsncIf8uiWNNEZQqDi4SMlToWkH7/fEuJqno4nC/vHfw3iUE1rA2zXnoTj9P5y9fHK4fiG1g+x61vz+Xxk/zMj3QHcYZuZtuyOP434aqhFR0B0dwjtwQhFylIDqCSR6IFlJYKo1mi5O2BhCaFwBZS0pBiWMRBZWmjX835XoHBek61AqXuykIxqLpS52un1uVqT2Qfx3jCw==", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Column1 = _t, Column2 = _t, Column3 = _t, Column4 = _t, Column5 = _t, Column6 = _t, Column7 = _t, Column8 = _t, Column9 = _t, Column10 = _t, Column11 = _t]), new_columns = List.Transform(List.Zip({Record.ToList(Table.FirstN(Source, 2){1}), Record.ToList(Table.FirstN(Source, 2){0})}), each if _{1} = " " then _{0} else Text.Combine(_, "-")), rename_list = List.Zip({List.Generate(()=>1, each _ <= List.Count(new_columns), each _ + 1, each "Column" & Text.From(_)), new_columns}), edit_table = Table.RenameColumns(Table.Skip(Source, 2), rename_list), unpivot_other_columns = Table.UnpivotOtherColumns(edit_table, {"ID", "Cost Center"}, "Attribute", "Value"), split_attribute_column = Table.SplitColumn(unpivot_other_columns, "Attribute", Splitter.SplitTextByDelimiter("-", QuoteStyle.Csv), {"Date", "Type"}), pivot_type_column = Table.Pivot(split_attribute_column, List.Distinct(split_attribute_column[Type]), "Type", "Value"), transform_date = Table.TransformColumns(pivot_type_column,{{"Date", each Date.FromText("01"&_&"2025"), type date}}), table_sort = Table.Buffer(Table.Sort(transform_date, {{"Date", Order.Ascending}, {"ID", Order.Ascending}, {"Cost Center", Order.Ascending}})) in table_sort
Starting with data formatted like...
You can end up with...
with the following code...
let
Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("jY4/C8IwEMW/SsncIf8uiWNNEZQqDi4SMlToWkH7/fEuJqno4nC/vHfw3iUE1rA2zXnoTj9P5y9fHK4fiG1g+x61vz+Xxk/zMj3QHcYZuZtuyOP434aqhFR0B0dwjtwQhFylIDqCSR6IFlJYKo1mi5O2BhCaFwBZS0pBiWMRBZWmjX835XoHBek61AqXuykIxqLpS52un1uVqT2Qfx3jCw==", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Column1 = _t, Column2 = _t, Column3 = _t, Column4 = _t, Column5 = _t, Column6 = _t, Column7 = _t, Column8 = _t, Column9 = _t, Column10 = _t, Column11 = _t]),
new_columns =
List.Transform(List.Zip({Record.ToList(Table.FirstN(Source, 2){1}), Record.ToList(Table.FirstN(Source, 2){0})}), each if _{1} = " " then _{0} else Text.Combine(_, "-")),
rename_list =
List.Zip({List.Generate(()=>1, each _ <= List.Count(new_columns), each _ + 1, each "Column" & Text.From(_)), new_columns}),
edit_table =
Table.RenameColumns(Table.Skip(Source, 2), rename_list),
unpivot_other_columns =
Table.UnpivotOtherColumns(edit_table, {"ID", "Cost Center"}, "Attribute", "Value"),
split_attribute_column =
Table.SplitColumn(unpivot_other_columns, "Attribute", Splitter.SplitTextByDelimiter("-", QuoteStyle.Csv), {"Date", "Type"}),
pivot_type_column =
Table.Pivot(split_attribute_column, List.Distinct(split_attribute_column[Type]), "Type", "Value"),
transform_date =
Table.TransformColumns(pivot_type_column,{{"Date", each Date.FromText("01"&_&"2025"), type date}}),
table_sort =
Table.Buffer(Table.Sort(transform_date, {{"Date", Order.Ascending}, {"ID", Order.Ascending}, {"Cost Center", Order.Ascending}}))
in
table_sortHello! thanks for the help, if it's not too much trouble could you explain how the code works so I can understand better? 😊
- jgeddes1 year agoSuper User
Sure.
The Source step is the sample data. You would have your source data here instead.new_columns = List.Transform(List.Zip({Record.ToList(Table.FirstN(Source, 2){1}), Record.ToList(Table.FirstN(Source, 2){0})}), each if _{1} = " " then _{0} else Text.Combine(_, "-")),A few things going on here. The idea is to take the first two rows of the table and turn them into a single header that can be used in a renaming columns step. So the Table.FirstN() function is used to get the first two rows from the Source table. Record.ToList(Table.FirstN(Source, 2){1}) gets the record values from the second of the two first rows (Power Query is zero indexed) and turns them into a list. The same is done for the values in the first row, the {0}. List.Zip() turns those two lists into a single list of lists. The List.Transform() iterates through the list and combines (Text.Combine()) the two list values with "-" as long as the value from the second row (of the current iteration) is not " ". The conditional logic is what prevents the first two columns from being "ID-" and "Cost Center-" .
Now we have a list of what we want our column names to be.
rename_list = List.Zip({List.Generate(()=>1, each _ <= List.Count(new_columns), each _ + 1, each "Column" & Text.From(_)), new_columns}),This step creates the list that will be used in the rename columns step.
First, the List.Generate() function creates a list starting at one and ending at the length of the list we created in the new_columns step. This list is formatted as "Column1", "Column2"... etc.
The "Column" list is zipped to the new_columns list to create the renaming list.
This step is built this way to allow for the table headers to grow in number over time when new months are added.
edit_table = Table.RenameColumns(Table.Skip(Source, 2), rename_list),The first two rows of the Source table are removed. (We do not need them as they are just the header info that we have captured already.)
The resulting table has the columns renamed using the rename_list that we created.
unpivot_other_columns = Table.UnpivotOtherColumns(edit_table, {"ID", "Cost Center"}, "Attribute", "Value"),Now we unpivot the table using the first two columns as the anchor. This allows for the table to expand with additional months without breaking the query.
split_attribute_column = Table.SplitColumn(unpivot_other_columns, "Attribute", Splitter.SplitTextByDelimiter("-", QuoteStyle.Csv), {"Date", "Type"}),This simply splits the "Attribute" column we just created. Of note is that the delimiter here must match the delimiter that is used in the new_columns step. Also, the "Date" and "Type" column names are set here.
pivot_type_column = Table.Pivot(split_attribute_column, List.Distinct(split_attribute_column[Type]), "Type", "Value"),Pivots the "Type" column using the Value column with no aggregation to get the values.
transform_date = Table.TransformColumns(pivot_type_column,{{"Date", each Date.FromText("01"&_&"2025"), type date}}),Turns the month abbreviation into the first of the month in 2025 using the Date.FromText() function.
table_sort = Table.Buffer(Table.Sort(transform_date, {{"Date", Order.Ascending}, {"ID", Order.Ascending}, {"Cost Center", Order.Ascending}}))Sorts the table. And you end up with the desired result.
- squarecat1 year agoHelper I
This is brilliant, thanks for the help! it worked like a charm 👌 and your explanation was very clear