SQL Recursive CTE 'where-used' / BOM explosion part 2 - sql

Hi this carries on from another post,
SQL Recursive CTE 'where-used' / BOM explosion
which the original requirement was answered however I have realised I now have another final requirement. The data that I have after '1' i.e. description etc., want to repeat for each level so the description correctly identifies with the correct parent item. I tried just adding the columns in again in the final select however it just repeated items from level 1. How can this be done?
* UPDATE * I'm really struggling to get the test data/query to look how i want. I want to get the items to relate to each other through bomid. Output to look like img.
The relationship between BOM and BOMVERSION is one item has many BOMID's in BOM table each bomID has a corresponding record in BOMVERSION which through BOMID you get a different ITEMID off of BOMVERSION. That itemID can exist itself in the BOM table with MULTIPLE BOMIDs. I know this is confusing and is very difficult to demonstrate with test data. That's why i'm happy to put on bounty.
* UPDATE * through what i've learnt i've redone the query/test data. I haven't been able to get it to do EXACTLY what i want so query/data may need to be tweaked or added. I'll output what i'm expecting. Basically when BOM.ItemID is linked to BV.ItemID through BOMID i would expect BV.ItemID to be moved into next level and then if THAT BOM.ItemID links to another BV.ItemID move that item to the next level etc. etc. along with all other info RELATED to item in level.
IF OBJECT_ID('tempdb..#BOM') IS NOT NULL
DROP TABLE #BOM;
CREATE TABLE #BOM
(
ItemID NVARCHAR(10) ,
BOMID NVARCHAR(10) ,
BOMQTY INT ,
UnitID NVARCHAR(10) ,
BOMQTYSERIE INT
);
INSERT INTO #BOM
( ItemID ,
BOMID ,
BOMQTY ,
UnitID ,
BOMQTYSERIE
)
VALUES ( N'100001' , -- ItemID - nvarchar(10)
N'1A' , -- BOMID - nvarchar(10)
10 , -- BOMQTY - int
N'g' , -- UnitID - nvarchar(10)
5 -- Bomqtyserie - int
);
INSERT INTO #BOM
( ItemID ,
BOMID ,
BOMQTY ,
UnitID ,
BOMQTYSERIE
)
VALUES ( N'100001' , -- ItemID - nvarchar(10)
N'2A' , -- BOMID - nvarchar(10)
15 , -- BOMQTY - int
N'kg' , -- UnitID - nvarchar(10)
13 -- Bomqtyserie - int
);
INSERT INTO #BOM
( ItemID ,
BOMID ,
BOMQTY ,
UnitID ,
BOMQTYSERIE
)
VALUES ( N'100001' , -- ItemID - nvarchar(10)
N'3A' , -- BOMID - nvarchar(10)
16 , -- BOMQTY - int
N'l' , -- UnitID - nvarchar(10)
16 -- Bomqtyserie - int
);
INSERT INTO #BOM
( ItemID ,
BOMID ,
BOMQTY ,
UnitID ,
BOMQTYSERIE
)
VALUES ( N'100002' , -- ItemID - nvarchar(10)
N'1A' , -- BOMID - nvarchar(10)
18 , -- BOMQTY - int
N'g' , -- UnitID - nvarchar(10)
17 -- Bomqtyserie - int
);
INSERT INTO #BOM
( ItemID ,
BOMID ,
BOMQTY ,
UnitID ,
BOMQTYSERIE
)
VALUES ( N'100004' , -- ItemID - nvarchar(10)
N'2A' , -- BOMID - nvarchar(10)
20 , -- BOMQTY - int
N'kg' , -- UnitID - nvarchar(10)
11 -- Bomqtyserie - int
);
INSERT INTO #BOM
( ItemID ,
BOMID ,
BOMQTY ,
UnitID ,
BOMQTYSERIE
)
VALUES ( N'100002' , -- ItemID - nvarchar(10)
N'2A' , -- BOMID - nvarchar(10)
23 , -- BOMQTY - int
N'kg' , -- UnitID - nvarchar(10)
19 -- Bomqtyserie - int
);
INSERT INTO #BOM
( ItemID ,
BOMID ,
BOMQTY ,
UnitID ,
BOMQTYSERIE
)
VALUES ( N'100003' , -- ItemID - nvarchar(10)
N'2A' , -- BOMID - nvarchar(10)
25 , -- BOMQTY - int
N'kg' , -- UnitID - nvarchar(10)
21 -- Bomqtyserie - int
);
IF OBJECT_ID('tempdb..#BOMVERSION') IS NOT NULL
DROP TABLE #BOMVERSION;
CREATE TABLE #BOMVERSION
(
ItemID NVARCHAR(10) ,
BOMID NVARCHAR(10) ,
Name NVARCHAR(20) ,
Active BIT
);
INSERT INTO #BOMVERSION
( ItemID ,
BOMID ,
Name ,
Active
)
VALUES ( N'100002' , -- ItemID - nvarchar(10)
N'1A' , -- BOMID - nvarchar(10)
N'100002 Version' , -- Name - nvarchar(10)
1 -- Active - bit
);
INSERT INTO #BOMVERSION
( ItemID ,
BOMID ,
Name ,
Active
)
VALUES ( N'100002' , -- ItemID - nvarchar(10)
N'2A' , -- BOMID - nvarchar(10)
N'100002.1 Version' , -- Name - nvarchar(10)
1 -- Active - bit
);
INSERT INTO #BOMVERSION
( ItemID ,
BOMID ,
Name ,
Active
)
VALUES ( N'100003' , -- ItemID - nvarchar(10)
N'3A' , -- BOMID - nvarchar(10)
N'100003 Version' , -- Name - nvarchar(10)
1 -- Active - bit
);
INSERT INTO #BOMVERSION
( ItemID ,
BOMID ,
Name ,
Active
)
VALUES ( N'100004' , -- ItemID - nvarchar(10)
N'4A' , -- BOMID - nvarchar(10)
N'100004 Version' , -- Name - nvarchar(10)
1 -- Active - bit
);
INSERT INTO #BOMVERSION
( ItemID ,
BOMID ,
Name ,
Active
)
VALUES ( N'100005' , -- ItemID - nvarchar(10)
N'5A' , -- BOMID - nvarchar(10)
N'100005 Version' , -- Name - nvarchar(10)
1 -- Active - bit
);
IF OBJECT_ID('tempdb..#INVENTTABLE') IS NOT NULL
DROP TABLE #INVENTTABLE;
CREATE TABLE #INVENTTABLE
(
ItemID NVARCHAR(10) ,
Name NVARCHAR(20) ,
Product INT
);
INSERT INTO #INVENTTABLE
( ItemID, Name, Product )
VALUES ( N'100001', -- ItemID - nvarchar(10)
N'100001 Name', -- Name - nvarchar(10)
1 -- Product - int
);
INSERT INTO #INVENTTABLE
( ItemID, Name, Product )
VALUES ( N'100002', -- ItemID - nvarchar(10)
N'100002 Name', -- Name - nvarchar(10)
2 -- Product - int
);
INSERT INTO #INVENTTABLE
( ItemID, Name, Product )
VALUES ( N'100003', -- ItemID - nvarchar(10)
N'100003 Name', -- Name - nvarchar(10)
3 -- Product - int
);
INSERT INTO #INVENTTABLE
( ItemID, Name, Product )
VALUES ( N'100004', -- ItemID - nvarchar(10)
N'100004 Name', -- Name - nvarchar(10)
4 -- Product - int
);
INSERT INTO #INVENTTABLE
( ItemID, Name, Product )
VALUES ( N'100005', -- ItemID - nvarchar(10)
N'100005 Name', -- Name - nvarchar(10)
5 -- Product - int
);
IF OBJECT_ID('tempdb..#ECORESPRODUCTTRANSLATION') IS NOT NULL
DROP TABLE #ECORESPRODUCTTRANSLATION;
CREATE TABLE #ECORESPRODUCTTRANSLATION
(
Product INT ,
Name NVARCHAR(20)
);
INSERT INTO #ECORESPRODUCTTRANSLATION
( Product, Name )
VALUES ( 1, -- Product - int
N'100001 Description' -- Name - nvarchar(10)
);
INSERT INTO #ECORESPRODUCTTRANSLATION
( Product, Name )
VALUES ( 2, -- Product - int
N'100002 Description' -- Name - nvarchar(10)
);
INSERT INTO #ECORESPRODUCTTRANSLATION
( Product, Name )
VALUES ( 3, -- Product - int
N'100003 Description' -- Name - nvarchar(10)
);
INSERT INTO #ECORESPRODUCTTRANSLATION
( Product, Name )
VALUES ( 4, -- Product - int
N'100004 Description' -- Name - nvarchar(10)
);
INSERT INTO #ECORESPRODUCTTRANSLATION
( Product, Name )
VALUES ( 5, -- Product - int
N'100005 Description' -- Name - nvarchar(10)
);
WITH CTE
AS ( SELECT B.ItemID AS MainItem ,
BV.Name AS BVName ,
B.BOMID ,
BV.ItemID AS ParentItem ,
ECPT.Name AS ParentItemName ,
B.BOMQTY ,
B.UnitID ,
B.BOMQTYSERIE ,
1 AS [Level]
FROM #BOM AS B
JOIN #BOMVERSION AS BV ON BV.BOMID = B.BOMID
JOIN #INVENTTABLE AS IT ON IT.ItemID = BV.ItemID
JOIN #ECORESPRODUCTTRANSLATION AS ECPT ON ECPT.Product = IT.Product
WHERE B.ItemID = '100001'
AND BV.Active = 1
UNION ALL
SELECT C.MainItem ,
C.BVName ,
C.BOMID ,
BV.ItemID ,
C.ParentItemName ,
C.BOMQTY ,
C.UnitID ,
C.BOMQTYSERIE ,
C.[Level] + 1
FROM CTE AS C
JOIN #BOM AS B ON C.ParentItem = B.ItemID
JOIN #BOMVERSION AS BV ON BV.BOMID = B.BOMID
WHERE C.[Level] <= 7
)
SELECT MainItem ,
[1] AS Level1 ,
BVName ,
ParentItemName ,
BOMQTY ,
UnitID ,
BOMQTYSERIE ,
[2] AS Level2 ,
BVName ,
ParentItemName ,
BOMQTY ,
UnitID ,
BOMQTYSERIE ,
[3] AS Level3 ,
BVName ,
ParentItemName ,
BOMQTY ,
UnitID ,
BOMQTYSERIE ,
[4] AS Level4 ,
BVName ,
ParentItemName ,
BOMQTY ,
UnitID ,
BOMQTYSERIE ,
[5] AS Level5 ,
BVName ,
ParentItemName ,
BOMQTY ,
UnitID ,
BOMQTYSERIE ,
[6] AS Level6 ,
BVName ,
ParentItemName ,
BOMQTY ,
UnitID ,
BOMQTYSERIE ,
[7] AS Level7 ,
BVName ,
ParentItemName ,
BOMQTY ,
UnitID ,
BOMQTYSERIE
FROM CTE PIVOT ( MAX(ParentItem) FOR [Level] IN ( [1], [2], [3], [4],
[5], [6], [7] ) ) AS pvt;

Here is a version that is easy to understand and maintain:
;
WITH CTE
AS ( SELECT B.RECID AS MainID ,
B.ITEMID AS MainItem ,
BV.NAME ,
BV.ITEMID AS ParentItem ,
ECPT.NAME AS ParentItemName ,
B.BOMQTY ,
B.UNITID ,
B.BOMQTYSERIE ,
0 AS [level]
FROM #BOM B
JOIN #BOMVERSION BV ON BV.BOMID = B.BOMID
JOIN #INVENTTABLE AS IT ON IT.ITEMID = BV.ITEMID
JOIN #ECORESPRODUCTTRANSLATION AS ECPT ON ECPT.PRODUCT = IT.PRODUCT
WHERE B.ITEMID = '113621'
AND BV.ACTIVE = '1'
UNION ALL
SELECT C.MainID ,
C.MainItem ,
C.NAME ,
BV.ITEMID ,
C.ParentItemName ,
C.BOMQTY ,
C.UNITID ,
C.BOMQTYSERIE ,
C.[level] + 1
FROM CTE C
JOIN #BOM B ON C.ParentItem = B.ITEMID
JOIN #BOMVERSION BV ON BV.BOMID = B.BOMID
WHERE C.[level] <= 6
)
SELECT B.ITEMID ,
C1.ParentItem AS ParentItem1 ,
C1.NAME AS BVName1 ,
C1.ParentItemName AS ParentItemName1 ,
C1.BOMQTY AS BomQty1 ,
C1.UNITID AS UnitID1 ,
C1.BOMQTYSERIE AS BomQtySerie1 ,
C2.ParentItem AS ParentItem2 ,
C2.NAME AS BVName2 ,
C2.ParentItemName AS ParentItemName2 ,
C2.BOMQTY AS BomQty2 ,
C2.UNITID AS UnitID2 ,
C2.BOMQTYSERIE AS BomQtySerie2 ,
C3.ParentItem AS ParentItem3 ,
C3.NAME AS BVName3 ,
C3.ParentItemName AS ParentItemName3 ,
C3.BOMQTY AS BomQty3 ,
C3.UNITID AS UnitID3 ,
C3.BOMQTYSERIE AS BomQtySerie3 ,
C4.ParentItem AS ParentItem2 ,
C4.NAME AS BVName2 ,
C4.ParentItemName AS ParentItemName4 ,
C4.BOMQTY AS BomQty4 ,
C4.UNITID AS UnitID4 ,
C4.BOMQTYSERIE AS BomQtySerie4 ,
C5.ParentItem AS ParentItem5 ,
C5.NAME AS BVName5 ,
C5.ParentItemName AS ParentItemName5 ,
C5.BOMQTY AS BomQty5 ,
C5.UNITID AS UnitID5 ,
C5.BOMQTYSERIE AS BomQtySerie5 ,
C6.ParentItem AS ParentItem6 ,
C6.NAME AS BVName6 ,
C6.ParentItemName AS ParentItemName6 ,
C6.BOMQTY AS BomQty6 ,
C6.UNITID AS UnitID6 ,
C6.BOMQTYSERIE AS BomQtySerie6 ,
C7.ParentItem AS ParentItem7 ,
C7.NAME AS BVName7 ,
C7.ParentItemName AS ParentItemName7 ,
C7.BOMQTY AS BomQty7 ,
C7.UNITID AS UnitID7 ,
C7.BOMQTYSERIE AS BomQtySerie7
FROM #BOM B
LEFT JOIN CTE C1 ON B.RECID = C1.MainID
AND C1.[level] = 0
LEFT JOIN CTE C2 ON B.RECID = C2.MainID
AND C2.[level] = 1
LEFT JOIN CTE C3 ON B.RECID = C3.MainID
AND C3.[level] = 2
LEFT JOIN CTE C4 ON B.RECID = C4.MainID
AND C4.[level] = 3
LEFT JOIN CTE C5 ON B.RECID = C5.MainID
AND C5.[level] = 4
LEFT JOIN CTE C6 ON B.RECID = C6.MainID
AND C6.[level] = 5
LEFT JOIN CTE C7 ON B.RECID = C7.MainID
AND C7.[level] = 6
WHERE B.ITEMID = '113621'
AND C1.ParentItem IS NOT NULL;
EDIT:
Input:
INSERT INTO #BOM VALUES
( 1, N'10', N'1A' ),
( 2, N'20', N'2A' ),
( 3, N'30', N'3A' ),
( 4, N'40', N'4A' ),
( 5, N'50', N'5A' ),
( 6, N'60', N'6A' ),
( 7, N'70', N'7A' ),
( 8, N'80', N'8A' ),
( 9, N'90', N'9A' ),
( 10, N'100', N'10A' ),
( 11, N'110', N'11A' )
INSERT INTO #BOMVERSION VALUES
( 1, N'20', N'10 PRE', N'1A' ),
( 2, N'30', N'20 PRE', N'2A' ),
( 3, N'40', N'30 PRE', N'3A' ),
( 4, N'50', N'40 PRE', N'4A' ),
( 5, N'60', N'50 PRE', N'5A' ),
( 6, N'70', N'60 PRE', N'6A' ),
( 7, N'80', N'70 PRE', N'7A' ),
( 8, N'100', N'90 PRE', N'9A' ),
( 9, N'110', N'100 PRE', N'10A' ),
( 9, N'120', N'110 PRE', N'11A' )
INSERT INTO #Item VALUES
( 1, N'10', N'10 DESC' ),
( 2, N'20', N'20 DESC' ),
( 3, N'30', N'30 DESC' ),
( 4, N'40', N'40 DESC' ),
( 5, N'50', N'50 DESC' ),
( 6, N'60', N'60 DESC' ),
( 7, N'70', N'70 DESC' ),
( 8, N'80', N'80 DESC' ),
( 9, N'90', N'90 DESC' ),
( 10, N'100', N'100 DESC' ),
( 11, N'110', N'110 DESC' )
Output:

I think You need this type of pivoting:
select p.MainItem, p.BVName
, left(p.[0],charindex('|',p.[0])-1) Level0_ID, REPLACE(p.[0], left(p.[0],charindex('|',p.[0])),'') Level0_Decription
, left(p.[1],charindex('|',p.[1])-1) Level1_ID, REPLACE(p.[1], left(p.[1],charindex('|',p.[1])),'') Level1_Decription
, left(p.[2],charindex('|',p.[2])-1) Level2_ID, REPLACE(p.[2], left(p.[2],charindex('|',p.[2])),'') Level2_Decription
, left(p.[3],charindex('|',p.[3])-1) Level3_ID, REPLACE(p.[3], left(p.[3],charindex('|',p.[3])),'') Level3_Decription
, left(p.[4],charindex('|',p.[4])-1) Level4_ID, REPLACE(p.[4], left(p.[4],charindex('|',p.[4])),'') Level4_Decription
, left(p.[5],charindex('|',p.[5])-1) Level5_ID, REPLACE(p.[5], left(p.[5],charindex('|',p.[5])),'') Level5_Decription
, left(p.[6],charindex('|',p.[6])-1) Level6_ID, REPLACE(p.[6], left(p.[6],charindex('|',p.[6])),'') Level6_Decription
, left(p.[7],charindex('|',p.[7])-1) Level7_ID, REPLACE(p.[7], left(p.[7],charindex('|',p.[7])),'') Level7_Decription
from (select t.MainItem, t.BVName, t.level, convert(varchar(20),t.ParentItem)+'|'+t.ParentItemName item from tree t) t
pivot (max(item) for t.level in ([0], [1], [2], [3], [4], [5], [6], [7])) p
If You want use more column to pivot the try this:
select p.MainItem, p.BVName
, p.Level0_ParentItem, p.Level0_ParentItemName
, p.Level1_ParentItem, p.Level1_ParentItemName
, p.Level2_ParentItem, p.Level2_ParentItemName
, p.Level3_ParentItem, p.Level3_ParentItemName
, p.Level4_ParentItem, p.Level4_ParentItemName
, p.Level5_ParentItem, p.Level5_ParentItemName
, p.Level6_ParentItem, p.Level6_ParentItemName
, p.Level7_ParentItem, p.Level7_ParentItemName
from (
select u.MainItem, u.BVName, 'Level'+convert(varchar(30), u.level)+'_'+u.col col, u.value
from (select t.MainItem, t.BVName, t.level, convert(nvarchar(max),t.ParentItem) ParentItem, convert(nvarchar(max),t.ParentItemName) ParentItemName from tree t) t /*Convert all columns to the desired, but the same type*/
unpivot (value for col in (ParentItem, ParentItemName /*and the other columns You want use*/)) u) src
pivot (max(src.value)
for src.col in (
Level0_ParentItem, Level0_ParentItemName
, Level1_ParentItem, Level1_ParentItemName
, Level2_ParentItem, Level2_ParentItemName
, Level3_ParentItem, Level3_ParentItemName
, Level4_ParentItem, Level4_ParentItemName
, Level5_ParentItem, Level5_ParentItemName
, Level6_ParentItem, Level6_ParentItemName
, Level7_ParentItem, Level7_ParentItemName
)) p
In the unpivot statement I make the new columns for the pivot statement.
More from this solution: SQL Server Pivot Table with multiple column aggregates
*** UPDATED WITH FINAL REQUIRED FORMAT ***
WITH CTE
AS ( SELECT B.ITEMID AS MainItem ,
BV.NAME AS BVName ,
B.BOMID ,
BV.ITEMID AS ParentItem ,
ECPT.NAME AS ParentItemName ,
B.BOMQTY ,
B.UNITID ,
B.BOMQTYSERIE ,
1 AS [Level]
FROM BOM AS B
JOIN BOMVERSION AS BV ON BV.BOMID = B.BOMID
JOIN dbo.INVENTTABLE AS IT ON IT.ITEMID = BV.ITEMID
JOIN dbo.ECORESPRODUCTTRANSLATION AS ECPT ON ECPT.PRODUCT = IT.PRODUCT
WHERE B.ITEMID = '113621' /*is this really needed*/
AND BV.ACTIVE = 1
UNION ALL
SELECT C.MainItem ,
C.BVName ,
C.BOMID ,
BV.ITEMID ,
C.ParentItemName ,
C.BOMQTY ,
C.UNITID ,
C.BOMQTYSERIE ,
C.[Level] + 1
FROM CTE AS C
JOIN BOM AS B ON C.ParentItem = B.ITEMID
JOIN BOMVERSION AS BV ON BV.BOMID = B.BOMID
WHERE C.[Level] <= 7
),
TREE
AS ( SELECT CTE.MainItem ,
CTE.BVName ,
CTE.ParentItem ,
CTE.ParentItemName ,
CTE.BOMQTY ,
CTE.UNITID ,
CTE.BOMQTYSERIE ,
CTE.[Level]
FROM CTE
)
SELECT p.MainItem ,
p.Level1_ParentItem ,
p.Level1_BVName ,
p.Level1_ParentItemName ,
p.Level1_BOMQTY ,
p.Level1_UnitID ,
p.Level2_ParentItem ,
p.Level2_BVName ,
p.Level2_ParentItemName ,
p.Level2_BOMQTY ,
p.Level2_UnitID ,
p.Level3_ParentItem ,
p.Level3_BVName ,
p.Level3_ParentItemName ,
p.Level3_BOMQTY ,
p.Level3_UnitID ,
p.Level4_ParentItem ,
p.Level4_BVName ,
p.Level4_ParentItemName ,
p.Level4_BOMQTY ,
p.Level4_UnitID ,
p.Level5_ParentItem ,
p.Level5_BVName ,
p.Level5_ParentItemName ,
p.Level5_BOMQTY ,
p.Level5_UnitID ,
p.Level6_ParentItem ,
p.Level6_BVName ,
p.Level6_ParentItemName ,
p.Level6_BOMQTY ,
p.Level6_UnitID ,
p.Level7_ParentItem ,
p.Level7_BVName ,
p.Level7_ParentItemName ,
p.Level7_BOMQTY ,
p.Level7_UnitID
FROM ( SELECT u.MainItem ,
'Level' + CAST(u.Level AS VARCHAR(20)) + '_' + u.col col ,
u.value
FROM ( SELECT t.MainItem ,
CAST(t.ParentItem AS VARCHAR(30)) AS ParentItem ,
CAST(t.BVName AS VARCHAR(30)) AS BVName ,
CAST(t.ParentItemName AS VARCHAR(30)) AS ParentItemName ,
CAST(t.BOMQTY AS VARCHAR(30)) AS BOMQTY ,
CAST(t.UNITID AS VARCHAR(30)) AS UnitID ,
CAST(t.BOMQTYSERIE AS VARCHAR(30)) AS BOMQTYSERIE ,
t.Level
FROM TREE t
) t /*Convert all columns to the desired, but the same type*/
UNPIVOT ( value FOR col IN ( ParentItem,
ParentItemName, BVName,
BOMQTY, UnitID,
BOMQTYSERIE
/*and the other columns You want use*/ ) ) u
) src PIVOT ( MAX(src.value) FOR src.col IN ( Level1_ParentItem,
Level1_BVName,
Level1_ParentItemName,
Level1_BOMQTY,
Level1_UnitID,
Level2_ParentItem,
Level2_BVName,
Level2_ParentItemName,
Level2_BOMQTY,
Level2_UnitID,
Level3_ParentItem,
Level3_BVName,
Level3_ParentItemName,
Level3_BOMQTY,
Level3_UnitID,
Level4_ParentItem,
Level4_BVName,
Level4_ParentItemName,
Level4_BOMQTY,
Level4_UnitID,
Level5_ParentItem,
Level5_BVName,
Level5_ParentItemName,
Level5_BOMQTY,
Level5_UnitID,
Level6_ParentItem,
Level6_BVName,
Level6_ParentItemName,
Level6_BOMQTY,
Level6_UnitID,
Level7_ParentItem,
Level7_BVName,
Level7_ParentItemName,
Level7_BOMQTY,
Level7_UnitID ) ) p;

Related

TSQL - Mapping Parent/Child Hierarchy

Sample Data:
DECLARE #Hierarchy TABLE
(
[ParentId] INT
, [ChildId] INT
) ;
INSERT INTO #Hierarchy
VALUES
( 1, 2 )
, ( 1, 3 )
, ( 2, 4 )
, ( 3, 5 )
, ( 4, 3 )
, ( 4, 6 )
, ( 5, 6 )
, ( 7, 3 ) ;
Current Query:
; WITH CTE AS
(
SELECT [ParentId]
, [ChildId]
, 1 AS [Level]
, CONCAT ( CAST ( [ParentId] AS VARCHAR(MAX) ), '.', CAST ( [ChildId] AS VARCHAR(MAX) ) ) AS [Path]
FROM #Hierarchy
UNION ALL
SELECT [C].[ParentId]
, [T].[ChildId]
, [C].[Level] + 1
, CAST ( [C].[Path] + '.' + CAST([T].[ChildId] AS VARCHAR(MAX) ) AS VARCHAR(MAX) )
FROM CTE AS [C]
JOIN #Hierarchy AS [T]
ON [C].[ChildId] = [T].[ParentId]
)
SELECT *
FROM CTE
ORDER BY [ParentId]
, [Level]
, [ChildId] ;
Goal:
distinctly group levels of shared "path" together
find the shallowest and the deepest levels of the shared "path"
Expected Output:
NOTICE: the records with Orange highlight at the end are manually inserted to show what I'm expecting, but haven't figure out yet.
Group: Basically a "dense rank" of each "groups" of nodes that follow the same path. I think if you look at the values of Group in the above image and relate it to Level and Path field's data, it'll make more sense.
IsShallowest: 1st level (I can see that now that someone brought it up). Just need to figure out how to derive those missing (repeating) records
IsDeepest: max level within the group.
Think IsShallowest and IsDeepest is easy to figure out once "Group" logic is figured out and adding missing (repeated) records.
Please check this solution. It provide the requested solution except adding the extra row which more information is needed
;WITH CTE AS (
SELECT
[ParentId]
, [ChildId]
, 1 AS [Level]
, CONCAT ( CAST ( [ParentId] AS VARCHAR(MAX) ), '.', CAST ( [ChildId] AS VARCHAR(MAX) ) ) AS [Path]
, MyGroup1 = ROW_NUMBER() OVER(ORDER BY [ParentId])
, MyGroup2 = ROW_NUMBER() OVER(ORDER BY [ParentId])
FROM Hierarchy
UNION ALL
SELECT
[C].[ParentId]
, [T].[ChildId]
, [C].[Level] + 1
, CAST ( [C].[Path] + '.' + CAST([T].[ChildId] AS VARCHAR(MAX) ) AS VARCHAR(MAX) )
, MyGroup1 = C.MyGroup1
, MyGroup2 = [C].[MyGroup1] + ROW_NUMBER() OVER(ORDER BY [T].[ParentId]) - 1
FROM CTE AS [C]
JOIN Hierarchy AS [T] ON [C].[ChildId] = [T].[ParentId]
)
, MyCTE2 as (
SELECT
[ParentId]
, [ChildId]
, [Level]
, [Path]
-- un-comment bellow 2 rows to see the logic
--, MyGroup1
--, MyGroup2
, MyGroup = DENSE_RANK() OVER (ORDER BY MyGroup1, MyGroup2)
FROM CTE
),
MyCTE3 as (
SELECT
[ParentId]
, [ChildId]
, [Level]
, [Path]
, MyGroup
, shallowest = ROW_NUMBER() OVER(PARTITION BY MyGroup ORDER BY [Path])
, deepest = ROW_NUMBER() OVER(PARTITION BY MyGroup ORDER BY [Path] DESC)
FROM MyCTE2
)
SELECT
[ParentId]
, [ChildId]
, [Level]
, [Path]
, MyGroup
, ISshallowest = CASE WHEN shallowest = 1 then 1 else 0 END
, Isdeepest = CASE WHEN deepest = 1 then 1 else 0 END
FROM MyCTE3
ORDER BY
--[Path]
[ParentId]
, [Level]
, [ChildId];

SQL Server : concatenate the primary key ID within a grouped statement

I am trying to select a grouped set of rows and concatenate those rows primary key values into the select statement and count the rows also and select that value.
Tables:
JobTable - JobID, ExpressJob, ItemID
ItemTable - ItemID, Colour, Size
Values in Jobs:
10001, true, 3
10002, true, 3
10003, false, 4
Values in Items:
3, Blue, 1-2
4, Pink, 5-6
Result set:
3,Blue,1-2,10001|10002
3,Pink,5-6,10003
I've explored the following within the select statement:
SELECT
i.ItemID, i.Colour, i.Size,
COUNT(i.ItemID) AS Quantity,
j.ExpressJob,
JobIDArray = STUFF((SELECT CONVERT(VARCHAR(10), jb.JOBID)
FROM Jobs jb
WHERE jb.JobID = j.JobID
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 1, ''))
FROM
Jobs j
INNER JOIN
Items i ON i.ItemID = j.ItemID
GROUP BY
i.ItemID, i.Colour, i.Size, j.ExpressJob
But I keep getting an aggregate group error on JobID. From what I have researched online FROM XML is the way to go but for some reason not effective when selecting the ID column.
Small tweak to what you already have will get you there.
Give this a try:
DECLARE #Jobs TABLE
(
[JobID] INT
, [ExpressJob] NVARCHAR(100)
, [ItemID] INT
);
DECLARE #Items TABLE
(
[ItemID] INT
, [Colour] NVARCHAR(100)
, [Size] NVARCHAR(100)
);
INSERT INTO #Jobs (
[JobID]
, [ExpressJob]
, [ItemID]
)
VALUES ( 10001, 'true', 3 )
, ( 10002, 'true', 3 )
, ( 10003, 'false', 4 );
INSERT INTO #Items (
[ItemID]
, [Colour]
, [Size]
)
VALUES ( 3, 'Blue', '1-2' )
, ( 4, 'Pink', '5-6' );
SELECT [i].[ItemID]
, [i].[Colour]
, [i].[Size]
, [j].[ExpressJob]
, COUNT([i].[ItemID]) AS [Quantity]
--Added '|' as that was how you wanted the results delimited
, STUFF((
SELECT '|' + CONVERT(VARCHAR(10), [jb].[JobID])
FROM #Jobs [jb]
WHERE [jb].[ItemID] = [i].[ItemID] --Change here as you're looking for JobID associated to the Item.
FOR XML PATH('') --No need to set TYPE or use '.value'
)
, 1
, 1
, ''
) AS JobIDArray
FROM #Jobs [j]
INNER JOIN #Items [i]
ON [i].[ItemID] = [j].[ItemID]
GROUP BY [i].[ItemID]
, [i].[Colour]
, [i].[Size]
, [j].[ExpressJob];

Insert into temp table from stored procedure causes error

I have this stored procedure:
ALTER Procedure [dbo].[sp_Prd_Dashboard_Summary]
(#Period AS INT)
AS
SELECT
SiteName AS SiteName,
MAX(Country) AS Country,
BudgetPrj,
MAX(PeriodEnd) AS PeriodEnd,
MAX(DaysMtd) AS DaysMtd,
MAX(ToGoMtd) AS ToGoMtd,
MAX(PeriodToTDays) AS PeriodToTDays,
SUM(MTDRevenue) AS MtdRev,
SUM(MTDRevenue) / NULLIF(MAX(DaysMTD), 0) * MAX(PeriodToTDays) AS PrjRevenue,
SUM(BdgRevenue) AS BdgRev, SUM(TrgRevenue) AS TrgRev,
SUM(BCMMtd) AS BCMMtd, SUM(HrsMtd) AS HrsMTD,
SUM(FuelVal) AS FuelVal, SUM(FuelLtrs) AS FuelLtrs,
SUM(FuelVal) / NULLIF(SUM(MTDRevenue), 0) AS FuelPerc
FROM
(SELECT
St.SiteName as SiteName,
St.Country as Country,
Null as BudgetPrj, Prd.PeriodEnd as PeriodEnd,
Day(GetDate()) as DaysMtd,
Prd.PeriodNoDays - Day(GetDate()) as ToGoMtd,
Prd.PeriodNoDays as PeriodToTDays,
0 as MTDRevenue, 0 as BdgRevenue,
0 as TrgRevenue, 0 as BCMMtd,
0 as HrsMtd, 0 as FuelVal,
0 as FuelLtrs
FROM
Periods Prd
JOIN
Sites St ON Prd.PeriodSiteID = St.SiteId
WHERE
Prd.Period = #Period AND St.SiteActive = 1
UNION All
Select SiteName as SiteName
, Dit.Country as Country
, Null as BudgetPrj
, Null as PeriodEnd
, 0 DaysMtd
, 0 as ToGoMtd
, 0 as PeriodToTDays
, IIF(Dit.Wcode = 101,
IIF(DiT.WBillMeth = 'Hours', DiT.Hrs * DiT.OpBill,
IIF(DiT.WBillMeth = 'BCM', Loads * DiT.ModelSize * DiT.WBillRate,
IIF(DiT.WBillMeth = 'Cost Plus', (DiT.Hrs * (DiT.OwnBill + DiT.OpBill)) +
(DiT.ShiftHrs * DiT.EmpBill),0))),0) as MTDRevenue
, 0 as BdgRevenue
, 0 as TrgRevenue
, IIF(DiT.WBillMeth = 'BCM', Loads * DiT.ModelSize, 0) as BCMMtd
, IIF(Dit.Wcode = 101,
IIF(DiT.WBillMeth <> 'BCM', DiT.Hrs, 0),0) as HrsMtd
, DiT.Fuel * DiT.FuelRate as FuelVal
, DiT.Fuel as FuelLtrs
From DataInputTotal DiT
Where DiT.Period = #Period and DiT.SiteActive = 1
Union All
Select SiteName as SiteName
, St.Country as Country
, Bdgt.BudgetProject as BudgetPrj
, Prd.PeriodEnd as PeriodEnd
, 0 as DaysMtd
, 0 as ToGoMtd
, 0 as PeriodToTDays
, 0 as MTDRevenue
, PrjRev as BdgRevenue
, BudgTarget as TrgRevenue
, 0 as BCMMtd
, 0 as HrsMtd
, 0 as FuelVal
, 0 as FuelLtrs
From Budget Bdgt Join
Sites St on Bdgt.SiteId = St.SiteId Join
Periods Prd on Bdgt.Period = prd.Period and Bdgt.SiteId = Prd.PeriodSiteID
Where Bdgt.Period = #Period and St.SiteActive = 1
) a
Group By SiteName, BudgetPrj
I am trying to call the procedure and insert the result into a temp table with the below script:
Declare #Period int = 22
Declare #DaysinMonth Int = 29
Declare #DayHrs Int = 24
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL DROP TABLE #Temp
Create Table #Temp (SiteName nvarchar(50)
, Country nvarchar(50)
, BudgetPrj nvarchar(50)
, PeriodEnd DateTime
, DaysMtd Int
, ToGoMtd Int
, PeriodToTDays Int
, MtdRev Numeric(13,2)
, PrjRevenue Numeric(13,2)
, BdgRev Numeric(13,2)
, TrgRev Numeric(13,2)
, BCMMtd Numeric(13,2)
, HrsMtd Numeric(13,2)
, FuelVal Numeric(13,2)
, FuelLtrs Numeric(13,2)
, FuelPerc Numeric(13,2)
, FltCnt Int
, Availibility Numeric(13,2)
, Utilization Numeric(13,2)
, Idle Numeric(13,2)
)
Insert #temp
Exec sp_Prd_Dashboard_Summary #Period
Insert into #temp
Exec summary_fleet_performance #DayHrs, #Period, #DaysinMonth
When running the script I get the following error:
Column name or number of supplied values does not match table definition.
I have checked the aliases of the SELECT and all columns have names.
I ended creating two temp tables and inserting each stored procedure into each table and then joining them.
It worked perfectly for me:
ALTER Procedure [dbo].[summary_dashboard]
(
#Period int,
#DaysinMonth Int,
#DayHrs Int
)
as
Declare #Tbl1 as table (SiteName nvarchar(50) null
, Country nvarchar(50) null
, BudgetPrj nvarchar(50) null
, PeriodEnd DateTime null
, DaysMtd Int null
, ToGoMtd Int null
, PeriodToTDays Int null
, MtdRev Numeric(13,2) null
, PrjRevenue Numeric(13,2) null
, BdgRev Numeric(13,2) null
, TrgRev Numeric(13,2) null
, BCMMtd Numeric(13,2) null
, HrsMtd Numeric(13,2) null
, FuelVal Numeric(13,2) null
, FuelLtrs Numeric(13,2) null
, FuelPerc Numeric(13,2) null
)
Declare #Tbl2 as Table (SiteName nvarchar(50) null
, FltCnt Int null
, Availability Numeric (5,2) null
, Utilization Numeric (5,2) Null
, Idle Numeric(5,2)
)
insert into #Tbl1 (SiteName
, Country
, BudgetPrj
, PeriodEnd
, DaysMtd
, ToGoMtd
, PeriodToTDays
, MtdRev
, PrjRevenue
, BdgRev
, TrgRev
, BCMMtd
, HrsMtd
, FuelVal
, FuelLtrs
, FuelPerc
)
Exec sp_Prd_Dashboard_Summary #Period
insert into #tbl2 (SiteName
, FltCnt
, Availability
, Utilization
, Idle
)
Exec summary_fleet_performance #DayHrs, #Period, #DaysinMonth
select tbl1.SiteName
, tbl1.Country
, tbl1.BudgetPrj
, tbl1.PeriodEnd
, tbl1.DaysMtd
, tbl1.ToGoMtd
, tbl1.PeriodToTDays
, tbl1.MtdRev
, tbl1.PrjRevenue
, tbl1.BdgRev
, tbl1.TrgRev
, tbl1.BCMMtd
, tbl1.HrsMtd
, tbl1.FuelVal
, tbl1.FuelLtrs
, tbl1.FuelPerc
, tbl2.FltCnt
, tbl2.Availability
, tbl2.Utilization
, tbl2.Idle
from #tbl1 tbl1 full outer join
#tbl2 tbl2
on tbl1.SiteName = tbl2.SiteName
TRY THIS: Your stored procedure is returning 14 columns and temporary table has more than that so you have to mention columnS in the INSERT INTO #TEMP as below and if you are not specifying the columns name of table then returning columns from the STORED PROCEDURE also must be same.
INSERT INTO #temp(SiteName
, Country
, BudgetPrj
, PeriodEnd
, DaysMtd
, ToGoMtd
, PeriodToTDays
, MtdRev
, PrjRevenue
, BdgRev
, TrgRev
, BCMMtd
, HrsMtd
, FuelVal
, FuelLtrs)
Exec sp_Prd_Dashboard_Summary #Period
The temporary table to which you are trying to insert rows contains more columns than the result from stored procedure. Provide column names in insert query, like this:
Insert into table(Column1, Column2....)

Increase the performance of an inventory (FIFO) query

My goal is to get the price from item which goes into stock and put the price into out item by (FIFO order by TranDate), then calculate the total price of any line of production (" lineid ").
This is what I have done
-- temp table for result
declare #Test table ( stockID int , OriQty decimal(16,2) ,inuse decimal(16,2) , price decimal(16,2) , lineid int , Inid int )
declare #StockIn table (ID int , StockID int ,qty decimal(16,2),Price decimal(16,2), tranDate Date , running int)
insert into #StockIn(ID , StockID , qty , Price , tranDate , running) values
(1,1 , 15 , 430 , '2014-10-09' , 1),
(2,1 , 10 , 431, '2014-12-09' , 2),
(3,1 , 15 , 432, '2015-02-02' , 3),
(4,2 , 15 , 450, '2014-08-05' , 1),
(5,2 , 6 , 450, '2014-10-09' , 2),
(6,2 , 15 , 452, '2015-02-02' , 3)
-- lineid = line production
declare #StockOut table (ID int , StockID int ,qty decimal(16,2), lineid int, tranDate Date)
insert into #StockOut(ID , StockID ,qty , lineid, tranDate )
values
(1,1 , 20 , 2, '2014-10-10'),
(2,1 , 10 , 4, '2014-12-20'),
(3,2 , 12 , 8, '2014-10-01'),
(4,2 , 3 , 8, '2014-10-01') ;
DECLARE #intFlag INT
SET #intFlag = 1 -- initial of loop
WHILE (#intFlag <= (Select Max(ID) from #StockOut) )
BEGIN
declare #stockID int
declare #ids table (ID int , Inuse decimal(16,2))
declare #lineid int
declare #qtyToRemove decimal(16,2) -- get qty from #StockOut
--get data from #StockOut row by row
select #stockID = StockID , #qtyToRemove = qty , #lineid = lineid from #StockOut where ID = #intFlag;
with cte as (
select *, sum(qty) over (order by tranDate ASC , qty DESC , tranDate ASC Rows UNBOUNDED PRECEDING) - #qtyToRemove
as Total FROM #StockIn Where stockID = #stockID )
-- running FIFO from #StockIn and update QTy when stock is running out
, RunningFIFO as (
select cte.StockID , cte.qty AS OriginalQty , Case when
case when 0 > total then cte.qty else cte.qty-total End > 0 then case when 0 > total then cte.qty else cte.qty-total End else 0 End As Inuse
, cte.Price , #lineid AS lineid , id
from cte
)
-- insert result into result table
insert into #test (stockID , OriQty ,inuse , cte.Price , lineid ,Inid ) OUTPUT inserted.Inid , inserted.inuse into #ids select * from RunningFIFO Where Inuse > 0
UPDATE #StockIn set qty = qty - inuse from #ids A inner join #StockIn B on A.ID = b.ID
SET #intFlag = #intFlag + 1
END
Select sum(inuse * price) AS total , lineid from #Test group by lineid
But when data are more than a thousand, this query runs super slow.
How could I increase the performance of this query?

How do I get the list of scope_identities for a inset into select using Sql Server 2008?

I am trying to do a insert into select from a temp table. My problem is that I need to insert the items and also retrieve IDs of the inserted items in the insert statement.
How do I get the list of identity values that were inserted?
The code is below:
SELECT O.OrderID,
O.SingleAgreementID ,
O.OrderTypeID ,
O.OrderStatusID ,
O.Reference ,
O.CreateDate ,
O.ValidityDate ,
O.DeliveredDate ,
O.PathologyID ,
O.DiscountTypeID ,
O.DiscountAmount ,
O.ValidityDays ,
O.DeductibleTypeID ,
O.DeductibleAmount ,
O.LimitOrder ,
O.Comments ,
O.CreatedUserID ,
O.StartPeriodDate ,
O.EndPeriodDate ,
O.GenerationDay ,
O.ParentOrderID ,
O.CanceledDate ,
O.CanceledUserID INTO #TEMPORDERS
FROM dbo.[Order] O
WHERE O.GenerationDay = DAY(GETDATE() -1)
AND O.OrderTypeID = 2
AND #Yesterday BETWEEN CONVERT(VARCHAR(10),O.StartPeriodDate,111) AND CONVERT(VARCHAR(10),O.EndPeriodDate,111)
INSERT INTO dbo.[Order]
( SingleAgreementID ,
OrderTypeID ,
OrderStatusID ,
Reference ,
CreateDate ,
ValidityDate ,
DeliveredDate ,
PathologyID ,
DiscountTypeID ,
DiscountAmount ,
ValidityDays ,
DeductibleTypeID ,
DeductibleAmount ,
LimitOrder ,
Comments ,
CreatedUserID ,
StartPeriodDate ,
EndPeriodDate ,
GenerationDay ,
ParentOrderID ,
CanceledDate ,
CanceledUserID
)
SELECT TEMP.SingleAgreementID ,
TEMP.OrderTypeID ,
1 ,
TEMP.Reference ,
TEMP.CreateDate ,
GETDATE() + TEMP.ValidityDays,
NULL ,
TEMP.PathologyID ,
TEMP.DiscountTypeID ,
TEMP.DiscountAmount ,
TEMP.ValidityDays ,
TEMP.DeductibleTypeID ,
TEMP.DeductibleAmount ,
TEMP.LimitOrder ,
TEMP.Comments ,
'Orden Generada de manera automatica' ,
TEMP.StartPeriodDate ,
TEMP.EndPeriodDate ,
TEMP.GenerationDay ,
TEMP.OrderID ,
NULL ,
NULL
FROM #TEMPORDERS TEMP
--Get all Ids inserted HERE
SELECT SCOPE_IDENTITY();
--Get the IDs saved and insert the detail.
I don't know whether that is possible or not? Any ideas on this?
Thanks.
You can use Output to fill a table with th new ID's
Declare #OutputTable table(aNewid int)
Insert into Table
........
Output inserted.ID
Select ....
from InputTable
Try this one -
INSERT INTO dbo.[Order] (
SingleAgreementID
, OrderTypeID
, OrderStatusID
...
)
OUTPUT INSERTED.[IdentityColumn] INTO #temp1
SELECT t.SingleAgreementID
, t.OrderTypeID
, 1
...
FROM #TEMPORDERS t
SELECT * FROM #temp1