if you have parent table
create table parent (
pid int not null,
name varchar(255)
)
and a parent-child join table
create table parent_child (
pid int not null,
cid int not null,
foreign key (pid) references parent(pid),
foreign key (cid) references child(cid)
)
create table child(
cid int not null,
name varchar(255)
)
How can I find all parent's names where all their children have names in the following list ('dave','henry','myriam','jill').
I don't want to see a parent if they have a child with a different name, but if they have 1 or more children and all their children have names in the list I want to see the parent's name.
I did find this https://stackoverflow.com/a/304314/1916621 that will help me find a parent with children of the exactly those names, but I can't figure out how to the parents who only have children with names in a subset of that list.
Extra points if someone knows performance tradeoff for different approaches.
SELECT
p.pid,
p.name
FROM
parent p
WHERE NOT EXISTS (
SELECT *
FROM
parent_child pc
JOIN child c
ON pc.cid = c.cid
AND c.name NOT IN ('dave','henry','myriam','jill')
WHERE
p.pid = pc.pid
) AND EXISTS (
SELECT *
FROM
parent_child pc
JOIN child c
ON pc.cid = c.cid
AND c.name IN ('dave','henry','myriam','jill')
WHERE
p.pid = pc.pid
)
Another method... no sub-queries, but additional DISTINCT needed to eliminate duplication of parent records from joining to the parent_child table.
SELECT DISTINCT
p.pid,
p.name
FROM
parent p
JOIN parent_child pc_exists ON pc_exists.pid = p.pid
JOIN child c_exists
ON c_exists.cid = pc_exists.cid
AND c_exists.name IN ('dave','henry','myriam','jill')
LEFT JOIN parent_child pc_notExists ON pc_notExists.pid = p.pid
LEFT JOIN child c_notExists
ON c_notExists.cid = pc_notExists.cid
AND c_notExists.name NOT IN ('dave','henry','myriam','jill')
WHERE
c_notExists.cid IS NULL
Here is my moderate bet:
Sample tables:
Parent
PID NAME
1 dad john
2 mum sandy
3 dad frank
4 mum kate
5 mum jean
Child
CID NAME
11 dave
22 maryam
33 henry
44 maryam
16 jill
17 lina
23 jack
34 jill
55 dave
Parent_Child
PID CID
1 11
1 16
1 17
2 22
3 33
4 44
2 23
5 55
3 34
Query:
select p.pid, p.name,
group_concat(c.name) as children
from parent as p
inner join parent_child as pc
on p.pid = pc.pid
join child as c
on pc.cid = c.cid
where c.name
in ('dave','henry','maryam','jill')
group by p.pid
;
Results:
PID NAME CHILDREN
1 dad john dave,jill
2 mum sandy maryam
3 dad frank henry,jill
4 mum kate maryam
5 mum jean dave
SQLFIDDLE
Using REGEXP and GROUP_CONCAT
It is something much better than in or find_in_set for SQL. The change I did, I used the list as a comma delimitted string ;)
*But the issue here: that group_concat string order has to be found in the comman delimitted string.* Unless we make the REGEXP much efficient :)
Query:
select x.pid, x.name,
x.children from(
select p.pid, p.name,
group_concat(c.name) as children,
count(c.name) as counts
from parent as p
inner join parent_child as pc
on p.pid = pc.pid
join child as c
on pc.cid = c.cid
group by p.pid) as x
where 'dave,maryam,henry,jill'
REGEXP x.children
;
Results:
PID NAME CHILDREN
3 dad frank henry,jill
4 mum kate maryam
5 mum jean dave
*SQLFIDDLE
Related
so I have the following table:
personID grade classID
13 7 147
13 7 456
19 8 123
19 8 789
25 7 123
25 7 456
25 7 789
82 8 147
82 8 456
155 7 456
155 7 789
I would like to keep the grade column, but have the personID and classID be mapped to another table to get the personName and ClassName - each are different tables, but the one separate table with the person name also has person ID, and the one with the class name has the classID so it makes it easy.
The result table should still be 11 columns long, just replaced with the persons name and class name for each row.
Is this done with a join?
Yes. The base idea is:
select
p.personID,
p.personName,
g.grade,
c.classID,
c.className
from grades g
inner join persons p on p.personID = g.personID
inner join classes c on c.classID = g.classID
This assumes the following data structures:
persons:
personID
personName
classes:
classID
className
grades:
personID --> foreign key to persons(personID)
grade
classID --> foreign key to classes(classID)
Select * from
gradeTable g
JOIN
personTable p
ON g.PersonID = p.PersonID
JOIN
classTable c
ON g.ClassID = c.ClassID
I have these tables
person table with following data
person_id description
1 first in the family
2 second in the family
3 third in the family
4 fourth int the family
5 fifth in the family
person_name table with following data
person_id first_name
1 Santiago
2 Lautaro
3 Lucas
4 Franco
5 Agustín
father table with following data
person_father_id description
1 father of Lautaro
2 father of Lucas
3 father of Franco
4 father of Agustín
children table with following data
person_child_id person_father_id
2 1
3 2
4 3
5 4
how to get full name (Agustín Franco Lucas Lautaro Santiago) of person where person_id 4 in select pl/sql query. the core table is person
You could use a hierarchical query with an inline view that joins the relevant tables together first. The query for the inline view could be:
select p.person_id, pn.first_name, c.person_father_id
from person p
join person_name pn on pn.person_id = p.person_id
left join children c on c.person_child_id = p.person_id;
PERSON_ID FIRST_NAME PERSON_FATHER_ID
---------- ---------- ----------------
2 Lautaro 1
3 Lucas 2
4 Franco 3
5 Agustín 4
1 Santiago
and with that as the basis for a hierarchical query:
select trim(sys_connect_by_path(first_name, ' ')) as whole_name
from (
select p.person_id, pn.first_name, c.person_father_id
from person p
join person_name pn on pn.person_id = p.person_id
left join children c on c.person_child_id = p.person_id
)
where connect_by_isleaf = 1
start with person_id = 4
connect by person_id = prior person_father_id;
WHOLE_NAME
--------------------------------------------------
Franco Lucas Lautaro Santiago
Or you could make the hierarchical query itself a further subquery, and then join to the names afterwards and aggregate that:
select listagg(pn.first_name, ' ') within group (order by lvl) as whole_name
from (
select person_id, level as lvl
from (
select p.person_id, c.person_father_id
from person p
left join children c on c.person_child_id = p.person_id
)
start with person_id = 4
connect by person_id = prior person_father_id
) t
join person_name pn on pn.person_id = t.person_id;
WHOLE_NAME
--------------------------------------------------
Franco Lucas Lautaro Santiago
Notice that with both of those you have to join the tables before you can filter based on your starting ID (start with rather than where, as this is hierarchical). That means that with larger tables it could end up doing more work than you really need or expect.
Or you could do the same thing with recursive subquery factoring (a recursive CTE) if you prefer, and you are using Oracle 11gR2 or higher:
with r (person_id, person_father_id, lvl) as (
select p.person_id, c.person_father_id, 1
from person p
left join children c on c.person_child_id = p.person_id
where p.person_id = 4
union all
select p.person_id, c.person_father_id, r.lvl + 1
from r
join person p on p.person_id = r.person_father_id
left join children c on c.person_child_id = p.person_id
)
select listagg(pn.first_name, ' ') within group (order by lvl) as whole_name
from r
join person_name pn on pn.person_id = r.person_id;
WHOLE_NAME
--------------------------------------------------
Franco Lucas Lautaro Santiago
which looks more complicated but can at least put the filter in the anchor member of the recursive CTE.
Read more about hierarchical queries and recursive subquery factoring.
excuse the bad title but I couldn't find a good way to express what I want in abstract terms.
Anyway I have 3 tables
tbl_product:
PID | productname
1 | product 1
2 | product 2
3 | product 3
4 | product 4
..
tbl_categories, motherCategory allows me to nest categories:
CID | categoriename | motherCategory
1 | electronics | NULL
2 | clothing | NULL
3 | Arduino | 1
4 | Casings, extra's | 3
..
tbl_productInCategory PID and CID are foreign keys to PID and CID in tbl_product and tbl_categories respectively. A product can have multiple categories assigned to it so PID can occur more than once in this table.
PID | CID
1 | 1
2 | 1
3 | 3
4 | 4
Now I have a query that returns all categories if I give the mothercategory.
What I want to do is show ONLY the categories that have products in them recursively.
for instance on the example data above I show all categories(motherCategory is null), I want it to return only electronics since there are no products category 2, clothing.
However the problem I am having is that I also want this to work recursively. Consider this tbl_productInCategory:
PID | CID
1 | 2
2 | 2
3 | 2
4 | 4
Now it should return both clothing and electronics even though there are no products in electronics, because there are products in the nested category arduino->Casings, extra's. If I show all categories with motherCategory, electronics it should also return arduino.
I can't figure out how to do this and any help or pointers are appreciated.
First you should select all categories where products exist. On the next steps select mother categories.
WITH CTE AS
(
SELECT tbl_categories.*
FROM
tbl_categories
JOIN tbl_productInCategory on tbl_productInCategory.CID = tbl_categories.CID
UNION ALL
SELECT tbl_categories.*
FROM tbl_categories
JOIN CTE on tbl_categories.CID = CTE.motherCategory
)
SELECT DISTINCT * FROM CTE
Use a recursive CTE to get a derived table of your category tree, and then INNER JOIN it to your ProductCategory table.
It's not something I've done before, but some googling indicates it is possible.
https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx
The semantics of the recursive execution is as follows:
Split the CTE expression into anchor and recursive members.
Run the anchor member(s) creating the first invocation or base result set (T0).
Run the recursive member(s) with Ti as an input and Ti+1 as an output.
Repeat step 3 until an empty set is returned.
Return the result set. This is a UNION ALL of T0 to Tn.
USE AdventureWorks2008R2;
GO
WITH DirectReports (ManagerID, EmployeeID, Title, DeptID, Level)
AS
(
-- Anchor member definition
SELECT e.ManagerID, e.EmployeeID, e.Title, edh.DepartmentID,
0 AS Level
FROM dbo.MyEmployees AS e
INNER JOIN HumanResources.EmployeeDepartmentHistory AS edh
ON e.EmployeeID = edh.BusinessEntityID AND edh.EndDate IS NULL
WHERE ManagerID IS NULL
UNION ALL
-- Recursive member definition
SELECT e.ManagerID, e.EmployeeID, e.Title, edh.DepartmentID,
Level + 1
FROM dbo.MyEmployees AS e
INNER JOIN HumanResources.EmployeeDepartmentHistory AS edh
ON e.EmployeeID = edh.BusinessEntityID AND edh.EndDate IS NULL
INNER JOIN DirectReports AS d
ON e.ManagerID = d.EmployeeID
)
-- Statement that executes the CTE
SELECT ManagerID, EmployeeID, Title, DeptID, Level
FROM DirectReports
INNER JOIN HumanResources.Department AS dp
ON DirectReports.DeptID = dp.DepartmentID
WHERE dp.GroupName = N'Sales and Marketing' OR Level = 0;
GO
OK, I'm an absolute beginner in SQL and I got one task to solve and I'm stuck, so I need help on your ideas how to get the required results.
I have 2 tables - first one is PARENTS, with the following data:
ID Name Age
1 John 25
2 Peter 28
3 Anny 30
4 Jack 32
and the second table is CHILDRENS, with the following data:
children_id parent_id name age
1 1 mary 5
2 1 Susanne 4
3 2 stephen 12
4 4 Kevin 7
What SQL command can be used to get following result:
id parent name number of childrens
1 John 2
2 Peter 1
3 Anny 0
4 Jack 1
Thanks in advance!
Try like this
select PARENTS.id,PARENTS.name,count(CHILDRENS.name)
from PARENTS left join CHILDRENS on PARENTS.id=CHILDRENS.parent_id
group by PARENTS.id,PARENTS.name
As mentioned by Jarlh in comments, Use LEFT OUTER JOIN + Group by
SELECT p.id,
p.name,
Count(parent_id) as number_of_childrens
FROM PARENTS p
LEFT OUTER JOIN CHILDRENS c
ON c.parent_id = p.ID
Group by p.id,
p.name
select p.id, p.name, count(p.id) as number_of_childrens
from parents p
left join childrens c
on p.parent_id = c.parent_id
group by p.id, p.name
SELECT A.ID
, A.name AS parent_name
, COUNT(B.children_id) AS number_of_children
FROM PARENTS AS A
LEFT JOIN CHILDRENS AS B
ON A.ID = B.parent_id
GROUP BY A.ID
, A.name
A left join, count and group by.
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.