Skip to main content
cancel
Showing results for 
Search instead for 
Did you mean: 

Be one of the first to start using Fabric Databases. View on-demand sessions with database experts and the Microsoft product team to learn just how easy it is to get started. Watch now

Reply
Amit_Bhaigade
Regular Visitor

Flattening of parent-child hierarchy in power query.

Hi Mates,

Happy new year!
I tried flattening parent child hierarchy with the help of power query and was successfully doing so. presenting solution below for larger audience so your suggestions can help me optimize it further.

Final result - 

 

Amit_Bhaigade_0-1735657648241.png

------------------------------------------------------------------------------------------------------------------------------------------------

Query -
 

let
Source = Excel.CurrentWorkbook(){[Name="ParentChild"]}[Content],
#"Changed Type" = Table.TransformColumnTypes(Source,{{"Child", type text}, {"Parent", type text}}),
#"Expanded Changed Type" = let
_Hierarchy_level = 5,             // This can be parameterized to go down number of levels into hierarchy
_Generate_List = List.Generate(()=>1, each _ <= _Hierarchy_level, each _ + 1, each "Parent_" & Text.From(_))             // This generates a list like Parent_1, Parent_2,,,. till end of defined hierarchy

// Idea is to flatten the hierarchy by performing merge operation on same parent-child table multiple times

in List.Accumulate (_Generate_List, #"Changed Type" , (s, c)=>
let
_Column_headers = Table.ColumnNames(s),
_First_Column = _Column_headers{0},                        // This field will work as look-up range
_Second_Column = _Column_headers{1},                  // This field will work as look-up output
_Last_Column = List.Last(_Column_headers)             // This field will be used as look up value
in
Table.ExpandTableColumn(Table.NestedJoin(s, {_Last_Column}, #"Changed Type", {_First_Column}, "X", JoinKind.LeftOuter), "X", {_Second_Column}, {c})),

// After getting final out put in above step, I want to reverse order with few below steps so level 1, level 2 and so on can be sorted correctly

ConsolidateHierarchyWithComma = Table.CombineColumns(#"Expanded Changed Type",List.Select (Table.ColumnNames (#"Expanded Changed Type"), each Text.Contains(_,"Parent")),Combiner.CombineTextByDelimiter(", "),"Parent"),
CreateTableWithLevels = let
_Table = Table.TransformColumns (ConsolidateHierarchyWithComma, {"Parent", each Text.Split (Text.Reverse (Text.TrimEnd(_,{","," "})), " ," )}),
_MaxRecords = List.Max (Table.TransformColumns(_Table,{"Parent", each List.Count(_)})[Parent]),
_NewList = List.Generate(()=> 1, each _ <= _MaxRecords,each _ + 1)
in List.Accumulate(_NewList,_Table,(s,c)=> Table.AddColumn(s,"Level_" & Text.From(c),each try [Parent]{c-1} otherwise null)),
#"Removed Columns" = Table.RemoveColumns(CreateTableWithLevels,{"Parent"}),   // Removing hierarchy path as it is not needed now
#"Sorted Rows" = Table.Sort(#"Removed Columns",{{"Child", Order.Ascending}})      // Sorting to original child order
in
#"Sorted Rows"


------------------------------------------------------------------------------------------------------------------------------------------------
I personally feel that, above solution might break if used on larger datasets due to multiple iterations. 

Would be happy to receieve a guidance.
Glad if above query helps anyone to flatten parent child hierarchy.
Cheers!

 

1 ACCEPTED SOLUTION
dufoq3
Super User
Super User

Hi @Amit_Bhaigade, check this:

 

Output

dufoq3_0-1735669556271.png

 

If you want to limit hierarchy levels, you can do it in HierarchyLevelLimit step:

 

dufoq3_2-1735671795317.png

 

let
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("Pcs5DQAwDANALp5Lov/LIAp/GrUstdstZ4aIAHgwJCJK+atQSapUltoLnWjSoKo0qS4takqbWtKhNtwv", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Child = _t, Parent = _t]),
    ReplacedBlank = Table.ReplaceValue(Source,"",null,Replacer.ReplaceValue,{"Parent"}),
    RenamedColumns = Table.RenameColumns(ReplacedBlank,{{"Child", "Name"}, {"Parent", "Value"}}),
    R = Record.FromTable(RenamedColumns),
    // Enter valid number greater than 0.
    // Default value = 0 (all levels).
    HierarchyLevelLimit = 0,
    StepBack = RenamedColumns,
    Ad_GenLev = Table.AddColumn(StepBack, "GenLev", each 
        List.Generate(
                ()=> [ y = [Name], z = {y} ],
                each [y] <> null and (if List.Contains({0, ""}, HierarchyLevelLimit) then true else List.Count([z]) <= HierarchyLevelLimit + 1),
                each [ y = Record.FieldOrDefault(R, [y], null), z = [z] & {y} ],
                each [y] ), type {text} ),
    Ad_Levels = Table.AddColumn(Ad_GenLev, "Levels", each 
        [ a = List.Zip({ {"1"..Text.From(List.Count([GenLev])-1)}, List.Reverse(List.Skip([GenLev])) }),
          b = List.Accumulate(a, #table(type table[Child=text], {{[Name]}}), (st, cur)=> Table.AddColumn(st, "Level_" & cur{0}, (x)=> cur{1}, type text))
        ][b], type table ),
    CombinedLevels = Table.Combine(Ad_Levels[Levels])
in
    CombinedLevels

 


Note: Check this link to learn how to use my query.
Check this link if you don't know how to provide sample data.

View solution in original post

5 REPLIES 5
AlienSx
Super User
Super User

just for fun

let
    find_all = (name, state) => 
        [next = Record.FieldOrDefault(pairs, name),
        s = state & {name}, 
        all = if next is null then {s{0}} & List.Reverse(List.Skip(s)) else @find_all(next, state & {name})][all],
    Source = your_table,
    pairs = Function.Invoke(Record.FromList, List.Reverse(Table.ToColumns(Source))), 
    lst = List.Buffer(List.Transform(Source[Child], (x) => find_all(x, {}))),
    col_names = {"Child"} & List.Skip(List.Transform({0..List.Max(List.Transform(lst, List.Count)) - 1}, (x) => "Level " & Text.From(x))),
    result = Table.FromColumns(List.Zip(lst), col_names)
in
    result

Works like wonder with slight adjustement in below line of code -


lst = List.Buffer(List.Transform(Source[Child], (x) => List.RemoveItems (find_all(x, {}),{""}))),

Amit_Bhaigade
Regular Visitor

Hi @dufoq3 , this really fits the requirement!

Enjoy 🙂


Note: Check this link to learn how to use my query.
Check this link if you don't know how to provide sample data.

dufoq3
Super User
Super User

Hi @Amit_Bhaigade, check this:

 

Output

dufoq3_0-1735669556271.png

 

If you want to limit hierarchy levels, you can do it in HierarchyLevelLimit step:

 

dufoq3_2-1735671795317.png

 

let
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("Pcs5DQAwDANALp5Lov/LIAp/GrUstdstZ4aIAHgwJCJK+atQSapUltoLnWjSoKo0qS4takqbWtKhNtwv", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Child = _t, Parent = _t]),
    ReplacedBlank = Table.ReplaceValue(Source,"",null,Replacer.ReplaceValue,{"Parent"}),
    RenamedColumns = Table.RenameColumns(ReplacedBlank,{{"Child", "Name"}, {"Parent", "Value"}}),
    R = Record.FromTable(RenamedColumns),
    // Enter valid number greater than 0.
    // Default value = 0 (all levels).
    HierarchyLevelLimit = 0,
    StepBack = RenamedColumns,
    Ad_GenLev = Table.AddColumn(StepBack, "GenLev", each 
        List.Generate(
                ()=> [ y = [Name], z = {y} ],
                each [y] <> null and (if List.Contains({0, ""}, HierarchyLevelLimit) then true else List.Count([z]) <= HierarchyLevelLimit + 1),
                each [ y = Record.FieldOrDefault(R, [y], null), z = [z] & {y} ],
                each [y] ), type {text} ),
    Ad_Levels = Table.AddColumn(Ad_GenLev, "Levels", each 
        [ a = List.Zip({ {"1"..Text.From(List.Count([GenLev])-1)}, List.Reverse(List.Skip([GenLev])) }),
          b = List.Accumulate(a, #table(type table[Child=text], {{[Name]}}), (st, cur)=> Table.AddColumn(st, "Level_" & cur{0}, (x)=> cur{1}, type text))
        ][b], type table ),
    CombinedLevels = Table.Combine(Ad_Levels[Levels])
in
    CombinedLevels

 


Note: Check this link to learn how to use my query.
Check this link if you don't know how to provide sample data.

Helpful resources

Announcements
Las Vegas 2025

Join us at the Microsoft Fabric Community Conference

March 31 - April 2, 2025, in Las Vegas, Nevada. Use code MSCUST for a $150 discount!

ArunFabCon

Microsoft Fabric Community Conference 2025

Arun Ulag shares exciting details about the Microsoft Fabric Conference 2025, which will be held in Las Vegas, NV.

December 2024

A Year in Review - December 2024

Find out what content was popular in the Fabric community during 2024.

Top Solution Authors