SQL Server : BOM Recursion from the bottom up

孤街浪徒 提交于 2020-01-16 02:50:10

问题


I am attempting to sum up the BOM costs from the bottom up. I need to be able to determine at a particular level of the BOM what the costs are for that level as all costs roll up from the levels below.

In the below example Job 1000 costs should be the sum of all costs for all jobs below as well as the cost for Job 1000. 1000-1 should be the sum of 1000-1 + 1000-1A, 1000-2 would only contain the costs for 1000-2 as there are no components tied to that Job, etc...

(NOTE: Job Numbers are random in the real world and cannot be sorted on reliably.)

1000              
   1000-1
      1000-1a
      1000-1B
         1000-1B1 
   1000-2
   1000-3
      1000-3A
      1000-3B
         1000-3B-1
   1000-4

Bill_Of_Jobs defines the assembly/BOM structure as well as a Job table that contains the costing information for each job.

In the below example I would expect to return:

1000      = $150
1000-1    = $140
1000-1A   = $ 30
1000-1B   = $ 90
1000-1B-1 = $ 50

SQL Fiddle Example

CREATE TABLE [Bill_Of_Jobs]
(
    [Parent_Job] varchar(10) NOT NULL,
    [Component_Job] varchar(10) NOT NULL,
    [Root_Job] varchar(10) NULL,
)

Insert into Bill_Of_Jobs (Parent_Job, Component_Job, Root_Job)
Values ('1000', '1000-1', '1000'),
       ('1000-1', '1000-1A', '1000'),
       ('1000-1', '1000-1B', '1000'),
       ('1000-1B', '1000-1B-1', '1000')

Create Table Job 
(
    Job varchar(10), 
    Top_Lvl_Job varchar(10), 
    [Type] varchar(10), 
    Act_Material money
)

Insert into Job (Job, Top_Lvl_Job, [Type], Act_Material)
Values ('1000', '1000', 'Assembly', 10.00),
       ('1000-1', '1000', 'Assembly', 20.00),
       ('1000-1A', '1000', 'Regular', 30.00),
       ('1000-1B', '1000', 'Assembly', 40.00),
       ('1000-1B-1', '1000', 'Regular', 50.00)

The below query is as close as I have been able to come. It does not sum correctly and it sums top down vs. bottom up. Any help is most appreciated.

WITH roots AS 
(
    SELECT DISTINCT 
        1 AS [Level], 
        BOJ.parent_job AS RootJob, 
        Cast(BOJ.parent_job AS VARCHAR(1024)) AS Path, 
        BOJ.parent_job, 
        BOJ.parent_job AS ComponentJob, 
        job.act_material 
    FROM   
        bill_of_jobs AS BOJ 
    INNER JOIN 
        job  ON BOJ.parent_job = job.job 
    WHERE  
        (NOT EXISTS (SELECT 'z' AS Expr1 
                     FROM bill_of_jobs 
                     WHERE (component_job = BOJ.parent_job)
                    )
        )
), 
bom AS 
(
    SELECT 
        [level], 
        rootjob, 
        path, 
        parent_job, 
        componentjob, 
        act_material 
    FROM   
        roots 

    UNION ALL 

    SELECT 
        bom.[level] + 1, 
        bom.rootjob, 
        Cast(bom.path + '»' + BOJ2.component_job AS VARCHAR(1024)), 
        BOJ2.parent_job, 
        BOJ2.component_job, 
        bom.act_material + J.act_material 
    FROM   
        bom 
    INNER JOIN 
        bill_of_jobs AS BOJ2 ON BOJ2.parent_job = bom.componentjob 
    INNER JOIN 
        job AS J ON BOJ2.component_job = J.job
) 
SELECT 
    componentjob AS Component_Job, 
    [path], 
    Space( [level] * 2 ) + componentjob AS IndentedBOM, 
    Dense_rank() OVER (partition BY rootjob ORDER BY path) AS View_Order, 
    act_material 
FROM   
    bom   

回答1:


You can use this recursive CTE:

;WITH BottomUp AS ( 
    SELECT Component_Job, Parent_Job, j.Act_Material, 1 AS level
    FROM Bill_Of_Jobs AS b
    INNER JOIN Job AS j ON b.Component_Job = j.Job

    UNION ALL

    SELECT c.Component_Job, b.Parent_Job, j.Act_Material, level = c.level + 1
    FROM Bill_Of_Jobs AS b  
    INNER JOIN BottomUp c ON c.Parent_Job = b.Component_Job
    INNER JOIN Job AS j ON c.Component_Job = j.Job   
)
SELECT *
FROM BottomUp

to get all ancestors of each Component_Job:

Component_Job   Parent_Job  Act_Material    level
-------------------------------------------------
1000-1          1000        20,00           1  
1000-1A         1000-1      30,00           1
1000-1B         1000-1      40,00           1
1000-1B-1       1000-1B     50,00           1
1000-1B-1       1000-1      50,00           2
1000-1B-1       1000        50,00           3
1000-1B         1000        40,00           2
1000-1A         1000        30,00           2

If you UNION the above result set with leaf nodes:

;WITH BottomUp AS (
  ... above query here
), BottomUpWithLeafNodes AS (
   SELECT Component_Job, Parent_Job, Act_Material, level
   FROM BottomUp 

   UNION 

   SELECT Job AS Component_Job, Job AS Parent_Job, Act_Material, 0 AS level
   FROM Job
   WHERE Job NOT IN (SELECT Parent_Job FROM Bill_Of_Jobs)
)
SELECT *
FROM BottomUpWithLeafNodes

then you have something you can GROUP BY Parent_Job column. You just have to add the Act_Material value for the parent of each group of non-leaf nodes to get the desired result:

;WITH BottomUp AS (
     ... above query here
), BottomUpWithLeafNodes AS (
     ... above query here
)
SELECT Parent_Job AS Job, 
       SUM(Act_Material) + CASE 
                              WHEN SUM(level) <> 0 THEN (SELECT Act_Material FROM Job WHERE Job = b.Parent_Job)
                             ELSE 0
                           END AS Act_Material 
FROM BottomUpWithLeafNodes AS b
GROUP BY Parent_Job

Output:

Parent_Job  Act_Material
------------------------
1000        150,00
1000-1      140,00
1000-1A     30,00
1000-1B     90,00
1000-1B-1   50,00

SQL Fiddle Demo



来源:https://stackoverflow.com/questions/29127163/sql-server-bom-recursion-from-the-bottom-up

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!