Select parent if all children meet criteria - sql

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( * )

Related

Find all ancestors without direct id-parentid in same table

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

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

Access To Recursive Tree With Cte [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I have 3 table of data: some people has access a branch(s) and branch can have some sub branches
Table Persons
ID PName
1 P1
2 P2
Table Branches
ID Title BrachIDRef
0 Master null
1 B1 0
2 B2 1
3 B3 2
4 B4 2
5 B5 1
6 B6 0
7 B7 6
Table PersonBranches
Id PersonIDref BranchIDref
1 1 1
2 2 6
3 2 2
And Result I need is Access of Persons to branches and Sub branches in SQL Server Query like this :
P1 B1
P1 (and All Childs of B1 - for each child have a record of data)
P2 B6
P2 and All Childs if B6
P2 B2
P2 and All Childs Of B2
You need to use recursive CTE for Branches table and you need to use ROW_NUMBER() for generate dynamic unique record ID, which will use order records.
Before UNION ALL select query generate parent record values with record ID.
After UNION ALL select query generate child records based on parent record.
Please check below query for your expected result.
SELECT * INTO #Persons
FROM (
SELECT '1' ID,'P1' PName UNION ALL
SELECT '2','P2') a
SELECT * INTO #Branches
FROM (
SELECT '0' ID,'Master' Title,NULL BrachIDRef UNION ALL
SELECT '1','B1','0' UNION ALL
SELECT '2','B2','1' UNION ALL
SELECT '3','B3','2' UNION ALL
SELECT '4','B4','2' UNION ALL
SELECT '5','B5','1' UNION ALL
SELECT '6','B6','0' UNION ALL
SELECT '7','B7','6'
) a
SELECT * INTO #PersonBranches
FROM (
SELECT '1' Id,'1' PersonIDref,'1' BranchIDref UNION ALL
SELECT '2' Id,'2' PersonIDref,'6' BranchIDref UNION ALL
SELECT '3' Id,'2' PersonIDref,'2' BranchIDref
) a
;WITH branchCTE
AS
(
SELECT
B.ID,B.Title,B.BrachIDRef,P.PName,ROW_NUMBER() OVER (ORDER BY B.BrachIDRef,B.ID) AS RID
FROM #Branches B
INNER JOIN #PersonBranches PB ON PB.BranchIDref = B.ID
INNER JOIN #Persons P ON P.ID = PB.PersonIDref
UNION ALL
SELECT
B1.ID,B1.Title,B1.BrachIDRef,C.PName,C.RID
FROM #Branches B1
INNER JOIN branchCTE C ON C.ID = B1.BrachIDRef
WHERE B1.BrachIDRef > 0
)
SELECT
C.PName,
C.Title
FROM branchCTE C
ORDER BY C.RID
DROP TABLE #Persons;
DROP TABLE #Branches;
DROP TABLE #PersonBranches;
You can use connect by prior with different CTE and union it with your normal query to fetch all the children as follows:
cte as
(SELECT ANCESTOR,
LISTAGG(case when Title = ANCESTOR then null else title end, '/') within group (order by null) as pth from
(SELECT DISTINCT ID,
CONNECT_BY_ROOT Title as ANCESTOR,
Title
FROM Branches
CONNECT BY PRIOR ID = BrachIDRef)
GROUP BY
ANCESTOR)
--
SELECT pb.id, p.pname, b.title
from PersonBranches pb
join persons p on (p.id = pb.PersonIDref)
join Branches b on (pb.BranchIDref = b.id)
union all
SELECT pb.id, p.pname, c.pth
from PersonBranches pb
join persons p on (p.id = pb.PersonIDref)
join Branches b on (pb.BranchIDref = b.id)
join cte c on (b.title = c.ANCESTOR)
order by id;
db<>fiddle demo
Cheers!!

Linking and counting records with one or multiple criteria

I am attempting to link together some records to identify sibling groups. The way we can do this is to identify clients who share the same parents.
A sample of the SQL follows:
SELECT
A.ClientID,
B.ParentID
FROM A
LEFT JOIN B ON B.ClientID to A.ClientID
AND B.REL_END is NULL AND B.REL_CODE = 'PAR'
Which would return data in the following format:
Client ID Parent ID
1 A
1 B
2 C
2 D
3 C
3 E
4 C
4 D
How I would like it to appear follows:
Client ID No. of Siblings
1 0
2 2
3 2
4 2
Hopefully the table shows that child 1 has 0 siblings (shares no parents with 2,3,4), child 2 has 2 siblings (3 and 4), child 3 has 2 siblings (2 and 4) and child 4 has 2 siblings (2,3). It seems like it should be simple enough to achieve this but I'm really struggling at the minute to think how! I think it's made slightly more confusing because a child may share only one parent with another child to be considered as a sibling.
Hopefully this is clear, thanks.
Edit: Just to make it clear, the relationship is identified by a child sharing a parent ID with another child (the ids are unique, but I provided generic ones for this example). So as Child 2, 3 and 4 all have a parent with an id of C they are considered siblings.
You may try this query, it displays to me the desired output.
with c_data as (
select a.clientid, b.parentid, count(a.clientid) over (partition by parentid order by parentid) as c_parents
FROM A
LEFT JOIN B ON (B.ClientID = A.ClientID)
AND B.REL_END is NULL AND B.REL_CODE = 'PAR'
)
select clientid as "Client ID", max(c_parents) -1 as "No of Siblings"
from c_data
group by clientid;
Example:
SQL> with c_data as (
2 select a.clientid, b.parentid, count(a.clientid) over (partition by parentid order by parentid) as c_parents
3 FROM A
4 LEFT JOIN B ON (B.ClientID = A.ClientID)
5 AND B.REL_END is NULL AND B.REL_CODE = 'PAR'
6 )
7 select clientid as "Client ID", max(c_parents) -1 as "No of Siblings"
8 from c_data
9 group by clientid;
Client ID No of Siblings
---------- --------------
1 0
2 2
4 2
3 2
Transcurrido: 00:00:00.03
SQL>
With the analytic function we count all client id's partitioned by the parentid related of the current tupple to count all clients that have in common the same parent.
After in the projection, we get the max number of parent in common for each client and substracts 1, the client itself.
Hope this helps!
Regards!
This is rather complicated. If you can assume exactly two parents for each child, then you can do something like:
select c.*, count(*) over (partition by min_parent, max_parent) - 1 as NumSiblings
from (SELECT A.ClientID, min(B.ParentID) as min_parent, max(b.parentid) as max_parent
FROM A LEFT JOIN
B
ON B.ClientID to A.ClientID AND B.REL_END is NULL AND B.REL_CODE = 'PAR'
group by a.clientid
) c
What this does is calculate the two parents for each client. It then uses the windows function to count the number of clients that have exactly the same parents. The "-1" is because all children are counted, and we don't want to count the current child.
If you can have more than two parents, then the query is more complicated.
If you want only one parent being shared (rather than two), then you can handle using a self join:
with cp as (SELECT A.ClientID, B.ParentID
FROM A LEFT JOIN
B
ON B.ClientID to A.ClientID AND B.REL_END is NULL AND B.REL_CODE = 'PAR'
)
select cp.client_id, count(distinct cp1.client_id) as NumSiblings
from cp left outer join
cp cp1
on cp.parent_id = cp1.parent_id and cp.client_id <> cp1.client_id
group by cp.client_id
Well, I don't understand how the table and relation are made, but you could do something like that :
SELECT ClientID, sum(NumberOfSibling) as NumberOfSibling
from(
SELECT A.ClientID, (select count(ClientID)
from A2
LEFT JOIN B2 ON B2.ClientID to A2.ClientID
AND B2.REL_END is NULL AND B2.REL_CODE = 'PAR'
where B2.ParentID = B.ParentID) as NumberOfSibling
FROM A
LEFT JOIN B ON B.ClientID to A.ClientID
AND B.REL_END is NULL AND B.REL_CODE = 'PAR'
) GROUP BY ClientID
Explanation : I took your request and modify the second result to return the sum of siblings wich possess the same Parent (that's the select count(*) ..., that's also the part I'm not sure about the conditions). And put this in another to group by ClientID to have the sum of the 2 parents.

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