EpicorFree generatorPDF, Excel, Word exportEpicor KineticAll Epicor ERP (Kinetic, 10, 9, Vantage) tools

Epicor Indented BOM Recursive CTE SQL Generator (PartMtl, PartRev)

This generator writes a SQL Server recursive CTE that explodes a multi-level BOM for one top part in the Epicor ERP 10 or Kinetic database (schema Erp), using Erp.PartRev and Erp.PartMtl. It also gives the matching three-subquery layout for the BAQ Designer, which does not accept raw SQL.

Fill in

Leave blank to use the approved revision of the top part.

Keep below 100, the MAXRECURSION setting.

Generated code
Download:

Recursive CTE (run read-only against a reporting copy)

WITH ChildRev AS (
    -- one approved revision per part, primary method, latest effective date wins
    SELECT pr.Company, pr.PartNum, pr.RevisionNum, pr.AltMethod,
           ROW_NUMBER() OVER (PARTITION BY pr.Company, pr.PartNum
                              ORDER BY pr.EffectiveDate DESC, pr.RevisionNum DESC) AS rn
    FROM Erp.PartRev pr
    WHERE pr.Company = 'EPIC06'
      AND pr.Approved = 1
      AND pr.AltMethod = ''
),
BOM (Lvl, ParentPart, ParentRev, MtlSeq, MtlPartNum, QtyPer, ExtQty, SortPath) AS (
    -- anchor: materials of the top part
    SELECT 1,
           pm.PartNum,
           pm.RevisionNum,
           pm.MtlSeq,
           pm.MtlPartNum,
           pm.QtyPer,
           CAST(pm.QtyPer AS decimal(22,8)),
           CAST(RIGHT('0000' + CAST(pm.MtlSeq AS varchar(10)), 4) AS varchar(4000))
    FROM Erp.PartRev pr
    JOIN Erp.PartMtl pm
      ON pm.Company = pr.Company
     AND pm.PartNum = pr.PartNum
     AND pm.RevisionNum = pr.RevisionNum
     AND pm.AltMethod = pr.AltMethod
    WHERE pr.Company = 'EPIC06'
      AND pr.PartNum = 'FG-1000'
      AND pr.AltMethod = '[Alternate method of the top part (blank = primary)]'
      AND (pr.RevisionNum = 'A' OR ('A' = '' AND pr.Approved = 1))

    UNION ALL

    -- recursive member: materials of each child that has its own method
    SELECT b.Lvl + 1,
           pm.PartNum,
           pm.RevisionNum,
           pm.MtlSeq,
           pm.MtlPartNum,
           pm.QtyPer,
           CAST(b.ExtQty * pm.QtyPer AS decimal(22,8)),
           CAST(b.SortPath + '.' + RIGHT('0000' + CAST(pm.MtlSeq AS varchar(10)), 4) AS varchar(4000))
    FROM BOM b
    JOIN ChildRev cr
      ON cr.PartNum = b.MtlPartNum
     AND cr.rn = 1
    JOIN Erp.PartMtl pm
      ON pm.Company = cr.Company
     AND pm.PartNum = cr.PartNum
     AND pm.RevisionNum = cr.RevisionNum
     AND pm.AltMethod = cr.AltMethod
    WHERE b.Lvl < 10
)
SELECT REPLICATE('  ', b.Lvl - 1) + b.MtlPartNum AS IndentedPart,
       b.Lvl, b.ParentPart, b.ParentRev, b.MtlSeq, b.MtlPartNum,
       p.PartDescription, b.QtyPer, b.ExtQty, b.SortPath
FROM BOM b
JOIN Erp.Part p
  ON p.Company = 'EPIC06'
 AND p.PartNum = b.MtlPartNum
ORDER BY b.SortPath
OPTION (MAXRECURSION 100);

Same logic in the BAQ Designer

The BAQ Designer does not accept pasted SQL. Rebuild it as subqueries:

1. SubQuery1, type CTE: tables PartRev and PartMtl joined on Company, PartNum, RevisionNum, AltMethod. Criteria PartRev.PartNum = 'FG-1000', AltMethod = '[Alternate method of the top part (blank = primary)]', revision filter. Display PartMtl.PartNum, RevisionNum, MtlSeq, MtlPartNum, QtyPer and calculated fields Lvl (int) = 1, ExtQty (decimal) = PartMtl.QtyPer.

2. SubQuery2, type UnionAll: the SubQuery1 CTE joined to PartRev (Approved = true, AltMethod blank) on PartRev.PartNum = SubQuery1.PartMtl_MtlPartNum, then PartMtl joined to PartRev. Same display columns in the same order; Lvl = SubQuery1.Calculated_Lvl + 1, ExtQty = SubQuery1.Calculated_ExtQty * PartMtl.QtyPer; criteria SubQuery1.Calculated_Lvl < 10.

3. SubQuery3, type TopLevel: select from SubQuery1, join Part for PartDescription, sort as needed.

The ROW_NUMBER revision filter has no direct BAQ equivalent inside the recursive member; if children carry several approved revisions, keep one approved revision per part as a business rule or filter on a UD flag.

Verify on your release: column names in Erp.PartMtl (Company, PartNum, RevisionNum, AltMethod, MtlSeq, MtlPartNum, QtyPer) and Erp.PartRev (Approved, EffectiveDate, AltMethod) in the Data Dictionary Viewer. The type of the anchor and recursive columns must match exactly, which is why ExtQty and SortPath are cast. Run against a reporting copy with a read-only login.

Need the BOM as a BAQ dashboard?

Send your Epicor version and an example part, and we will check the query and the BAQ layout against your data.

Optional. The tool and every export work without this. No spam.

Was this tool useful?

How to use it

Enter the company ID, the top part, its revision (or leave it blank to take the approved revision), the alternate method if the top part is built by one, and a level limit. Copy the SQL into SQL Server Management Studio connected to a reporting copy or a read-only login. The result has one row per BOM line with level, parent, sequence, material, quantity per and extended quantity. Sort by SortPath to get the indented order. If you need the BOM inside Epicor for a dashboard, follow the BAQ Designer block instead.

  • Lvl 1 rows are the direct materials of the top part
  • ExtQty multiplies QtyPer down the chain
  • SortPath keeps children under their parent
  • Purchased parts end the branch because they have no PartMtl rows

Where this lives in Epicor

Engineering Workbench writes the method of manufacture to PartRev (one row per part, revision and alternate method) and PartMtl (one row per material line, keyed by Company, PartNum, RevisionNum, AltMethod, MtlSeq). MtlPartNum is the component, QtyPer the quantity per parent. A revision is used for jobs and MRP once it is approved (PartRev.Approved). The query reads only the approved method of each child, primary alternate method (blank), which mirrors how a job pulls subassembly methods by default. Pulled-as-assembly behaviour on the job may differ; compare against a job's method if you see a mismatch.

Worked example

Top part FG-1000 revision A has materials SA-200 (QtyPer 2, MtlSeq 10) and SCR-08 (QtyPer 8, MtlSeq 20). SA-200 has an approved revision B with materials BRK-11 (QtyPer 1) and PLT-30 (QtyPer 4). The query returns FG-1000 level 1 rows 0010 and 0020, then SA-200 children with SortPath 0010.0010 and 0010.0020 at level 2. PLT-30 ExtQty is 2 x 4 = 8 per FG-1000. If SA-200 had approved revisions A and B, the ChildRev CTE keeps only the one with the latest EffectiveDate.

Version differences and gotchas

Multiple approved revisions per child duplicate every row below that child; the ChildRev filter prevents this, but confirm the rule matches how your engineers approve revisions. Recursive CTEs require identical data types between anchor and recursive member, so keep the casts if you add columns. Circular BOMs stop at the level limit; MAXRECURSION 100 is a backstop. In the BAQ Designer, CTE display fields are referenced as SubQuery1.PartMtl_MtlPartNum and calculated fields as Calculated_Lvl. Never run this against production during MRP or month end on a large BOM without testing the row count first.

Frequently Asked Questions

How do I write an indented BOM query in Epicor?

Use a recursive CTE over Erp.PartMtl: the anchor selects the top part's materials, the recursive member joins PartMtl again where PartMtl.PartNum equals the previous level's MtlPartNum, choosing one approved PartRev per child. Add a level counter and a sort path. This generator writes it for you.

Can I paste this SQL into the BAQ Designer?

No. The BAQ Designer builds SQL from subqueries and does not take raw SQL. Rebuild it as a CTE subquery, a UnionAll subquery that joins back to the CTE, and a TopLevel subquery that reads the CTE. The second block of the output lists the exact tables, joins and calculated fields.

Why does my BOM query return duplicate rows?

A child part has more than one approved revision, or more than one alternate method is being joined. Restrict each child to one revision, for example the latest EffectiveDate among approved revisions, and to the primary method (AltMethod blank). The ChildRev CTE in the generated SQL does exactly that.

Is it safe to run this on the live Epicor database?

It is read-only, but a deep BOM explosion on a busy server can add load. Run it with a read-only login against a reporting copy or replica, test on one top part, and keep the level limit. Never add write statements to queries against Epicor tables.

If you want engineers to ask for any part's indented BOM in plain English, answered from your own Epicor database on your servers, we can build that layer.

ArchRay builder
Design the on-prem AI stack for Epicor ERP: model, GPUs, RAG pipeline, guardrails
See memory, speed and cost as you swap parts, then export the diagram. Free, in your browser.
Open the builder

Keep exploring

Free tools

ERP fixes and how-tos

Guides

AI on your ERP

Integrations

Comparisons

Software