Forum Discussion
How to transform wide-format data into the structure shown in 'Result' sheet using Power Query?
Hi everyone,
I have a dataset in wide format in the "Data" sheet, and I’d like to reshape it using Power Query to match the format shown in the "Result" sheet.
What the source looks like (Data sheet):
Row 1 contains repeating column headers for measures like Sales, Purchase, Sell value, etc., grouped by date:
| Product Brand | Product Name | Region | 01.05 Sales | 01.05 Purchase | ... | 01.06 Sales | 01.06 Purchase | ... |
Note:
The values in the file are randomly generated using a function, just for demonstration purposes.
Rows where the Region column contains values like a1_1, a1_2, a1_3 are subregions, and the row with Region = a1 is the sum of those subregions for that product.
What I want to achieve (Result sheet):
I need each value in a separate row, with the following columns:
Product Brand
Product Name
Region
Date
Measure
Values
Example:
Product Brand Product Name Region Date Measure Values
Brand A Product A_1 a1 2025-05-01 Sales 15
Brand A Product A_1 a1 2025-05-01 Purchase 22
Brand A Product A_1 a1 2025-05-01 Sell value 32
The Excel file contains two sheets:
"Data" – raw data
"Result" – expected outcome
Can anyone help me write the proper Power Query steps (M code) to achieve this transformation? Thank you in advance!
https://docs.google.com/spreadsheets/d/1nMz_TyXRSQNelq-cbUDfSf8ekKezGCTn/edit?usp=sharing&ouid=109036053433195029380&rtpof=true&sd=true
3 Replies
- SergeiBaklanDiamond Contributor
One more variant. First, to query the source data we need to transform it to structured table, or define the name, or query from separate file. I'd use second variant. Since data is expandable, I'd define the name with some gap, for example
We query this named range and as first step remove empty rows and columns. Next, from first two rows we combine headers for our table. Skip these two rows and apply headers to the table.
Next, unpivot other but first 3 columns of the table splitting the Attribute column on Date and Measure.
We don't hardcode any field names within the query, with we flexible as with number of fields as with their names.
Entire query is
let Source = Excel.CurrentWorkbook(){[Name="data"]}[Content], RemoveEmptyColumns = Table.SelectColumns( Source, List.Select( Table.ColumnNames(Source), each List.NonNullCount(Table.Column(Source,_)) <> 0 ) ), RemoveEmptyRows = Table.SelectRows( RemoveEmptyColumns, each not List.IsEmpty(List.RemoveMatchingItems( Record.FieldValues(_), {"", null} ) ) ), // prepare and set headers one = Record.FieldValues( RemoveEmptyRows{0} ), two = Record.FieldValues( RemoveEmptyRows{1} ), headers = List.Transform( {0..List.Count(two)-1}, (q) => Text.Combine( {DateTime.ToText(one{q}, [Format="yyyy-MM-dd"] ), two{q} }, "=" ) ), RemoveTopRows = Table.Skip(RemoveEmptyRows,2), SetHeaders = Table.RenameColumns( RemoveTopRows, List.Zip( {Table.ColumnNames(RemoveTopRows), headers}) ), UnpivotColumnsButFirstThree = Table.UnpivotOtherColumns(SetHeaders, List.FirstN( two, 3 ), "Attribute", "Value"), SplitAttribute = Table.SplitColumn(UnpivotColumnsButFirstThree, "Attribute", Splitter.SplitTextByDelimiter("=", QuoteStyle.Csv), {"Date", "Measure"}), DeclareType = Table.TransformColumnTypes(SplitAttribute,{{"Date", type date}}) in DeclareType
I'd don't sort result within query. First, that's expensive operation. Second, we'll be more flexible if apply sorting to the resulting table directly
Please note, since you generate random numbers in the sample, we can't compare result with the source. Query takes data, after loading the result into the grid RANDBETWEEN generates another set of numbers in the source.
- OliverScheurichGold Contributor
In the attached file you can add data to the blue dynamic table. Then you can click in any cell of the green table and right-click with the mouse and select refresh to update the green result table.
Try the following:
let // Load the source table Source = Excel.CurrentWorkbook(){[Name="Data"]}[Content], // Unpivot all date-measure columns Unpivoted = Table.UnpivotOtherColumns(Source, {"Product Brand", "Product Name", "Region"}, "Attribute", "Value"), // Split the 'Attribute' column into Date and Measure SplitColumn = Table.SplitColumn(Unpivoted, "Attribute", Splitter.SplitTextByEachDelimiter({" "}, QuoteStyle.Csv, false), {"DateText", "Measure"}), // Convert the date string to actual date format (assuming dd.MM) AddedYear = Table.AddColumn(SplitColumn, "Date", each Date.FromText("2025-" & Text.Middle([DateText], 3, 2) & "-" & Text.Start([DateText], 2)), type date), // Remove the original DateText column RemovedDateText = Table.RemoveColumns(AddedYear, {"DateText"}), // Reorder columns for clarity Reordered = Table.ReorderColumns(RemovedDateText, {"Product Brand", "Product Name", "Region", "Date", "Measure", "Value"}) in Reordered