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.
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.
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.
Related Tools
Epicor BAQ Subquery Types Reference (TopLevel, InnerSubQuery, Union, CTE)
What each BAQ Designer SubQuery Type generates in SQL, the ordering rules that make unions and recursive CTEs work, and tested pattern recipes for BOMs, demand lists and last operations.
EpicorEpicor BAQ Calculated Field Examples Reference
A searchable cheat sheet of BAQ calculated field expressions you can copy into the Query Builder, grouped by dates, quantities, pricing and text.
EpicorEpicor Job Material Shortage SQL Generator
Build a query that lists unissued job material for released, open jobs so planners can chase shortages.
EpicorEpicor Kinetic Database Tables Reference
Look up the main Erp.* tables in Epicor Kinetic with what each holds and the columns that identify a row. Useful when writing BAQs and SQL.
More Epicor tools
See all- Epicor Kinetic Customization Change Request Form Template
- Epicor Kinetic Go-Live Cutover Plan Template
- Epicor Kinetic User Access Request Form Template
- Epicor Kinetic BPM Directive Inventory Worksheet
- Epicor Kinetic Scheduled Task Register Worksheet
- Epicor Kinetic Custom Report Inventory Worksheet
- Epicor Kinetic Data Migration Mapping Worksheet
- Epicor Kinetic Data Load Effort Calculator
- Epicor Business Object Services Reference
Keep exploring
Free tools
- BOM Cost Rollup Recursive CTE SQL GeneratorName your BOM and cost tables and get a recursive SQL query that explodes a bill of material and rolls up purchased component cost.
- Epicor BAQ Subquery Types Reference (TopLevel, InnerSubQuery, Union, CTE)What each BAQ Designer SubQuery Type generates in SQL, the ordering rules that make unions and recursive CTEs work, and tested pattern recipes for BOMs, demand lists and last operations.
- Recursive BOM Explosion SQL CTE GeneratorBuilds a recursive common table expression that explodes a multi-level BOM with extended quantities, plus a where-used query, using your own table and column names.
ERP fixes and how-tos
- Epicor BAQ Recursive BOM Join on PartMtl, Previous Operation Complete and Stale HeadersMulti-level BOM in an Epicor BAQ: join PartMtl to itself, previous-operation-complete logic, table relations, and fixing stale column headers.
- Shortage, Scrap, WIP, BOM, GL and Sales Reports in Epicor KineticWhere to find the Epicor Kinetic shortage, scrap, WIP, BOM, general ledger and sales reports, and how to build each one as a BAQ when the standard report falls short.
- How to Add AI to Epicor Kinetic On-Premises Without Moving Your DataReference architecture for a private AI assistant on Epicor Kinetic on-premises: BAQ and REST v2 access, SQL replica, local LLM, roles, audit and rollout.
AI on your ERP
- Private AI for Epicor Kinetic Cloud, Outside the Multi-Tenant BoundaryRun a private LLM on Epicor Kinetic private cloud or Kinetic Cloud data via BAQs, REST v2 and Data Fabric, keeping sensitive manufacturing data off shared AI.
- AI for Epicor Kinetic Job Shops: Quote History, Job Variance and Schedule RiskPrivate AI for Epicor Kinetic job shops: quote-to-job history, JobHead and JobOper variance, subcontract delays and schedule risk, via REST and BAQs.
Integrations
- How to use Claude with Epicor KineticUse Claude with Epicor Kinetic through an MCP connector, the Anthropic API, AWS Bedrock or Google Vertex, plus the private on-prem alternative for sensitive data.
- How to connect Power BI to Epicor KineticConnect Power BI to Epicor Kinetic using BAQ OData feeds, a data warehouse or a SQL replica, with an on-prem gateway, key dashboards and natural-language Q&A.
Software
- Epicor KineticEpicor Kinetic explained for manufacturers: modules, REST API, BAQs, deployment, strengths, limits and alternatives to compare before choosing or upgrading.
- Epicor CPQEpicor CPQ is a cloud configure-price-quote product with visual 3D configuration for manufacturers. Modules, ERP integrations and alternatives explained.