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

The Power BI Data Visualization World Championships is back! Get ahead of the game and start preparing now! Learn more

Reply
Alexelbertse
New Member

Recursive assembly tree

Hello,

 

have searched a lot, use ai, copilot etc. But I don't seem to get any real working solution. 

we use erp and sell assembly's. There can be sub-assembly's and so on. Each assembly contains material and work activity. 

I want to have an report which the user selects an order number with an slicer. Based on that order number I would like to have al the assembly's and sub-assembly's with the items and work activity's.

 

next I want to calculate the totals based on the assembly and work activity. 

I have and stored procedure which returns the correct data in sql, but in powerbi it is not working. 

I tried witch stored procedure, sql view, sql function. 

tabels:

 

order ( for the order number and corresponding pk)

Salesorderassembly ( get sold assembly's based on order fk)

assemblydetailitem ( items in an assembly fk for lookup)

assemblydetailworkactivity ( workactivty in an assembly fk for lookup)

 

I hope someone can help me create this report, or point me in the right direction. 

11 REPLIES 11
Alexelbertse
New Member

@PwerQueryKees @lbendlin 
we use SQL Server 2022. 

sample excel with data. https://we.tl/t-hfxlL5re9t

 

Output expected is 

 

Alexelbertse_0-1735889852035.png

 

What i would like to achive is when user inpunts an order with the slicer, Then i would like to now how much production time is needed per main assembly and its subassemblys. Sometimes there are more then one assembly sold. 

V_R_ORDER( for the order number and corresponding pk)

V_R_SALESORDERDETAILASSEMBLY( get sold assembly's based on order fk)

V_R_ASSEMBLYDETAILWORKACTIVITY( workactivty in an assembly fk for lookup)

V_R_ASSEMBLYDETAILSUBASSEMBLY( Subassemblys in an assembly fk for lookup)



I`ve tried with this M-code, but it just keeps trying and then just times out. 

 

let
    // Verbind met de SQL Server database
    Bron = Sql.Database(ServerName, DatabaseName),

    // Laad de benodigde tabellen
    R_ASSEMBLYDETAILSUBASSEMBLY = Bron{[Schema="dbo",Item="R_ASSEMBLYDETAILSUBASSEMBLY"]}[Data],
    R_ASSEMBLY = Bron{[Schema="dbo",Item="R_ASSEMBLY"]}[Data],

    // Begin met de basis assembly waar FK_ASSEMBLY = 147468
    Source = Table.SelectRows(R_ASSEMBLYDETAILSUBASSEMBLY, each [FK_ASSEMBLY] = 147468),
    
    // Converteer het initiële resultaat om Path en Level kolommen te hebben
    InitialAssembly = Table.AddColumn(Source, "Path", each Text.From([PK_R_ASSEMBLYDETAILSUBASSEMBLY], "en-US")) & 
                      Table.AddColumn(_, "Level", each 0),
    
    // Buffer het initiële resultaat
    BufferedInitialAssembly = Table.Buffer(InitialAssembly),

    // Recursieve functie om alle subassemblies te krijgen
    RecursiveSubassemblies = (currentTable as table) as table =>
        let
            // Haal subassemblies op voor elke assembly in de huidige tabel
            Subassemblies = Table.NestedJoin(currentTable, {"FK_SUBASSEMBLY"}, R_ASSEMBLYDETAILSUBASSEMBLY, {"FK_ASSEMBLY"}, "Subassemblies", JoinKind.LeftOuter),
            
            // Vouw de geneste tabel uit om subassemblies te krijgen
            Expanded = Table.ExpandTableColumn(Subassemblies, "Subassemblies", {"PK_R_ASSEMBLYDETAILSUBASSEMBLY", "FK_ASSEMBLY", "FK_SUBASSEMBLY"}, {"Sub_PK_R_ASSEMBLYDETAILSUBASSEMBLY", "Sub_FK_ASSEMBLY", "Sub_FK_SUBASSEMBLY"}),
            
            // Construheer nieuw pad en verhoog niveau
            WithNewPath = Table.AddColumn(Expanded, "NewPath", each [Path] & "." & Text.From([Sub_PK_R_ASSEMBLYDETAILSUBASSEMBLY], "en-US")),
            WithNewLevel = Table.TransformColumns(WithNewPath, {"Level", each _ + 1, type number}),
            
            // Selecteer relevante kolommen
            SelectedColumns = Table.SelectColumns(WithNewLevel, {"Sub_PK_R_ASSEMBLYDETAILSUBASSEMBLY", "Sub_FK_ASSEMBLY", "Sub_FK_SUBASSEMBLY", "NewPath", "Level"}),
            
            // Hernoem voor consistentie
            Renamed = Table.RenameColumns(SelectedColumns, 
                {{"Sub_PK_R_ASSEMBLYDETAILSUBASSEMBLY", "PK_R_ASSEMBLYDETAILSUBASSEMBLY"}, 
                 {"Sub_FK_ASSEMBLY", "FK_ASSEMBLY"}, 
                 {"Sub_FK_SUBASSEMBLY", "FK_SUBASSEMBLY"}, 
                 {"NewPath", "Path"}}),
            
            // Combineer de huidige laag met de nieuwe laag
            Combined = Table.Combine({currentTable, Renamed}),
            
            // Als er geen subassemblies meer zijn, retourneer; anders, ga door met recursie
            RecursiveCall = if Table.IsEmpty(Renamed) then Combined else @RecursiveSubassemblies(Renamed)
        in
            RecursiveCall,
    
    // Start de recursie met initiële gegevens
    AllSubassemblies = RecursiveSubassemblies(BufferedInitialAssembly),

    // Buffer het resultaat van alle subassemblies
    BufferedAllSubassemblies = Table.Buffer(AllSubassemblies),

    // Voeg R_ASSEMBLY toe om CODE en REVISION te krijgen
    JoinedWithAssembly = Table.NestedJoin(BufferedAllSubassemblies, {"FK_SUBASSEMBLY"}, R_ASSEMBLY, {"PK_R_ASSEMBLY"}, "AssemblyDetails", JoinKind.Inner),
    ExpandedAssemblyDetails = Table.ExpandTableColumn(JoinedWithAssembly, "AssemblyDetails", {"CODE", "REVISION"}, {"CODE", "REVISION"}),

    // Sorteer op Path voor hiërarchische weergave
    Sorted = Table.Sort(ExpandedAssemblyDetails, {{"Path", Order.Ascending}}),

    // Eindtransformatie voor weergave van het pad
    FinalResult = Table.AddColumn(Sorted, "DisplayPath", each if [Level] > 0 then [Path] else Text.From([PK_R_ASSEMBLYDETAILSUBASSEMBLY], "en-US")),
    SelectedFinal = Table.SelectColumns(FinalResult, {"PK_R_ASSEMBLYDETAILSUBASSEMBLY", "CODE", "REVISION", "DisplayPath", "Level"})
in
    SelectedFinal

check and correct this. It is based on this SQL querie but want to use in powerbi. 
WITH RecursiveAssembly AS (
    -- Anchor member: De basis assembly
    SELECT 
        PK_R_ASSEMBLYDETAILSUBASSEMBLY,
        FK_ASSEMBLY,
        FK_SUBASSEMBLY,
        CAST(PK_R_ASSEMBLYDETAILSUBASSEMBLY AS NVARCHAR(2000)) AS Path,
        0 AS Level
    FROM 
        R_ASSEMBLYDETAILSUBASSEMBLY
    WHERE 
        FK_ASSEMBLY = 147468

    UNION ALL

    -- Recursive member: Voor elke subassembly, haal de subassemblies op
    SELECT 
        d.PK_R_ASSEMBLYDETAILSUBASSEMBLY,
        d.FK_ASSEMBLY,
        d.FK_SUBASSEMBLY,
        CAST(ra.Path + '.' + CAST(d.PK_R_ASSEMBLYDETAILSUBASSEMBLY AS NVARCHAR) AS NVARCHAR(2000)) AS Path,
        ra.Level + 1
    FROM 
        R_ASSEMBLYDETAILSUBASSEMBLY d
    INNER JOIN 
        RecursiveAssembly ra ON d.FK_ASSEMBLY = ra.FK_SUBASSEMBLY
)
-- Query die de resultaten uit v_R_assemblydetailworkactivity haalt voor alle gevonden subassemblies
SELECT 
    v.*,
    CASE WHEN ra.Level > 0 THEN CAST(ra.Path AS NVARCHAR) ELSE CAST(ra.PK_R_ASSEMBLYDETAILSUBASSEMBLY AS NVARCHAR) END AS Path,
    ra.Level
FROM 
    RecursiveAssembly ra
LEFT JOIN 
    v_R_assemblydetailworkactivity v ON v.FK_ASSEMBLY = ra.FK_SUBASSEMBLY
ORDER BY 
    ra.Level, ra.Path;

 



Looking at the sample data, why do you need recursion?
The "Path" gives you all children in 1 go.

I made a start of a solution for you, because I don't fully understand you data. I hope it helps you.

 

This:

let
    Source = ASSEMBLYDETAILSUBASSEMBLY,  // From you sample data NOTE: The path has Type text!!!
    // Selectrows to get all children of this row as a new column. My ASSUMPTION: The row itself should  be included
    #"Added Custom" = Table.AddColumn(Source, "Child", each Table.SelectRows(Source, (child) =>  Text.StartsWith(child[Path], [Path]))),
    #"Expanded Child" = Table.ExpandTableColumn(#"Added Custom", "Child", {"PK_R_ASSEMBLYDETAILSUBASSEMBLY"}, {"Child.PK_R_ASSEMBLYDETAILSUBASSEMBLY"}), // Get the subassemply key and add a row for each child.
    #"Merged Queries" = Table.NestedJoin(#"Expanded Child", {"Child.PK_R_ASSEMBLYDETAILSUBASSEMBLY"}, ASSEMBLYDETAILWORKACTIVITY, {"FK_ASSEMBLY"}, "ASSEMBLYDETAILWORKACTIVITY", JoinKind.LeftOuter),
// you could/should place an additional filter here I guess???
    #"Expanded ASSEMBLYDETAILWORKACTIVITY" = Table.ExpandTableColumn(#"Merged Queries", "ASSEMBLYDETAILWORKACTIVITY", {"OPERATIONTIME", "SETUPTIME"}, {"DETAILWORK.OPERATIONTIME", "DETAILWORK.SETUPTIME"}), // get the times
    // Group the rows back to the original records and sum the time fields from ASSEMBLYDETAILWORKACTIVITY 
    #"Grouped Rows" = Table.Group(#"Expanded ASSEMBLYDETAILWORKACTIVITY", {"PK_R_ASSEMBLYDETAILSUBASSEMBLY", "CODE", "Path", "Level"}, {{"Detail Count", each Table.RowCount(_), Int64.Type}, {"Operation Time", each List.Sum([PK_R_ASSEMBLYDETAILSUBASSEMBLY]), type nullable number}, {"Setup Time", each List.Sum([DETAILWORK.SETUPTIME]), type nullable number}})
    // add a filter here to get the data at the level you want.
in
    #"Grouped Rows"

 

Produces this:

PwerQueryKees_0-1735917772183.png

 

Thank you, I’ll give this a try.

I need recursion because the subassembly list contains subassemblies, but a subassembly could have another subassembly, and so on. Therefore, I need recursion because I only know the main assembly that is sold. I can easily look up the first subassembly I know, but for deeper levels, I need recursion.

 

I understand you need subassemlies using subassemblies. My example solution uses the column "Path" to find all parts used by all subassemplies, including all subassemplies used by those subassemblies without using a recursive function.

Recursion is definitely feasible, (I saw you used it already yourself), but in general Power Query does not perform very well on large volumes of data with a recursive structure. 

PwerQueryKees
Super User
Super User

If you send some before and after sample data, I can give the M Code a try, with comments to help you understand.

What is the DBMS you are using?

PwerQueryKees
Super User
Super User

So, essentially, what you want is to have a 'flattended" assembly table giving so you can join this with the detail and workitems tables for you report? 

You would need a recursive function in PowerQuery to do this.

This is doable, but complicated. It needs manual M code programming. I, and may others on this forum could do this.

Two words of warning:

  1. Performance may be an issue for highre volumes. I have done a recursive function on 10000 rows and it took over 10 minutes to complete on a high end laptop in excel. And response times will grow quadratically with volume.
  2. The resulting function may not be easy to understand without sufficient M code knowledge. I don't know your level of expertise or the expertise level of your company, but you might want to consider maintainability before going this route.

Depending on the SQL dialect used by your DBMS, you could consider solving it in SQL. DataBricks should be able to do this. Also complicated probably, but SQL expertise is more wide spread and performance may be a lot better. I have no experience with solving this kind of problem in DataBricks and I have no access to any advanced SQL implementation at the moment, so I probably would not be the best person to help you on an SQL solution.

 

If my assumption on what you want to achieve is correct AND you want to pursue a M code solution, let us know. I might give it a go and otherwise there are many more experts here that could do it.

 

A bit more sample data, perferably as a downloadable file from any file hosting platform (Google, SharePoint, DropBox, etc) would help! Please also include a sample of your expected result!

 

Kees Stolker

A big fan of Power Query and Excel

 

thank you for your repley. SQL knowledge is pressent. M-Code, where learning as we go 🙂

 

i managed to "solve" the problem in SQL with the stored procedure. I join`t the relevant collums there and it returns me most of the data i want now. But the problem with the stored procedure is, i cannot pass the parameters to it every time some body change the slicer input. 

 

I have a parameter OrderNumer wich i connected to the slicer. This works, but when changing the slicer, the stored procedure is not returning the data. When in advanced qeurie editor and altering the parameter there the qeury with the stored procedure is used directly and returning me the data. 

But when in the report view not. When refreshing in report view i get this error: 
Non-null assertion failure: resource: Resource should be available for query V_R_SALESORDERDETAILASSEMBLY to store FoldedArtifacts in cache

But when refreshing in the advanced qeurie editor, this error does not show. 

So, i would like to know and learn how to use the manual M code to recursive get the data i need. Would like to test it to see if it works for us. 

But if possible, also for peformance reasons to use the Stored Procedure with CTE`s i think i would prefere that. 

Alexelbertse
New Member

also would like to know how i can show the hyarchie based on the ID or parrentId data? 

Alexelbertse
New Member

Te stored procedure returns me this. The detailId is the Primary Key of the table it is from. 

 

 

 

DetailIdDetailTypeIdcodeparentIdquantityDOCCOUNTAssemblyIdDetailLevelIsDetail
14746810.147468107831-0001-0000021474680False
4783720.147468.45835.47837107831-0003-0050.147468.45835111475472False
4783820.147468.45835.47838107831-0003-0060.147468.45835111475482False
4783920.147468.45835.47839107831-0003-2000.147468.45835101474802False
4592320.147468.45835.47839.45923107831-0003-0120.147468.45835.47839111475543False
4784520.147468.45835.47845107831-0004-0030.147468.45835101478962False
57135330.147468.45836.47777.45851.I571353MAGPLG4.00 ZM3100.147468.45836.47777.458510,01301475051True
57209430.147468.45836.47777.45851.I572094PEMFH0515 0.147468.45836.47777.45851201475051True
57206530.147468.45836.47777.45852.I572065PEMFH0520 0.147468.45836.47777.45852201475061True
57135430.147468.45836.47777.45852.I571354MAGPLG4.00 ZM3100.147468.45836.47777.458520,00401475061True
57135630.147468.45836.47777.45853.45874.I571356BPLG8.000.147468.45836.47777.45853.458740,00901475081True
57135730.147468.45836.47777.45853.45875.I571357BPLK10.000.147468.45836.47777.45853.458750,0026626201475091True
63502960.147468.45836.47778.45856.W63502980000.147468.45836.47778.45856111474901True
63503960.147468.45836.47778.45856.W63503912000.147468.45836.47778.45856111474901True
63504060.147468.45836.47778.45856.W63504012000.147468.45836.47778.45856101474901True
63504160.147468.45836.47778.45856.W63504160000.147468.45836.47778.45856111474901True
63504260.147468.45836.47778.45856.W63504261000.147468.45836.47778.45856111474901True
63737460.147468.45836.47778.45856.W63737415000.147468.45836.47778.45856101474901True
63383460.147468.45836.47778.45856.45881.W63383439000.147468.45836.47778.45856.45881111475151True
63383560.147468.45836.47778.45856.45881.W63383550000.147468.45836.47778.45856.45881111475151True
63383760.147468.45836.47778.45856.45882.W63383739000.147468.45836.47778.45856.45882111475161True
63383860.147468.45836.47778.45856.45882.W63383850000.147468.45836.47778.45856.45882111475161True
63384060.147468.45836.47778.45856.45883.W63384039000.147468.45836.47778.45856.45883111475171True
63507560.147468.45836.47778.45857.W63507511120.147468.45836.47778.45857111474911True
63502860.147468.45836.47778.45857.45884.W63502852000.147468.45836.47778.45857.45884111475181True
63384360.147468.45836.47778.45857.45884.W63384350000.147468.45836.47778.45857.45884111475181True
63384260.147468.45836.47778.45857.45884.W63384237000.147468.45836.47778.45857.45884111475181True
63384560.147468.45836.47778.45857.45885.W63384537000.147468.45836.47778.45857.45885111475191True

 

SalesOrderAssembly, that is detailType 0. The assembly that is sold. Detail Type 2 is subassemblys (recursyve). The parrentId shows the hyarchie. detailType 3 are the assembly Items. detailType 6 are the workactivitys. 

What i would like to achive is when user inpunts an order with the slicer, the ordernmuber is passed to the Query that uses the stored procedure. Then i would like to now how much production time is needed per main assembly and its subassemblys. Sometimes there are more then one assembly sold. 

 

something like this:

Alexelbertse_0-1735023567355.png

 

 

Anonymous
Not applicable

Hi @Alexelbertse ,

-- I have and stored procedure which returns the correct data in sql, but in powerbi it is not working.

 

 

vcgaomsft_1-1735010827831.png

 

Total Cost = 
SUMX(
SUMMARIZE(
    'AssemblyDetailItem',
    'AssemblyDetailItem'[ItemId],
    'SalesOrderAssembly'[AssemblyId],
    'SalesOrderAssembly'[Quantity],
    'Order'[OrderId],
    "@cost", SUM('AssemblyDetailItem'[Cost])
),
'SalesOrderAssembly'[Quantity]*[@cost]
)
Total Hours = 
SUMX(
SUMMARIZE(
    'AssemblyDetailWorkActivity',
    'AssemblyDetailWorkActivity'[ActivityId],
    'SalesOrderAssembly'[AssemblyId],
    'SalesOrderAssembly'[Quantity],
    'Order'[OrderId],
    "@hours", SUM('AssemblyDetailWorkActivity'[Hours])
),
'SalesOrderAssembly'[Quantity]*[@hours]
)

 

vcgaomsft_0-1735010799880.png

Best Regards,
Gao

Community Support Team

 

If there is any post helps, then please consider Accept it as the solution  to help the other members find it more quickly.
If I misunderstand your needs or you still have problems on it, please feel free to let us know. Thanks a lot!

How to get your questions answered quickly --  How to provide sample data in the Power BI Forum

lbendlin
Super User
Super User

Please provide sample data that covers your issue or question completely, in a usable format (not as a screenshot).
Do not include sensitive information. Do not include anything that is unrelated to the issue or question.
Please show the expected outcome based on the sample data you provided.

Need help uploading data? https://community.fabric.microsoft.com/t5/Community-Blog/How-to-provide-sample-data-in-the-Power-BI-...
Want faster answers? https://community.fabric.microsoft.com/t5/Desktop/How-to-Get-Your-Question-Answered-Quickly/m-p/1447...

Helpful resources

Announcements
Power BI DataViz World Championships

Power BI Dataviz World Championships

The Power BI Data Visualization World Championships is back! Get ahead of the game and start preparing now!

December 2025 Power BI Update Carousel

Power BI Monthly Update - December 2025

Check out the December 2025 Power BI Holiday Recap!

FabCon Atlanta 2026 carousel

FabCon Atlanta 2026

Join us at FabCon Atlanta, March 16-20, for the ultimate Fabric, Power BI, AI and SQL community-led event. Save $200 with code FABCOMM.

Top Solution Authors