Find all ancestors without direct id-parentid in same table - sql

I have a parent-child structure across two tables. The first table has BOM_ID for bills and ITEM_ID for associated children items. The other has BOM_ID and ITEM_ID for the parent item.
I am able to find the first level of parents' ITEM_ID with the following query
SELECT item_id
FROM bomversion
WHERE bom_id IN (SELECT bom_id FROM bom WHERE item_id = 1)
So, in order to find all ancestors, I would have to repeat this step. I've tried to look at CTE and recursion techniques but all examples have parentid and childid in the same table. I cannot figure this one out.
If 1 is child of 2, 2 is child of 3, 3 is child of 4, and 2 is also child of 5, I am looking for the following result:
ChildID
ParentID
1
2
2
3
2
5
3
4
The starting point will be a specific ChildID.
S O L U T I O N
Based on Adnan Sharif's proposal, I found the solution for my problem:
WITH items_CTE AS (
-- create the mapping of items to consider
SELECT
B.ITEMID AS Child_id,
BV.ITEMID AS Parent_Id
FROM BOM AS B
INNER JOIN BOMVERSION AS BV
ON B.BOMID = BV.BOMID
), parent_child_cte AS (
-- select those items as initial query
SELECT
Child_id,
Parent_id
FROM items_CTE
WHERE Child_id = '111599' -- this is the starting point
UNION ALL
-- recursive approach to find all the ancestors
SELECT
c.Child_Id,
c.Parent_Id
FROM items_CTE c
JOIN parent_child_cte pc
ON pc.Parent_Id = c.Child_id
)
SELECT * FROM parent_child_cte

From the dbfiddle that you shared in the comment, if I understand you correctly, you want to have the rows showing all the parents of a child.
For example, lets consider the hierarchy, 1 is a child of 2, 2 is a child of 3, and 3 is a child of 4. You want the final result as,
child_id
parent_id
1
2
1
3
1
4
2
3
2
4
3
4
If that's the case, we can use recursive CTE to build that table. First of all, we need to be build a relation between those two tables based on bom_id and then we need to find out parent-child relation. After that we will add rows based on the initial query. Please see the below code.
WITH RECURSIVE items_CTE AS (
-- create the mapping of items to consider
SELECT
B.Item_id AS Child_id,
BV.Item_id AS Parent_Id
FROM BOM AS B
INNER JOIN BOMVERSION AS BV
ON B.bom_id = BV.bom_id
), parent_child_cte AS (
-- select those items as initial query
SELECT
Child_id,
Parent_id
FROM items_CTE
UNION
-- recursive approach to find all the ancestors
SELECT
parent_child_cte.Child_Id,
items_CTE.Parent_Id
FROM items_CTE
INNER JOIN parent_child_cte
ON parent_child_cte.Parent_Id = items_CTE.Child_id
)
SELECT * FROM parent_child_cte

Related

Is branch pruning possible for recursive cte query

This is inspired by question Retrieve a list of lists in one SQL statement - I have come up with a solution, but I have doubts on its efficiency.
To restate the problem:
we have 2 Tables: Person and Parent
Person contains basic data about each person
Parent is a join table relating person with its parents
each Person can have multiple parents
we want to receive each person data with list of all their ancestors - each ancestor in its own row
if there are no ancestors, we have only one row for that person, with null parentId
Here is the data format:
Person table
Id <PK>
Name
Parent table
Id<PK>
ParentPersonId <FK into Person >
Person has rows with values PK
1, 'Jim'
2, 'John'
3, 'Anna'
4, 'Peter'
Parent has rows with values
1, 2
1, 3
2, 3
3, 4
So person 1 has ancestors 2, 3, 4
I want the output in the following form
id name parentPersonId
--------------------------
1 Jim 2
1 Jim 3
1 Jim 4
2 John 3
2 John 4
3 Anna 4
4 Peter (null)
My solution used recursive CTE query, but my fear is that it produces too many rows, as each subtree can be entered multiple times. I needed to filter out duplicates with distinct, and execution plan shows that, even with this simple data, sorting for distinct takes 50% of the time. Here is my query:
WITH cte_org AS
(
SELECT per.id, per.name, par.parentPersonId
FROM Person per
LEFT JOIN Parent par ON per.id = par.id
UNION ALL
SELECT o.id, o.name, rec.parentPersonId
FROM Parent rec
INNER JOIN cte_org o ON o.parentPersonId = rec.id
WHERE rec.parentPersonId IS NOT NULL
)
SELECT DISTINCT *
FROM cte_org
ORDER BY id, parentPersonId;
http://sqlfiddle.com/#!18/d7d62/4
My questions:
can I somehow prune the already visited branches, so that recursive-CTE does not produce duplicate rows, and final distinct is not necessary
is recursive CTE a right approach to this problem?
On PostgreSQL you can acheive that by replacing UNION ALL with UNION.
So the query looks like that:
WITH RECURSIVE cte_org AS (
select per.id, per.name, par.parentPersonId
from Person per left join Parent par
on per.id = par.id
UNION
SELECT
o.id,
o.name,
rec.parentPersonId
FROM
Parent rec
INNER JOIN cte_org o
ON o.parentPersonId = rec.id
where rec.parentPersonId is not null
)
SELECT *
FROM cte_org
ORDER BY id, parentPersonId;
http://sqlfiddle.com/#!17/225cf4/4

Keep only one record from left table

I need to get records after inner join without duplicate data from left table.
Parent table :
Parent id parent name
1 Douglas
Child table :
Parent id child name
1 George
1 Michael
With classic Oracle Inner join I get this :
Parent id parent name child name
1 Douglas George
1 Douglas Michael
But I need this result:(i need null values instead of parents names)
Parent id parent name child name
1 Douglas George
Null Null Michael
Of course, there are more parents and children.
Use row_number() or lag() or lead(), like here:
select case when rn = 1 then parent_id end parent_id,
case when rn = 1 then parent_name end parent_name,
child_id, child_name
from (select p.parent_id, p.parent_name, c.child_id, c.child_name,
row_number() over (partition by p.parent_id order by c.child_id) rn
from parents p join children c on p.parent_id = c.parent_id)
rextester demo
PARENT_ID PARENT_NAME CHILD_ID CHILD_NAME
---------- ----------- ---------- ----------
1 Douglas 1 George
2 Michael
You want to do a right join on child id
select p.parentid,p.parentname,c.childname
from parent p
right join child c
on p.parentid=c.childid
One way of doing this is with a CURSOR EXPRESSION:
select parent_id
, parent_name
, cursor ( select child_id
, child_name
from child
where child.parent_id = parent.parent_id )
from parent
How well this renders depends on the client you use to run it. Some handle the output better than others.
Alternatively you can treat it as a display issue. For instance, in SQL*Plus you could use BREAK ON parent_id ON parent_name to suppress repeating values. Find out more.
select parent.parent_id
, parent.parent_name
, child.child_id
, child.child_name
from parent
join child
on child.parent_id = parent.parent_id
order by parent.parent_id, child.child_id;
To make this work you have to have ordering.
"It seems more complicated than I thought. Is there other way without Cursor?"
That's because result sets are meant to be flat not jagged. Tweaking the query to implement display features often results in clunky or verbose SQL.
Talking of which, here's another solution for you:
with cte as (
select parent.parent_id
, parent.parent_name
, child.child_id
, child.child_name
, row_number() over (partition by parent.parent_id
order by child.child_id ) as prn
, row_number() over (order by parent.parent_id , child.child_id ) as rn
from parent
join child
on child.parent_id = parent.parent_id
)
select case when prn = 1 then parent_id else null end as parent_id
, case when prn = 1 then parent_name else null end as parent_name
, child_id
, child_name
from cte
order by rn
This generates two row numbers, one to track the parents, one to sort the entire row set.

sql - How to return NULL row from left table for each parent even when child records exist

I have 2 tables with a parent-child relation. I'd like to return a row with NULL child values for parents that have children, in addition to the child records.
For example, for tables parent and child:
SELECT parent.parentid, childid
FROM parent
LEFT JOIN child
ON parent.parentid=child.parentid
For a parent parentA with children childA1, childA2, I'd like a result:
parentA, NULL
parentA, childA1
parentA, childA2
A normal LEFT JOIN will not return the first row. A UNION query works, but I'm wondering if there is a simpler/better query, using standard SQL for Oracle and SQL Server. My UNION query is:
SELECT parent.parentid, childid
FROM parent
INNER JOIN child ON parent.parentid=child.parentid
UNION
SELECT parent.parentid, NULL
FROM parent
I think your query is better written as:
SELECT p.parentid, childid
FROM parent p INNER JOIN
child c
ON p.parentid = c.parentid
UNION ALL
SELECT p.parentid, NULL
FROM parent p;
That is UNION ALL is better than UNION, because UNION has additional overhead for removing duplicate values. If you can have duplicates in parent, just use SELECT DISTINCT in the second query.
Also, you might want to add an ORDER BY to get results in order the question seems to specify:
ORDER BY parentid, (CASE WHEN child is NULL then 0 else 1 end), chile
An alternative way of doing it would be
SELECT parent.parentid,
childid
FROM parent
LEFT JOIN child
ON parent.parentid = child.parentid
GROUP BY GROUPING SETS( ( parent.parentid ), ( parent.parentid, childid ) )
HAVING (childid IS NOT NULL) OR GROUPING(childid) =1
ORDER BY parentid ASC,
GROUPING(childid) DESC
If you only want the row for parents with children you should use an inner join
SQL Fiddle - Oracle
SQL Fiddle - SQL Server

Select parent if all children meet criteria

I have tables set up like so:
Parent
------
id, ...
Child
-----
id, parent_id, x, y
I want to find the Parents, or the distinct parent_id(s), if all of the rows in Child containing a given parent_id meet a criteria involving x and y(in my case x = y).
For example:
Parent
------
id
1
2
3
Child
id, parent_id, x, y
1, 1, 2, 3
2, 1, 3, 4
3, 2, 5, 5
4, 2, 6, 7
5, 3, 8, 8
6, 3, 9, 9
would result in 3. Currently, I have a query that finds parent_ids that any of the children meet the criteria. I then use that to retrieve those records and check them in code if all the children meet the criteria. With the example data, I get parent_id 2 and 3, get the two parent records with all children, and evaluate. I want to do this with a single query, if possible.
You can use NOT EXISTS
SELECT id
FROM Parent p
WHERE NOT EXISTS
(
SELECT 1 FROM Child c
WHERE c.parent_Id = p.id
AND c.x <> c.y
)
Edit: Here's the sql-fiddle: http://sqlfiddle.com/#!3/20128/1/0
Should join 2 tables first because the parents does not have children that will satisfy
And should add index for pa_id column
SELECT DISTINCT pa.id
FROM pa INNER JOIN c ON c.pa_id = pa.id
WHERE NOT EXISTS ( SELECT 1 FROM c WHERE c.parent_Id = p.id and c.x <> c.y )
This is what you need?
select id from parent where id not in(
select parent_id from child
where x<>y
group by parent_id)
Old question, but I think it worth to give my 5 cents on this topic. I believe more efficient way is to use HAVING clause:
SELECT
Parent.id
FROM
Parent
JOIN Child ON Child.parent_id = Parent.id
GROUP BY
Parent.id
HAVING
SUM( CASE WHEN Child.x = Child.y THEN 1 ELSE 0 END) = COUNT( * )

Finding all children for multiple parents in single SQL query

Suppose I have a table with parent-child relationships.
parent child
1 4
1 5
2 6
3 7
4 8
6 9
7 10
8 11
Now I have a query that returns a list of people (eg. 1 and 2) and I want to find all their children, grandchildren etc. (in this case: 4, 5, 6, 8, 9, 11).
I know I can use common table expressions to search recursively, but I wondered if I could create a SQL statement to find all descendents at once without having to iterate over the input set.
Edit: sorry for not being clear enough. I'm looking for something like:
SELECT {Hierarchical relation} from table where parent in (1,2)
which should result in a single output column with rows for 4, 5, 6, 8, 9, 11.
I'm no longer interested in the relationship in the output, just the complete set of family members for multiple families.
Here it is
---- PlainTable ----
parent idElement (child)
Null 1
1 4
1 5
2 6
3 7
4 8
6 9
7 10
8 11
WITH tableR (parent, idElement)
AS
(
-- Anchor member definition
SELECT e.parent, e.idElement
FROM PlainTable AS e
WHERE parent in (1,2)
UNION ALL
-- Recursive member definition
SELECT e.parent, e.idElement
FROM PlainTable AS e
INNER JOIN tableR AS d
ON e.parent = d.idElement
)
-- Statement that executes the CTE
SELECT idElement
FROM tableR --inner join to plain table by id if needed
Ok, danihp's solution did put me on the right track. This is the solution I came up with:
DECLARE #Input TABLE (
id int
)
INSERT INTO #Input VALUES (1),(2)
;WITH Relations (parent, child)
AS
(
SELECT e.parent, e.child
FROM RelationTable AS e
WHERE parent in (SELECT * FROM #Input)
UNION ALL
SELECT e.parent, e.child
FROM RelationTable AS e
INNER JOIN Relations AS d
ON e.parent = d.child
)
SELECT child
FROM Relations
It results in a list of child ids (excluding the 2 parent ids like I said earlier in the question):
4,5,6,8,9,11
using CTE
Recursive Queries Using Common Table Expressions
http://msdn.microsoft.com/en-us/library/ms186243.aspx
and
http://www.sqlservercentral.com/articles/T-SQL/65540/
be can help you.
SQL Server 2008 has built in features to facilitate hierarchical data: http://msdn.microsoft.com/en-us/magazine/cc794278.aspx
I know I can use common table expressions to search recursively, but I
wondered if I could create a SQL statement to find all descendents at
once without having to iterate over the input set.
I'm not sure what you mean by that. Most (maybe all?) CTE's can be accomplished through the use of subqueries, but using a subqueries wouldn't be any faster. When you say of you don't want to 'iterate' over the input set it sounds like you're talking about the use of cursors and of course you can do it as a set based operation (using CTEs or subqueries) but there's no way around the recursion.
Edit: I'm sorry, I'm not thinking straight... of course you can't do recursion with normal subqueries but the point still stands that even if you could it wouldn't be faster. If you'd like to see strategies for doing recursion without CTE's then try searches for something like 'recursion sql 2000' since CTE's weren't around back then. Here are some examples: http://www.mssqltips.com/sqlservertip/938/recursive-queries-with-sql-server-2000/. Of course, the answer to your question remains the same though.
Typically I use a Nested Set Model for this and Yes you can have SQL do all the work for you, in fact you have SQL output XML that can be directly attached to .net treeview. Hope this helps
Link: Is there a simple way to query the children of a node?
While waiting for an updated post:
SELECT DISTINCT
p.parent AS parent
, c.child AS child
, IFNULL(g.child, 'NONE') AS grandchild_of_parent
FROM parent_child as p
LEFT JOIN parent_child AS c ON p.parent = c.parent
LEFT JOIN parent_child AS g ON c.child = g.parent;
Results would look like this:
parent child grandchild_of_parent
1 4 8
1 5 NONE
2 6 9
3 7 10
4 8 11
6 9 NONE
7 10 NONE
8 11 NONE
Such a simple-minded-but-probably-harder-to-maintain type of code, but since I'm not familiar with SQL Server 2008's built in features to handle this type of request, I'll just throw a long shot...
EDIT:
Just so you can see results for yourself while you study common table expressions and/or perhaps pivots ... this will get your results, but only up to the great grandchildren of 1 and 2.
-- A. Parents 1 and 2
SELECT DISTINCT p.parent FROM parent_child AS p
WHERE p.parent IN (1,2)
UNION
-- B : Children of A
SELECT DISTINCT p.child FROM parent_child AS p
WHERE p.parent IN (1,2)
UNION
-- C : Children of B, Grandchildren of A
SELECT DISTINCT p.child FROM parent_child AS p
WHERE p.parent IN (
SELECT DISTINCT p.child FROM parent_child AS p
WHERE p.parent IN (1,2)
)
UNION
-- D : Children of C, Great-Grandchildren of A
SELECT DISTINCT p.child FROM parent_child AS p
WHERE p.parent IN (
SELECT DISTINCT p.child FROM parent_child AS p
WHERE p.parent IN (
SELECT DISTINCT p.child FROM parent_child AS p
WHERE p.parent IN (1,2)
)
)
Again, I strongly suggest you study what the others have been posting.. and look into the links they provided. The inelegant query I provided you is not going to last long->it will absolutely FAIL once you have great-great-grandchildren.
---- PlainTable ----
parent idElement (child_id)
Null 1
1 4
1 5
2 6
3 7
4 8
6 9
7 10
8 11
**Table value function to get Child ids at 4(any) Level of parent in the same table:-**
FUNCTION fc_get_Child_IDs(Parent)
DECLARE #tbl TABLE (ID int)
DECLARE #level int=4
DECLARE #i int=1
insert into #tbl values (Parent)
while #i < #level
BEGIN
INSERT into #tbl
select child_id from PlainTable where Parent in (select ID from #tbl) and child_id not in (select ID from #tbl)
set #i = #i + 1
END