问题
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