Reply
squarecat
Helper I
Helper I

Advanced unpivoting multiple columns

Hello, I have this source table 

 

  PLANPLANPLANACTACTACTLYLYLY
IDCost CenterJanFebMarJanFebMarJanFebMar
123A1009012090110806015075
234B506540405570356080
345C758085852050558065
567D604080408065855060

 

And I need it to be in this format

 

IDCost CenterDatePLANACTLY
123A1/1/20251009060
234B1/1/2025504035
345C1/1/2025758555
567D1/1/2025604085
123A1/2/202590110150
234B1/2/2025655560
345C1/2/2025802080
567D1/2/2025408050
123A1/3/20251208075
234B1/3/2025407080
345C1/3/2025855065
567D1/3/2025806560

 

Can someone guide me into how to achieve this result?

squarecat_0-1743626346258.png

 

1 ACCEPTED SOLUTION
jgeddes
Super User
Super User

Starting with data formatted like...

jgeddes_0-1743628918541.png

You can end up with...

jgeddes_1-1743628942965.png

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




Did I answer your question? Mark my post as a solution!

Proud to be a Super User!





View solution in original post

5 REPLIES 5
lbendlin
Super User
Super User

Your source pivot table is not in a usable format, especially taking into account that it will likely get wider through the course of the year.  See if you can get it in a more suitable format.

 

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]),
    Months = {1..(List.Count(Table.ColumnNames(Source))-2)/3},
    #"Converted to Table" = Table.FromList(Months, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Added Custom1" = Table.AddColumn(#"Converted to Table", "Columns", each {"Column1","Column2"} & List.Transform({[Column1]*3..[Column1]*3+2},each "Column" & Text.From(_))),
    #"Added Custom2" = Table.AddColumn(#"Added Custom1", "Data", each let s = Table.SelectColumns(Source,[Columns]) in Table.AddColumn(Table.PromoteHeaders(Table.Skip(s,1), [PromoteAllScalars=true]), "Type", each List.Max(Record.ToList(s{0})))),
    #"Removed Other Columns" = Table.SelectColumns(#"Added Custom2",{"Data"}),
    #"Expanded Data" = Table.ExpandTableColumn(#"Removed Other Columns", "Data", {"ID", "Cost Center", "Jan", "Feb", "Mar", "Type"}, {"ID", "Cost Center", "Jan", "Feb", "Mar", "Type"}),
    #"Unpivoted Other Columns" = Table.UnpivotOtherColumns(#"Expanded Data", {"ID", "Cost Center", "Type"}, "Month", "Value")
in
    #"Unpivoted Other Columns"

How to use this code: Create a new Blank Query. Click on "Advanced Editor". Replace the code in the window with the code provided here. Click "Done". Once you examined the code, replace the entire Source step with your own source.

 

lbendlin_0-1743631679752.png

As you can see your months are sorted alphabetically. Your data model should include a calendar table.

 

 

 

jgeddes
Super User
Super User

Starting with data formatted like...

jgeddes_0-1743628918541.png

You can end up with...

jgeddes_1-1743628942965.png

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




Did I answer your question? Mark my post as a solution!

Proud to be a Super User!





Hello! thanks for the help, if it's not too much trouble could you explain how the code works so I can understand better? 😊

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.

jgeddes_0-1743684506301.png

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.

jgeddes_1-1743685101203.png

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.

jgeddes_2-1743685240232.png

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.

jgeddes_3-1743685373955.png

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.

jgeddes_4-1743685499932.png

 





Did I answer your question? Mark my post as a solution!

Proud to be a Super User!





This is brilliant, thanks for the help! it worked like a charm 👌 and your explanation was very clear

avatar user

Helpful resources

Announcements
March PBI video - carousel

Power BI Monthly Update - March 2025

Check out the March 2025 Power BI update to learn about new features.

March2025 Carousel

Fabric Community Update - March 2025

Find out what's new and trending in the Fabric community.

Top Solution Authors (Last Month)
Top Kudoed Authors (Last Month)