Link to home
Start Free TrialLog in
Avatar of Muriel Taft
Muriel Taft

asked on

Need to Add Levels for Indented BOM Explosion Dataset Using SQL

Hello Experts!

As you can see from the attached file containing example data and the desired output, not only are there many levels of parent/child records, but you also have to make sure you're accessing the latest revision of the part number where the effectivedate <= today's date.

A partial BOM explosion would go like this (Partnum, Revisionnum):

TOP LEVEL 0:  NC006000    C
      LEVEL 1:  NC00M021    6  (NO FURTHER BOM EXPLOSION ON THIS PART NUMBER)
        LEVEL 1:  NC00M022    5  (NO FURTHER BOM EXPLOSION ON THIS PART NUMBER)
      LEVEL 1:  NC006303    B
            LEVEL 2:  NC006301   B
            LEVEL 2:  NC006140   K
                  LEVEL 3:  NC006141   K
                  ...and so on.

I only need to perform this explosion for one part number at a time, in this case NC006000.  This will always be a hardcoded part number because this query is not to be run by anybody but a programmer at this point.

I have very little SQL experience but am a good fast learner.  I've been scouring many different books, forums, web sites, and any other source I can get my paws on for an answer for this.  I've seen some good answers on EE but they usually are based on tables that (conveniently!) include the BOM levels.  I somehow have to count them as I go and that is just over my head!  

I found a snippet of recursive code that I've been playing with but have no idea how to add levels.  Help???


WITH RPL AS (SELECT     PARTMTL.PARTNUM, PARTMTL.MTLPARTNUM, PARTMTL.QTYPER
                                FROM         dbo.partmtl
                                WHERE     (PARTMTL.PARTNUM = 'NC006000')
                                UNION ALL
                                SELECT     CHILD.partnum, CHILD.mtlpartnum, CHILD.qtyper
                                FROM         RPL AS PARENT INNER JOIN
                                                      dbo.partmtl AS CHILD ON PARENT.MTLPARTNUM = CHILD.partnum)
    SELECT DISTINCT PARTNUM, MTLPARTNUM, QTYPER
     FROM         RPL AS RPL_1
     ORDER BY PARTNUM, MTLPARTNUM, QTYPER

Open in new window

BOM-Data-Output.txt
Avatar of Muriel Taft
Muriel Taft

ASKER

The deepest level would be 8.
Avatar of Mark Wills
Sounds like a CTE query to me... Will have a look at your txt file and get back to you...

Any chance of getting the three tables as spreadsheets ?
Sure, I've just stripped out the fields you need for this query.  Each table is on a different Excel tab.  They are all linked by company ("NC") and partnum.
Sample-Data-for-BOM.xlsx
Thanks for that, was getting very caught up on mtlpartnum existing in parts table and then the revnum not matching...
I can't tell you how much I appreciate your help.  I've been struggling for days on this, and I am an IT department of one at the little company I work for.  Although I've been programming for 10+ years in Progress, I've never had to do much more than a basic select statement in SQL.  Do you have any recommendations for books or other resources that go into good concise detail on the more complex SQL logic that's possible?
Have played with Progress. Bit different :)  As to best books, there are a few, suggest looking at MS Press as a starting point, and then there is always EE :)

Right...

First challenge was to get the current versions. Easiest way was to create a view on the PARTREV table so that every PARTNUM has only 1 REVISIONNUM.

A VIEW is like a "virtual" table. It is created just the once, and can be accessed much like any table there after.

The second step was to get the BOM exploding the correct way. We use a CTE query to do this - basically a (recursive) query that allows you to select back on itself.

Have a look below...

-- step 1 and a once off task, once run, then do not have to worry about creating the view again.
-- a view is like a virtual table - it is resolved at runtime. This one is giving us the current revisionnum as at "today"
 
create view vw_PARTREV as
Select P.PARTNUM, P.REVISIONNUM, P.EFFECTIVEDATE
from PARTREV P
inner join (select partnum, max(effectivedate) as effectivedate from partrev where effectivedate <= getdate() group by partnum) B on B.partnum = P.partnum and B.effectivedate = P.effectivedate
GO
 
-- just to check, have a look
 
select * from vw_PARTREV
 
-- now the real query...
-- a recursive query - must be the first command in a batch - easiest way is to prefix it with a semicolon
-- basic syntax is :  with cte as (select stuff) select * from cte 
 
;with CTE_BOM as
(
     select 0 as level, 000 as seq, part.partnum as parent, part.partnum, revisionnum , description, convert(decimal(18,8),0) as qty, ium, convert(varchar(800),'0') as path  from part inner join vw_partrev on part.partnum=vw_partrev.partnum
     where part.partnum = 'NC006000'
 
     union all
 
     select B.level+1, convert(int,M.mtlseq), M.partnum, M.mtlpartnum, R.revisionnum, P.description, convert(decimal(18,8),M.qtyper), P.IUM, convert(varchar(800),b.path+','+convert(varchar(10),M.mtlseq))
     from CTE_BOM B
     inner join PART P on P.partnum = B.partnum
     inner join vw_PARTREV R on R.partnum = B.partnum 
     inner join PARTMTL M on M.partnum = R.partnum and M.revisionnum = R.revisionnum
)
select *,replicate('....',level)+partnum as tree from CTE_BOM
Order by level,parent,seq,partnum     -- or order by path to get indented BOM

Open in new window

What a wonderful explanation.  Thank you!

Now, when I try to run it as a query, it comes up with this error:

'CREATE VIEW' must be the first statement in a query batch.

And except for the comments, it is, so that doesn't make any sense to me.
open a new QUERY window in SSMS (shortcut key = ctrl+N), copy just the create view lines 4 through to 7 and press execute (shortcut key = F5)

You can also highlight just the required code and press F5 to execute just the highlighted (selected) code.
I see.. so when you say it's a once-only deal, it actually created something permanent in the db.  

The only two things that is not quite right with this breathtakingly lovely piece of code (and it is entirely my fault for not specifying) is that the revisionnum must be the revision num of the child part number, and the description must be the description of the child part number.  I'm going to fiddle with it to see if I can get it because darn it, I should be able to figure SOMETHING out on my own!
Ah yes, picking it up in the wrong sequence...


;with CTE_BOM as
(
     select 0 as level, 000 as seq, part.partnum as parent, part.partnum, revisionnum , description, convert(decimal(18,8),0) as qty, ium, convert(varchar(800),'0') as path  from part inner join vw_partrev on part.partnum=vw_partrev.partnum
     where part.partnum = 'NC006000'

     union all

     select B.level+1, convert(int,M.mtlseq), M.partnum, M.mtlpartnum, R.revisionnum, P.description, convert(decimal(18,8),M.qtyper), P.IUM, convert(varchar(800),b.path+','+convert(varchar(10),M.mtlseq))
     from CTE_BOM B
     inner join PARTMTL M on M.partnum = B.partnum
     inner join vw_PARTREV R on R.partnum = B.partnum and R.revisionnum = M.revisionnum  
     inner join PART P on P.partnum = M.mtlpartnum
)
select *,replicate('....',level)+partnum as tree from CTE_BOM
Order by level,parent,seq,partnum     -- or order by path to get indented BOM

Almost.... that fixed the description but not the revision level.  Still showing the revision level of the parent part number.
ASKER CERTIFIED SOLUTION
Avatar of Mark Wills
Mark Wills
Flag of Australia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thank you so much!  This is EXACTLY what I needed, and you explained it very well.  I will definitely be able to re-use and modify this logic and know what it means.  Thank you again!
A pleasure working with you and very happy to have been of assistance...