SQL Query to find Parent-Child Child-Parent relationships? - sql

o_O
If I have the following records in a table:
Parent Child
1 2 <--
2 1 <--
3 2
3 4
etc...
And I want to identify records that are both the parent of their child AND the child of their parent such as the 2 records identified by arrows above, how would I accomplish this?
I am trying to run some recursive SQL on this table, but these items are causing an infinite loop. I would like to identify these items so they can be addressed manually.
My brain is fried-enough from messing with recursive queries, I have nothing left to solve this one. Please help :)

If understood you well, you don't need recursion at all:
SELECT a.parent, a.child
FROM table1 a
INNER JOIN table1 b ON (b.child=a.parent and a.child = b.parent)
You might want to use LEFT JOIN instead of INNER if you also need to display rows that don't satisfy condition .

The following query will work in your example case. If it needs more you'll have to extend the demonstration information
;WITH CTE_DATA AS (
Select Parent = 1, Child = 2
union Select Parent = 2, Child = 1
union Select Parent = 3, CHild = 2
union Select Parent = 3, Child = 4
)
select
d1.*
from
CTE_DATA d1
join CTE_DATA d2 on d1.Child = d2.Parent and d2.Child = d1.Parent

DECLARE #YourTable TABLE (Parent INT, Child INT)
INSERT INTO #YourTable
SELECT 1, 2
UNION
SELECT 2, 1
UNION
SELECT 3, 2
UNION
SELECT 3, 4
SELECT *
FROM #YourTable A
INNER JOIN #YourTable B
ON A.Parent = B.Child AND A.Child = B.Parent

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)

Recursive Query CTE Father - Son - Grandson error

I have a table that has an ID and IDFATHER of some projects, these projects can receive N sons, so, the structure is like
ID
IDFATHER
REV
1
1
0
2
1
1
5
2
2
I need to, iniciating in ID 5 go to ID 1, so I did a CTE Query:
WITH lb (ID, IDFATHER) AS (
SELECT ID, IDFATHER
FROM PROJECTS
WHERE ID = 5
UNION ALL
SELECT I.ID, I.IDFATHER
FROM PROJECTS I
JOIN lb LBI ON I.ID = LBI.IDFATHER
--WHERE I.ID = LBI.IDFATHER -- Recursive Subquery
)
SELECT *
FROM lb
WHERE LB.ID = LB.IDFATHER
When this code runs it gives me:
The statement terminated. The maximum recursion 100 has been exhausted
before statement completion.
So basically I handle it by just adding:
SELECT TOP 1 * FROM LB WHERE LB.ID = LB.IDFATHER
But I really want to know were is my error. Can anyone give me a hand on these?
The first row points to itself so the recursion never stops. You need to add this condition inside the recursive cte:
WHERE LBI.ID <> LBI.IDFATHER
I would rather set IDFather of the first row to NULL.
The recursion didn't stop because your top row refers to itself endlessly.
If the top row has a null parent, that would have stopped the recursion.
Another approach is to use that case id = parentid as the termination logic.
The fiddle
WITH LB (ID, IDFATHER, idstart) AS (
SELECT ID, IDFATHER, id
FROM PROJECTS WHERE ID = 5
UNION ALL
SELECT I.ID, I.IDFATHER, lbi.idstart
FROM PROJECTS I
JOIN LB LBI
ON I.ID = lbi.IDFATHER
AND lbi.id <> lbi.idfather
)
SELECT id AS idtop, idstart
FROM LB
WHERE LB.ID = LB.IDFATHER
;
The result:

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

SQL - where condition for multple child tables

I have some 10 tables in which 1 is parent and other 9 are parallel children.
All these 9 tables have a column named Version with values 0 & above. zero is draft.
I tried with JOINS but with joins I got ambiguity for Version Column.
Is there any way where I can say that any of these 9 child tables has any drafts
Required output is Parent table columns + HasDrafts (From child tables).
Is there any way to achieve this ? If yes then guide me please.
If you don't care which table has the drafts or how many, then you can use exists in a case expression:
select p.*,
(case when exists (select 1 from child1 c where c.parentid = p.parentid and c.version = 0) or
exists (select 1 from child2 c where c.parentid = p.parentid and c.version = 0) or
exists (select 1 from child3 c where c.parentid = p.parentid and c.version = 0) or
. . .
then 1 else 0
end) as has_drafts
from parent p;
I would have made a union and set an extra column for which table it is Ex.
Select ID, Version, 'tableA' from TableA
union
Select ID, Version, 'tableB' from TableB
Thats my dirty solution.

Troubles isolating target cell in recursive sql query

I have a table, let's say it looks like this:
c | p
=====
|1|3|
|2|1|
|7|5|
c stands for current and p stands for parent
Given a c value of 2 I would return its top most ancestor (which has no parent) this value is 3. Since this is a self referencing table, I figured using CTE would be the best method however I am very new to using it. Nevertheless, I gave it a shot:
WITH Tree(this, parent) AS
( SELECT c ,p
FROM myTable
WHERE c = '2'
UNION ALL
SELECT M.c ,M.p
FROM myTable M
JOIN Tree T ON T.parent = M.c )
SELECT parent
FROM Tree
However this returns:
1
3
I only want 3 though. I have tried putting WHERE T.parent <> M.c but that doesn't entirely make sense. Neadless to say, I am a little confused for how to isolate the grandparent.
DECLARE #Table AS TABLE (Child INT, Parent INT)
INSERT INTO #Table VALUES (1,3),(2,1),(7,5)
;WITH cteRecursive AS (
SELECT
OriginalChild = Child
,Child
,Parent
,Level = 0
FROM
#Table
WHERE
Child = 2
UNION ALL
SELECT
c.OriginalChild
,t.Child
,t.Parent
,Level + 1
FROM
cteRecursive c
INNER JOIN #Table t
ON c.Parent = t.Child
)
SELECT TOP 1 TopAncestor = Parent
FROM
cteRecursive
ORDER BY
Level DESC
Use a recursive cte to Recuse up the tree until you cannot. Keep track of the Level of recursion, then take the last level of recursions parent and you have the top ancestor.
And just because I wrote it I will add in if you wanted to find the top ancestor of every child. The concept is still the same but you would need to introduce a row_number() to find the last level that was recursed.
DECLARE #Table AS TABLE (Child INT, Parent INT)
INSERT INTO #Table VALUES (1,3),(2,1),(7,5),(5,9)
;WITH cteRecursive AS (
SELECT
OriginalChild = Child
,Child
,Parent
,Level = 0
FROM
#Table
UNION ALL
SELECT
c.OriginalChild
,t.Child
,t.Parent
,Level + 1
FROM
cteRecursive c
INNER JOIN #Table t
ON c.Parent = t.Child
)
, cteTopAncestorRowNum AS (
SELECT
*
,TopAncestorRowNum = ROW_NUMBER() OVER (PARTITION BY OriginalChild ORDER BY Level DESC)
FROM
cteRecursive
)
SELECT
Child = OriginalChild
,TopMostAncestor = Parent
FROM
cteTopAncestorRowNum
WHERE
TopAncestorRowNum = 1