Capturing level in multilevel BOM sql query - sql

I have the following query to capture a multi-level BOM. I now know all the child items for the parent but is there a way to capture the level as well in this query.
with mlBOM
AS (
select bom_item_material_number, bom_item_component
from BOM_TABLE bom
where not exists (
select *
from BOM_TABLE BOM inner join
BOM_TABLE BOM1
on BOM1.bom_item_component = BOM.bom_item_material_number
)
union all
select BOM.bom_item_material_number,
BOM.bom_item_component
from BOM_TABLE BOM
inner join BOM_TABLE mlBOM on mlBOM.bom_item_component = BOM.bom_item_material_number
) select *
from mlBOM
;
Thanks,
Running on DBeaver Postgressql database

You add an integer column that starts at 1 and increment it for each iteration:
with recursive mlbom as (
select bom_item_material_number, bom_item_component, 1 lvl
from bom_table bom
where not exists (
select 1
from bom_table bom1
where bom1.bom_item_component = bom.bom_item_material_number
)
union all
select bom.bom_item_material_number, bom.bom_item_component, mlbom.lvl + 1
from bom_table bom
inner join mlbom on mlbom.bom_item_component = bom.bom_item_material_number
)
select * from mlbom ;
I made a few fixes to the query:
the cte declaration needs the recursive keyword
there is no need to join in the exists subquery of the anchor of the recusive query
presumably, the recursive part of the query oughts to join bom to mlbom instead of self-joining bom.

Related

Use Exists with a Column of Query Result?

I have 2 tables.
One is bom_master:
CHILD
PARENT
1-111
66-6666
2-222
77-7777
2-222
88-8888
3-333
99-9999
Another one is library:
FileName
Location
66-6666_A.step
S:\ABC
77-7777_C~K1.step
S:\DEF
And I want to find out if the child's parents have related files in the library.
Expected Result:
CHILD
PARENT
FileName
1-111
66-6666
66-6666_A.step
2-222
77-7777
77-7777_C~K1.step
Tried below lines but return no results. Any comments? Thank you.
WITH temp_parent_PN(parentPN)
AS
(
SELECT
[PARENT]
FROM [bom_master]
where [bom_master].[CHILD] in ('1-111','2-222')
)
SELECT s.[filename]
FROM [library] s
WHERE EXISTS
(
SELECT
*
FROM temp_parent_PN b
where s.[filename] LIKE '%'+b.[parentPN]+'%'
)
If you have just one level of dependencies use the join solution proposed by dimis164.
If you have deeper levels you could use recursive queries allowed by WITH clause (
ref. WITH common_table_expression (Transact-SQL)).
This is a sample with one more level of relation in bom_master (you could then join the result of the recursive query with library as you need).
DECLARE #bom_master TABLE (Child NVARCHAR(MAX), Parent NVARCHAR(MAX));
INSERT INTO #bom_master VALUES
('1-111', '66-666'),
('2-222', '77-777'),
('3-333', '88-888'),
('4-444', '99-999'),
('A-AAA', '1-111');
WITH
leaf AS ( -- Get the leaf elements (elements without a child)
SELECT Child FROM #bom_master b1
WHERE NOT EXISTS (SELECT * FROM #bom_master b2 WHERE b2.Parent = b1.Child) ),
rec(Child, Parent, Level) AS (
SELECT b.Child, b.Parent, Level = 1
FROM #bom_master b
JOIN leaf l ON l.Child = b.Child
UNION ALL
SELECT rec.Child, b.Parent, Level = rec.Level + 1
FROM rec
JOIN #bom_master b
ON b.Child = rec.Parent )
SELECT * FROM rec
I think you don't have to use exists. The problem is that you need to substring to match the join.
Have a look at this:
SELECT b.CHILD, b.PARENT, l.[FileName]
FROM [bom_master] b
INNER JOIN [library] l ON b.PARENT = SUBSTRING(l.FileName,1,7)

Re-writting the query without "Connect By "

I am rewriting the query to replace to remove CONNECT BY:
SELECT *
FROM ADM_TRT AT
INNER JOIN UTILISATEUR U
ON U.UTI_ID = AT.UTI_ID
INNER JOIN
(
SELECT CM.MAI_ID
FROM CON_MAI CM
CONNECT BY CM.MAI_PER_RES = PRIOR CM.MAI_ID
START WITH CM.MAI_ID IN (
SELECT MAJ_ID
FROM DROIT_LOGIN
WHERE LOG_ID = 21543
)
) CON_MAI_FILTERED_ON_LOGIN
ON AT.TRT_MAI_ID = CON_MAI_FILTERED_ON_LOGIN.MAI_ID;
For CONNECT BY Part , I wrote this
WITH tree (MAI_ID,MAI_PER_RES, level1) AS (
SELECT MAI_PER_RES, MAI_ID, 1 as level1 FROM CON_MAI
UNION ALL
SELECT child.MAI_ID, child.MAI_PER_RES, parent.level1 + 1
FROM CON_MAI child --Line 20
JOIN tree parent
on parent.MAI_PER_RES = child.MAI_ID
)
SELECT MAI_ID FROM tree
But I am stuck to integrate this in subquery in the CONNECT BY sub-query. Can someone please help to integrate this?
It looks like you have the recursion reversed in the recursive sub-query and can use:
WITH tree (MAI_ID) AS (
SELECT MAI_ID
FROM CON_MAI
WHERE MAI_ID IN ( SELECT MAJ_ID
FROM DROIT_LOGIN
WHERE LOG_ID = 21543 )
UNION ALL
SELECT c.MAI_ID
FROM CON_MAI c
JOIN tree p
on c.MAI_PER_RES = p.MAI_ID
)
SELECT *
FROM ADM_TRT AT
INNER JOIN UTILISATEUR U
ON U.UTI_ID = AT.UTI_ID
INNER JOIN tree CON_MAI_FILTERED_ON_LOGIN
ON AT.TRT_MAI_ID = CON_MAI_FILTERED_ON_LOGIN.MAI_ID;
(untested as I do not have your tables or data)

How to distinct column by starting from earliest/latest row with SQL query?

how can I distinct the column but the row were removed is from the earliest found/retain the last?
I have tried some ways, but all of them not worked.
below is the raw, column that I want to work with
parent_item_id
------------------------------------
9B3E7A72-D36A-42D3-A04C-186DEC409F93
942E1854-9EB4-4C19-8A1E-4FCC4953B50C
E75C7294-F0C4-4C6E-8C12-DF5FBC93FA3B
942E1854-9EB4-4C19-8A1E-4FCC4953B50C
942E1854-9EB4-4C19-8A1E-4FCC4953B50C
below is the ways I tried:
using the default behaviour of distinct like this.
query:
WITH tree AS (SELECT distinct(ic.parent_item_id) FROM dbo.item_combination ic, dbo.product p WHERE ic.child_item_id != p.item_id
UNION ALL
SELECT ic.parent_item_id FROM tree t, dbo.item_combination ic WHERE t.parent_item_id=ic.child_item_id
)
SELECT DISTINCT (parent_item_id) from tree
result:
parent_item_id
--
9B3E7A72-D36A-42D3-A04C-186DEC409F93
942E1854-9EB4-4C19-8A1E-4FCC4953B50C
E75C7294-F0C4-4C6E-8C12-DF5FBC93FA3B
using row_number like this. but based on my logic it should change the order but why the final result is the same as way 1?
query:
WITH tree AS (SELECT distinct(ic.parent_item_id) FROM dbo.item_combination ic, dbo.product p WHERE ic.child_item_id != p.item_id
UNION ALL
SELECT ic.parent_item_id FROM tree t, dbo.item_combination ic WHERE t.parent_item_id=ic.child_item_id
)
SELECT DISTINCT(parent_item_id) FROM
(
SELECT t.parent_item_id, [row_number]=ROW_NUMBER() OVER(ORDER BY (SELECT 1)) FROM tree t ORDER BY [row_number] DESC OFFSET 0 ROWS
) r
group by r.parent_item_id, r.[row_number]
result:
parent_item_id
--
9B3E7A72-D36A-42D3-A04C-186DEC409F93
942E1854-9EB4-4C19-8A1E-4FCC4953B50C
E75C7294-F0C4-4C6E-8C12-DF5FBC93FA3B
the result I want/expected is like this.
parent_item_id
--
9B3E7A72-D36A-42D3-A04C-186DEC409F93
E75C7294-F0C4-4C6E-8C12-DF5FBC93FA3B
942E1854-9EB4-4C19-8A1E-4FCC4953B50C
From your comments, This is what I think should happen:
You need to establish a parent-child item view or a product-source view. This would look like:
create view v_ProductSourceMap as
SELECT ic.parent_item_id as item_id, p.item_id as source_id
FROM dbo.item_combination ic left join dbo.product p on ic.child_item_id = p.item_id
group by ic.parent_item_id, p.item_id
Check that the view represents all items derived from other items and for new items, source_id will be null.
select * from v_ProductSourceMap order by item_id, source_id
Now use a recursive query to traverse the mapping
WITH tree AS (
SELECT item_id, source_id, 1 as depth, cast(ic.item_id as varchar(max)) as bc
FROM v_ProductSourceMap ic
WHERE source_id is null
UNION ALL
SELECT ic.item_id, source_id, t.depth + 1, t.bc + '>' + cast(ic.item_id as varchar(max))
FROM tree t, v_ProductSourceMap ic WHERE ic.source_id=t.item_id
)
select * from tree
From here, look at the pattern in the depth and/or the breadcrumbs to figure out what your sort order could be.

Recursive Parent/Child in same table query in SQL where parent is PK

I have seen a lot of examples about how to implement a recursive query where there is the parent and the child in the same table, but in the examples, the child has a parent and I need at the contrary, when a parent has a child.
I would like to obtain all children in recursive mode just like in the image.
In the image, you can see, I have a parent with id 1, it has a child with id 2. The child 2 is a parent too who has a child with id 3, etc.
I don't know how to create a recursive query to obtain all the childs from a parent.
You can access to the next link to execute the sql online: http://www.sqlfiddle.com/#!18/dbed2/1
This produces the results you are asking for:
with cte as (
select idchild, idparent,
convert(varchar(max), idchild) as children
from family f
where not exists (select 1 from family f2 where f2.idparent = f.idchild)
union all
select f.idchild, f.idparent,
concat(f.idchild, ',', cte.children)
from cte join
family f
on cte.idparent = f.idchild
)
select *
from cte
order by idchild;
Here is the SQL Fiddle.
Here you go:
with
n as (
select idparent, idchild, 1 as lvl,
cast(concat('', idchild) as varchar(255)) as children from family
union all
select n.idparent, f.idchild, lvl + 1,
cast(concat(children, ',', f.idchild) as varchar(255))
from n
join family f on f.idparent = n.idchild
)
select n.idparent, f.idchild, n.children
from n
join (
select idparent, max(lvl) as maxlvl from n group by idparent
) m on n.idparent = m.idparent and n.lvl = m.maxlvl
join family f on f.idparent = n.idparent
order by n.idparent
See SQL Fiddle.
if you are using SQL Server 2017 or newer you can use the following:
WITH CTE
AS (SELECT *
FROM dbo.Table_1
UNION ALL
SELECT Child.idParent,
Parent.idChild
FROM CTE AS Parent
INNER JOIN dbo.Table_1 AS Child
ON Parent.idParent = Child.idChild)
SELECT CTE.idParent,
STRING_AGG(CTE.idChild, ', ') AS Childs
FROM CTE
GROUP BY CTE.idParent;
but if you have older version use the following :
WITH CTE
AS (SELECT *
FROM dbo.Table_1
UNION ALL
SELECT Child.idParent,
Parent.idChild
FROM CTE AS Parent
INNER JOIN dbo.Table_1 AS Child
ON Parent.idParent = Child.idChild)
SELECT DISTINCT
B.idParent,
STUFF(
(
SELECT ',' + CONVERT(VARCHAR(10), CTE.idChild)
FROM CTE
WHERE B.idParent = CTE.idParent
ORDER BY CTE.idChild
FOR XML PATH('')
),
1,
1,
''
) AS Childs
FROM CTE AS B

Oracle to T-SQL conversion. How can I make this work?

Having trouble converting Oracle syntax to T-SQL. Trying to convert the following statement:
SELECT ORIG.*
,V.COUNTRY_COMMON_NAME
FROM
(SELECT O.*
,LC.LOCAL_COUNCIL
,LC.REGIONAL_COUNCIL
FROM ES_W_ORG_DIM_INIT O
LEFT JOIN ES_W_ORG_DIM_INIT CNTR ON (O.RSC_CNTR_ORG_ID = CNTR.ORG_ID)
LEFT JOIN ES_W_LCL_CNCL_BASE LC ON (TO_CHAR(O.STK_DIST_UNIT_NUMBER) =
TRIM(LC.UNITNUMBER))
WHERE O.ORG_TYPE_ID IN (7,8)
UNION
SELECT O.*
,LC.LOCAL_COUNCIL
,LC.REGIONAL_COUNCIL
FROM ES_W_ORG_DIM_INIT O
LEFT JOIN ES_W_ORG_DIM_INIT CNTR ON (O.RSC_CNTR_ORG_ID = CNTR.ORG_ID)
LEFT JOIN ES_W_LCL_CNCL_BASE LC ON (TO_CHAR(O.UNIT_NUMBER) =
TRIM(LC.UNITNUMBER))
WHERE O.ORG_TYPE_ID IN (5,6)
UNION
SELECT O.*
,NULL AS LOCAL_COUNTCIL
,NULL AS REGIONAL_COUNCIL
FROM ES_W_ORG_DIM_INIT O
WHERE O.ORG_TYPE_ID IN (60,61)
) ORIG
LEFT JOIN DW_ERSDB_ORG_ADDR_VW V ON (ORIG.ORG_ID = V.ORG_ID AND
V.ORG_ADDRESS_TYPE_ID = 1)
Attempted conversion:
WITH ORIG AS(
SELECT O.*
,LC.LOCAL_COUNCIL
,LC.REGIONAL_COUNCIL
FROM DSS_ERS_STAGE.ES_ORG_DIM O
LEFT JOIN DSS_ERS_STAGE.ES_ORG_DIM CNTR ON (O.RSC_CNTR_ORG_ID = CNTR.ORG_ID)
LEFT JOIN DSS_ERS_STAGE.ES_W_LCL_CNCL_BASE LC ON (CONVERT(VARCHAR,
O.STK_DIST_UNIT_NUMBER) = RTRIM(LTRIM(LC.UNITNUMBER)))
WHERE O.ORG_TYPE_ID IN (7,8)
UNION
(SELECT O.*
,LC.LOCAL_COUNCIL
,LC.REGIONAL_COUNCIL
FROM DSS_ERS_STAGE.ES_ORG_DIM O
LEFT JOIN DSS_ERS_STAGE.ES_ORG_DIM CNTR ON (O.RSC_CNTR_ORG_ID = CNTR.ORG_ID)
LEFT JOIN DSS_ERS_STAGE.ES_W_LCL_CNCL_BASE LC ON (CONVERT(VARCHAR,
O.UNIT_NUMBER) = RTRIM(LTRIM(LC.UNITNUMBER)))
WHERE O.ORG_TYPE_ID IN (5,6)
UNION
SELECT O.*
,NULL AS LOCAL_COUNCIL
,NULL AS REGIONAL_COUNCIL
FROM DSS_ERS_STAGE.ES_ORG_DIM O
WHERE O.ORG_TYPE_ID IN (60,61)
))
SELECT ORIG.*, V.COUNTRY_COMMON_NAME
FROM ORIG
LEFT JOIN DSS_ERS_STAGE.DW_ERSDB_ORG_ADDR_VW V ON (ORIG.ORG_ID = V.ORG_ID
AND
V.ORG_ADDRESS_TYPE_ID = 1)
*Just a note that the schemas specified are required in the target database
SQL Server error:
Msg 8156, Level 16, State 1, Line 1
The column 'LOCAL_COUNCIL' was specified multiple times for 'ORIG'.
Any ideas on how I can engineer this to make it work in SQL Server?
Jamie mentioned this in a comment but I'll try to explain in a bit more detail. For purposes of illustration, suppose I have the following two very simple tables.
create table CouncilA (LOCAL_COUNCIL int);
create table CouncilB (LOCAL_COUNCIL int);
insert CouncilA values (1);
insert CouncilB values (1);
SQL Server does allow you to query a result set that has non-unique column names. For instance, the following is legal:
select *
from
CouncilA A
inner join CouncilB B on A.LOCAL_COUNCIL = B.LOCAL_COUNCIL;
It produces the following result set:
LOCAL_COUNCIL LOCAL_COUNCIL
1 1
However, the documentation for common table expressions explicitly states:
Duplicate names within a single CTE definition are not allowed.
So if I try to wrap my earlier query like this, as you've done in your attempted conversion:
with CTE as
(
select *
from
CouncilA A
inner join CouncilB B on A.LOCAL_COUNCIL = B.LOCAL_COUNCIL
)
select * from CTE;
Then I get the error message that you're seeing:
Msg 8156, Level 16, State 1, Line 7
The column 'LOCAL_COUNCIL' was specified multiple times for 'CTE'.
Incidentally, the same is true for a sub-SELECT:
select * from
(
select *
from
CouncilA A
inner join CouncilB B on A.LOCAL_COUNCIL = B.LOCAL_COUNCIL
) X;
Result:
Msg 8156, Level 16, State 1, Line 13
The column 'LOCAL_COUNCIL' was specified multiple times for 'X'.
The error message you see refers to ORIG, which is the name of your CTE, so the definition of that CTE has multiple columns called LOCAL_COUNCIL, which presumably means that your ES_W_ORG_DIM_INIT table has a column called LOCAL_COUNCIL. Make sure your column names are unique within your CTE and you should be okay.