Let's say I have two tables, "Parent" and "Child". Parent-to-Child is a many:many relationship, implemented through a standard cross-referencing table.
I want to find all records of Parent that are referenced by ALL members of a given set of Child using SQL (in particular MS SQL Server's T-SQL; 2005 syntax is acceptable).
For example let's say I have:
List item
Parent Alice
Parent Bob
Child Charlie references Alice, Bob
Child David references Alice
Child Eve references Bob
My goals are:
If I have Children Charlie, I want the result set to include Alice and Bob
If I have Children Charlie and David, I want the result set to include Alice and NOT Bob.
If I have Children Charlie, David, and Eve, I want the result set to include nobody.
Relying on a numerical trick (where the number of parent-child links = the number of children, that parent is linked to all children):
SELECT Parent.ParentID, COUNT(*)
FROM Parent
INNER JOIN ChildParent
ON ChildParent.ParentID = Parent.ParentID
INNER JOIN Child
ON ChildParent.ChildID = Child.ChildID
WHERE <ChildFilterCriteria>
GROUP BY Parent.ParentID
HAVING COUNT(*) = (
SELECT COUNT(Child.ChildID)
FROM Child WHERE <ChildFilterCriteria>
)
Here's an answer.
SQL query: Simulating an "AND" over several rows instead of sub-querying
And here's a specific application of that to this problem.
SELECT * FROM Parents
WHERE ParentId in
(
SELECT ParentId FROM ChildParent
WHERE ChildId in
(
SELECT ChildId FROM Child
WHERE ChildName in ('Charlie', 'David')
)
GROUP BY ParentId
HAVING COUNT(*) = 2
)
( I guess where you said "Child Eve references Eve" you meant "Child Eve references Bob", right?)
I think I've got it... looks ugly... the secret is the double negation... that is, everyone for which it's true,, is the same as not anyone for which is false... (ok, I have troubles with my english, but I guess you understand what I mean)
select * from parent
parent_id name
--------------------------------------- --------------------------------------------------
1 alice
2 bob
select * from child
child_id name
--------------------------------------- --------------------------------------------------
1 charlie
2 david
3 eve
select * from parent_child
parent_id child_id
--------------------------------------- ---------------------------------------
1 1
2 1
1 2
2 3
select * from parent p
where not exists(
select * from child c
where
c.child_id in ( 1, 2, 3 ) and
not exists(
select * from parent_child pc where
pc.child_id = c.child_id and
pc.parent_id = p.parent_id
)
)
--when child list = ( 1 )
parent_id name
--------------------------------------- --------------------------------------------------
1 alice
2 bob
--when child list = ( 1, 2 )
parent_id name
--------------------------------------- --------------------------------------------------
1 alice
--when child list = ( 1, 2, 3 )
parent_id name
--------------------------------------- --------------------------------------------------
well, I hope it helps...
Related
I have an Oracle Tree hierarchy structure that is basically similar to the following table called MY_TABLE
(LINK_ID,
PARENT_LINK_ID,
STEP_ID )
with the following sample data within MY_TABLE:
LINK_ID PARENT_LINK_ID STEP_ID
-----------------------------------------------
A NULL 0
B NULL 0
AA A 1
AB A 1
AAA AA 2
BB B 1
BBB BB 2
BBBA BBB 3
BBBB BBB 3
Based on the above sample data, I need to produce a report that basically returns the total count of rows for all children
of both parent link IDs (top level only required), that is, I need to produce a SQL query that returns the following information, i.e.:
PARENT RESULT COUNT
----------------------------
A 3
B 4
So I need to rollup total children that belong to all (parent) link ids, where the LINK_IDs have a PARENT_LINK_ID of NULL
I think something like this:
select link, count(*)-1 as "RESULT COUNT"
from (
select connect_by_root(link_id) link
from my_table
connect by nocycle parent_link_id = prior link_id
start with parent_link_id is null)
group by link
order by 1 asc
Please try:
WITH parent(LINK_ID1, LINK_ID, asCount) AS
(
SELECT LINK_ID LINK_ID1, LINK_ID, 1 as asCount
from MY_TABLE WHERE PARENT_LINK_ID is null
UNION ALL
SELECT LINK_ID1, t.LINK_ID, asCount+1 as asCount FROM parent
INNER JOIN MY_TABLE t ON t.PARENT_LINK_ID = parent.LINK_ID
)
select
LINK_ID1 "Parent",
count(asCount)-1 "Result Count"
From parent
group by LINK_ID1;
SQL Fiddle Demo
I have a parent/child relation with the following child table:
CHILD_ID PARENT_ID CHILD_VALUE
------------------------------
1 1 x
2 1 y
3 2 y
Now I want to select all existing distinct parents where the CHILD_VALUE is:
child has value = x (there will be only 1 or none)
if no child with value x exists - NULL
In other words show all parents with "matching" child or NULL if no child "matching" the value exists.
So the result shall look like this:
PARENT_ID CHILD_ID CHILD_VALUE
------------------------------
1 1 x
2 NULL NULL
Question would be how I can narrow down the join in this case.
This does the trick:
select distinct a.parent_id, b.child_id, b.child_value
from test1 a
left outer join test1 b
on a.parent_id = b.parent_id
and b.child_value = 'x'
See also this SQL fiddle.
I have a self-referencing table with content like this:
Self-referencing parent table
ID ParentID Name
---------------------
1 John
2 1 Mike
3 2 Erin
4 1 Janie
5 Eric
6 5 Peter
The tree hierarchy should look like this
John
Mike
Erin
Janie
Eric
Peter
And a child table that stores the leaf of parent table that looks like this:
ID Sales
3 100
3 100
4 200
4 200
6 300
6 300
6 300
I'm trying to roll-up the sum from the leaf node up to the hierarchy so it would return as ..
ID Name Sum
1 John 800
2 Mike 200
3 Erin 200
4 Janie 400
5 Eric 900
6 Peter 900
Any ideas how to achieve this in sql 2008? Thanks in advance.
EDIT All aggregation moved out of the CTE
WITH
tree AS
(
SELECT
id AS root_id,
name AS root_name,
id AS leaf_id
FROM
yourTreeTable
UNION ALL
SELECT
tree.root_id AS root_id,
tree.name AS root_name,
yourTreeTable.id AS leaf_id
FROM
tree
INNER JOIN
yourTreeTable
ON tree.leaf_id = yourTreeTable.ParentID
)
SELECT
tree.root_id,
tree.root_name,
COALESCE(SUM(yourScoresTable.score), 0) AS total
FROM
tree
LEFT JOIN
yourScoresTable
ON yourScoresTable.ID = tree.leafID
GROUP BY
tree.root_id,
tree.root_name
Here it is:
Let's supose this schema:
​create table #parent (
ID int,
ParentID int,
Name varchar(50) );
create table #child (
ID int,
Sales int );
The query is self explained:
WITH
tree AS
(
SELECT
id as id_parent,
id as id
FROM
#parent
UNION ALL
SELECT
tree.id_parent as id_parent,
#parent.id AS id
FROM
tree
INNER JOIN
#parent
ON tree.id = #parent.ParentID
)
SELECT
#parent.id,
#parent.name,
COALESCE(SUM(#child.Sales), 0) AS total
FROM
#parent
LEFT JOIN
tree
ON #parent.ID = tree.id_parent
LEFT JOIN
#child on tree.id = #child.id
GROUP BY
#parent.id,
#parent.name
CTE returns a list of 'leafs' for each employee (#parent), then query sums all sales for this 'leafs'. You can test it running.
EDITED
Query is fixed.
I have a single table with people's info such as
ID
Family (last name)
Name (first name)
Status
IDFather
IDMother
I'd like to bring up the information containing childs' Name and Family, as well as their parent's ID, Name and Family, but only those records where parents are dead. So far I've done this, but it only selects children who are dead, not parents (status 5 is deceased).
select * from A1 where (IDFather IS NOT NULL and IDMother IS NOT NULL) and [Status] = '5'
Table sample below:
ID Family Name Status IDFather IDMother
001 Watson Jason 1 123 321
002 Smith Matt 5 333 111
003 Smith Mike 1 002 NULL
004 Derulo Sam 5 NULL NULL
005 Pitt Jenny 1 NULL 004
I tried this statement:
select * from Table1 where [Status] = '5'
I then wrote a select statement in C#
select ID,Name,Family from Table1 where IDFather = "value" or IDMother = "value"
At the end of the day I need the Name, Family of the child and Name and Family of their parents with parents ID.
select father.id,
father.family,
father.name
mother.id,
mother.family,
mother.name
child.id,
child.family,
child.name
from A1 father,
A1 mother,
A1 child
where child.IDFather = father.id
and child.IDMother = child.id
and (father.status = 5 OR mother.status = 5)
Replace the OR with AND if you want to retrieve records where both parents are dead
Oh, and if this is a homework assignment, you may want to rewrite my archaic join syntax with the more modern vernacular. I'm stuck in my ways...
Try this:
SELECT * from your_table
WHERE
IDFather IN
(SELECT ID from your_table WHERE Status = 5)
AND IDMother IN
(SELECT ID from your_table WHERE Status = 5)
If you want also dead people you could try:
SELECT * from your_table
WHERE
Status = 5
OR (
IDFather IN
(SELECT ID from your_table WHERE Status = 5)
AND IDMother IN
(SELECT ID from your_table WHERE Status = 5))
Given a simple (id, description) table t1, such as
id description
-- -----------
1 Alice
2 Bob
3 Carol
4 David
5 Erica
6 Fred
And a parent-child relationship table t2, such as
parent child
------ -----
1 2
1 3
4 5
5 6
Oracle offers a way of traversing this as a tree with some custom syntax extensions:
select parent, child, sys_connect_by_path(child, '/') as "path"
from t2
connect by prior parent = child
The exact syntax is not important, and I've probably made a mistake in the above. The
important thing is that the above will produce something that looks like
parent child path
------ ----- ----
1 2 /1/2
1 3 /1/3
4 5 /4/5
4 6 /4/5/6
5 6 /5/6
My question is this: is it possible to join another table within the sys_connect_by_path(), such as the t1 table above, to produce something like:
parent child path
------ ----- ----
1 2 /Alice/Bob
1 3 /Alice/Carol
... and so on...
In your query, replace T2 with a subquery that joins T1 and T2, and returns parent, child and child description. Then in the sys_connect_by_path function, reference the child description from your subquery.
Based on Mike McAllister's idea, the following uses a derived table to achieve the desired result:
select
T.PARENT
,T.CHILD
,sys_connect_by_path(T.CDESC, '/')
from
(
select
t2.parent as PARENT
,t2.child as CHILD
,t1.description as CDESC
from
t1, t2
where
t2.child = t1.id
) T
where
level > 1 and connect_by_isleaf = 1
connect by prior
T.CHILD = T.PARENT
In my problem, all the parents are anchored under a "super-parent" root, which means that the paths can be fully described with SYS_CONNECT_BY_PATH, thereby obviating the need for cagcowboy's technique of concatenating the parent with the path.
SELECT parent, child, parents.description||sys_connect_by_path(childs.description, '/') AS "path"
FROM T1 parents, T1 childs, T2
WHERE T2.parent = parents.id
AND T2.child = childs.id
CONNECT BY PRIOR parent = child