SQL get Parent where Children have specific values - sql

Given is a Parent with the field id and Child relation with parent_id and name. How would a query look like to get all Parents which have two children, one with the name 'John' and one with the name 'Mike'. My problem is, that I am not able to build a query which returns the Parents having both children. I used Where IN ('John', 'Mike') so I get also the Parents returned which have also one child with the name 'John' or 'Mike'. But I want only the Parents with both children only.
SELECT * FROM Parent
JOIN Child ON Child.parent_id = Parent.id
WHERE Child.name IN ('John', 'Mike')
My query is of course more complex and this is only an abstraction for what I want to achieve. I have in mind, that I first need to join the children on parent_id and make something with that, but no idea.

You can do two joins and look for your specific records. This example shows that parent 1 will return with both kiddos, but not parent 2 that only has a Mike.
DECLARE #parent TABLE (ID INT)
DECLARE #child TABLE (ID INT, parentID INT, name VARCHAR(100))
INSERT INTO #parent
VALUES
(1),
(2),
(3),
(4),
(5),
(6)
INSERT INTO #child (ID, parentID, name)
VALUES
(1, 1, 'Mike'),
(2, 1, 'John'),
(3, 2, 'Mike'),
(4, 2, 'Bill'),
(5, 3, 'Dave'),
(6, 4, 'Sam')
SELECT p.*
FROM #parent p
INNER JOIN #child c1
ON c1.parentID = p.id
AND c1.name = 'Mike'
INNER JOIN #child c2
ON c2.parentID = p.ID
AND c2.name = 'John'

Try having two steps in the where clause. Both conditions will have to be true to return a parent record.
where parent.id in (select parent_id from child where child.name='John')
and parent.id in (select parent_id from child where child.name='Mike')

something like this would work in postgres if you have having.
SELECT parent_id, SUM(num) FROM (
SELECT parent_id, 1 as num FROM Child Where name = 'John'
UNION
SELECT parent_id, 1 as num FROM Child Where name = 'Mike'
) parents
GROUP BY parent_id HAVING SUM(num) = 2

So,
added the solution with the double join into an Ecto query and it passed my tests :)
from c in Child,
join: p in Parent, on: c.parent_id = p.id,
join: cc in Child, on: p.id = cc.parent_id,
where: c.name == ^"John",
where: cc.name == ^"Mike"
select: count(p.id)
Thanks for the ideas and the fast help :)

Related

Calculations through a hierarchy in SQL

I am trying to perform some calculation by navigating through a hierarchy. In the simple example below, where organizations have a headcount and can be associated with parent organizations, the headcount is only specified for "leafs" organizations. I want to calculate the headcount all the way up the hierarchy using the simple rule: parent_headcount = sum(children_headcount).
I liked the idea of using SQL Common Table Expression for this, but this does not quite work. The determination of the level works (as it follows the natural top-down order of navigation), but not the headcount determination.
How would you fix this, or is there a better way to perform this calculation bottom-up?
-- Define the hierachical table Org
drop table if exists Org
create table Org (
ID int identity (1,1) not null, Name nvarchar(50), parent int null, employees int,
constraint [PK_Org] primary key clustered (ID),
constraint [FK_Parent] foreign key (parent) references Org(ID)
);
-- Fill it in with a simple example
insert into Org (name, parent, employees) values ('ACME', NULL, 0);
insert into Org (name, parent, employees) values ('ACME France', (select Org.ID from Org where Name = 'ACME'), 0);
insert into Org (name, parent, employees) values ('ACME UK', (select Org.ID from Org where Name = 'ACME'), 0);
insert into Org (name, parent, employees) values ('ACME Paris', (select Org.ID from Org where Name = 'ACME France'), 200);
insert into Org (name, parent, employees) values ('ACME Lyons', (select Org.ID from Org where Name = 'ACME France'), 100);
insert into Org (name, parent, employees) values ('ACME London', (select Org.ID from Org where Name = 'ACME UK'), 150);
select * from Org;
-- Try to determine the total number of employees at any level of the hierarchy
with Orgs as (
select
ID, name, parent, 0 as employees, 0 as level from Org where parent is NULL
union all
select
child.ID, child.name, child.parent, Orgs.employees + child.employees, level + 1 from Org child
join Orgs on child.parent = Orgs.ID
)
select * from Orgs;
This query returns:
The determination of the level is correct, but the calculation of the headcount is not (UK should be 150, France 300, and 450 at the top fo the hierarchy). It seems that CTE is suitable for top-down navigation, but not bottom-up?
Just another option using the datatype hierarchyid
Note: the #Top and Nesting is optional
Example
Declare #Top int = null
;with cteP as (
Select ID
,Parent
,Name
,HierID = convert(hierarchyid,concat('/',ID,'/'))
,employees
From Org
Where IsNull(#Top,-1) = case when #Top is null then isnull(Parent ,-1) else ID end
Union All
Select ID = r.ID
,Parent = r.Parent
,Name = r.Name
,HierID = convert(hierarchyid,concat(p.HierID.ToString(),r.ID,'/'))
,r.employees
From Org r
Join cteP p on r.Parent = p.ID)
Select Lvl = A.HierID.GetLevel()
,A.ID
,A.Parent
,Name = Replicate('|---',A.HierID.GetLevel()-1) + A.Name
,Employees = sum(B.Employees)
From cteP A
Join cteP B on B.HierID.ToString() like A.HierID.ToString()+'%'
Group By A.ID,A.Parent,A.Name,A.HierID
Order By A.HierID
Returns
You need to traverse hierarchy for every non-leafe node and the sum up all the paths from the node.
with Orgs as (
select
id as [top], ID, name, parent, 0 as employees, 0 as level
from Org g
where exists (select 1 from Org g2 where g.ID = g2.parent)
union all
select
orgs.[top], child.ID, child.name, child.parent, Orgs.employees + child.employees, level + 1 from Org child
join Orgs on child.parent = Orgs.ID
)
select [top] as id, sum(employees) employees
from Orgs
group by [top];
Db fiddle
Try this:
/***** DATA *************/
-- Define the hierachical table Org
drop table Org
create table Org (
ID int identity (1,1) not null, Name nvarchar(50), parent int null, employees int,
constraint [PK_Org] primary key clustered (ID),
constraint [FK_Parent] foreign key (parent) references Org(ID)
);
-- Fill it in with a simple example
insert into Org (name, parent, employees) values ('ACME', NULL, 0);
insert into Org (name, parent, employees) values ('ACME France', (select Org.ID from Org where Name = 'ACME'), 0);
insert into Org (name, parent, employees) values ('ACME UK', (select Org.ID from Org where Name = 'ACME'), 0);
insert into Org (name, parent, employees) values ('ACME Paris', (select Org.ID from Org where Name = 'ACME France'), 200);
insert into Org (name, parent, employees) values ('ACME Lyons', (select Org.ID from Org where Name = 'ACME France'), 100);
insert into Org (name, parent, employees) values ('ACME London', (select Org.ID from Org where Name = 'ACME UK'), 150);
select * from Org;
/******** END DATA ***********/
/******** QUERY ******/
-- Try to determine the total number of employees at any level of the hierarchy
with Orgs as (
select
ID, name, parent, employees, ID as RootID, 0 as level from Org
union all
select
child.ID , child.name, child.parent, child.employees, Orgs.RootID, level + 1 from Org child
join Orgs on child.parent = Orgs.ID
)
select Org.Id,
Org.Parent,
Org.Name,
Org.employees,
(select max(level) from Orgs a where a.Id = Org.Id) as [Level],
S.ProductCountIncludingChildren
from Org
inner join (
select RootID,
sum(employees) as ProductCountIncludingChildren
from Orgs
group by RootID
) as S
on Org.Id = S.RootID
left join Org Org2 on Org2.ID = Org.Parent
order by Org.Id
/**** END QUERY ******/

Distinct results from view - selecting the first record

I am searching parents according to child properties
I have this view returning parent and child information, I cannot change the view definition because I need to provide the child info for filtering.
Since different children can be related to the same parents, I don't get distinct results
How can I force SQL to return single parent results by using the first child found?
For example:
parent1 foo1 child1 data a
parent1 foo1 child3 data a
parent2 foo2 child1 data a
parent2 foo2 child3 data a
parent2 foo2 child4 data a
What I would be happy to get is:
parent1 foo1 child1 data a
parent2 foo2 child1 data a
Sample scripts:
drop table relations;
CREATE TABLE relations(
parent_id nchar(10) NULL,
child_id nchar(10) NULL
)
drop table items;
CREATE TABLE items(
item_id nchar(10) NULL,
data nchar(10) NULL
);
insert into items values('parent1', 'data x');
insert into items values('parent2', 'data y');
insert into items values('child1', 'data a');
insert into items values('child2', 'data b');
insert into items values('child3', 'data a');
insert into items values('child4', 'data a');
insert into relations values('parent1', 'child1');
insert into relations values('parent1', 'child3');
insert into relations values('parent2', 'child1');
insert into relations values('parent2', 'child2');
insert into relations values('parent2', 'child3');
insert into relations values('parent2', 'child4');
drop view v_parent_child;
create view v_parent_child as
select rel.parent_id,parent.data as parent_data ,rel.child_id, child.data as child_data
from relations as rel
join items as parent on rel.parent_id = parent.item_id
join items as child on rel.child_id = child.item_id
;
select * from v_parent_child where child_data = 'data a';
you can do row over partition in the join.
select parent_id, parent_data , child_id, child_data from (
select *,
row_number() over (partition by parent_id order by parent_id) as rn
FROM v_parent_child) t
where t.rn = 1 ;
like here: Oracle 'Partition By' and 'Row_Number' keyword
Try the below view.
create view v_parent_child as
select rel.parent_id,parent.data as parent_data ,rel.child_id, child.data as child_data
from ( select parent_id,
child_id
from
(
select parent_id,
child_id,
row_number() over (partition by parent_id order by child_id) rn
from relations ) oneChild
where rn = 1 ) rel
join items parent on rel.parent_id = parent.item_id
join items child on rel.child_id = child.item_id

How to get all child of a given id in SQL Server query

I have two tables in SQL Server database:
category(
itemid,
parentid
)
ArticleAssignedCategories(
categid,
artid
)
categid is a foreign key of itemid
I want to get count of artids and child of that for given itemid (child means categories with parentid of given itemid.)
For example; If given itemid = 1 and in table category have (3,1),(4,1)(5,3)
All of 3, 4, 5 are child of 1
Can anyone help me to write a good query?
Recursive queries can be done using CTE
with CTE(itemid, parentid)
as (
-- start with some category
select itemid, parentid
from category where itemid = <some_itemid>
union all
-- recursively add children
select c.itemid, c.parentid
from category c
join CTE on c.parentid = CTE.itemid
)
select count(*)
from ArticleAssignedCategories a
join CTE on CTE.itemid = a.categid
Here is the query. I hope this may help you
select b.artid,count(b.artid) from category a
inner join ArticleAssignedCategories b on a.itemid = b.artid
group by b.artid

T-SQL get root node in hierarchy

So I have two tables structured like so:
CREATE TABLE #nodes(node int NOT NULL);
ALTER TABLE #nodes ADD CONSTRAINT PK_nodes PRIMARY KEY CLUSTERED (node);
CREATE TABLE #arcs(child_node int NOT NULL, parent_node int NOT NULL);
ALTER TABLE #arcs ADD CONSTRAINT PK_arcs PRIMARY KEY CLUSTERED (child_node, parent_node);
INSERT INTO #nodes(node)
VALUES (1), (2), (3), (4), (5), (6), (7);
INSERT INTO #arcs(child_node, parent_node)
VALUES (2, 3), (3, 4), (2, 6), (6, 7);
If I have two nodes, lets say 1 and 2. I want a list of their root nodes. In this case it would be 1, 4, and 7. How can I write a query to get me that information ?
I took a stab at writing it but ran into the issue that I can't use a LEFT join in the recursive part of a CTE for some unknown reason. Here is the query that would work if I was allowed to do a LEFT JOIN.
WITH root_nodes
AS (
-- Grab all the leaf nodes I care about and their parent
SELECT n.node as child_node, a.parent_node
FROM #nodes n
LEFT JOIN #arcs a
ON n.node = a.child_node
WHERE n.node IN (1, 2)
UNION ALL
-- Grab all the parent nodes
SELECT rn.parent_node as child_node, a.parent_node
FROM root_nodes rn
LEFT JOIN #arcs a -- <-- LEFT JOINS are Illegal for some reason :(
ON rn.parent_node = a.child_node
WHERE rn.parent_node IS NOT NULL
)
SELECT DISTINCT rn.child_node as root_node
FROM root_nodes rn
WHERE rn.parent_node IS NULL
Is there a way I can restructure the query to get what I want ? I can't restructure the data and I would really prefer to stay away from temporary tables or having to do anything expensive.
Thanks,
Raul
How about moving the LEFT JOIN out of the CTE?
WITH root_nodes
AS (
-- Grab all the leaf nodes I care about
SELECT NULL as child_node, n.node as parent_node
FROM #nodes n
WHERE n.node IN (1, 2)
UNION ALL
-- Grab all the parent nodes
SELECT rn.parent_node as child_node, a.parent_node
FROM root_nodes rn
JOIN #arcs a
ON rn.parent_node = a.child_node
)
SELECT DISTINCT rn.parent_node AS root_node
FROM root_nodes rn
LEFT JOIN #arcs a
ON rn.parent_node = a.child_node
WHERE a.parent_node IS NULL
The result set is 1, 4, 7.

SQL: Selecting Where Latest Sub Child

I have a few model which looks a little something like this: Parent has a 1-2-M relationship with Child, and Child has a 1-2-M relationship with Sub-Child.
Parent
------
Parent_ID
Child
-----
Child_ID,
Parent_ID
Sub-Child
---------
Child_ID,
Version_Number (numeric),
Sent (date),
Void (date)
I want a query which returns a list of unique parent_id's where the latest version (judged by the version_number) of a related sub-child is 'sent' == null, but 'void' != null.
I've been chewing this over in my head and can't figure things out.
Any advice would be greatly appreciated.
Thanks,
Robert
It'll be something like:
;WITH CTE_LatestSubChildren AS
(
SELECT Parent_ID, Latest_Version_Number = max(sc.Version_Number)
FROM
Child c
JOIN [Sub-Child] sc on c.Child_ID = sc.Child_ID
GROUP BY c.Parent_ID
)
SELECT
ParentID
FROM
CTE_LatestSubChildren lsc
JOIN Child c
on lsc.Parent_ID = c.Parent_ID
JOIN [Sub-Child] sc
ON sc.Child_ID = c.Child_ID
AND sc.version_number = lsc.Latest_Version_Number
AND sc.Sent IS NULL
AND sc.Void IS NOT NULL
Note that this may require amendments as its not tested, and its not completely clear what should happen about multiple child records where the latest version is the same.
I'm not where I can test this, but it sounds like you'll need a subquery to pull the max version numbers of each child, then a self-join to get the rest of the sub-child information. Something like this is what I'm thinking:
SELECT DISTINCT
Parent_ID
FROM
Parent JOIN Child
ON Parent.Parent_ID = Child.Parent_ID
JOIN (
SELECT Child_ID, MAX(Version_Number)
FROM Sub-Child
GROUP BY Child_ID ) AS MaxSubchild
JOIN Sub-Child
ON Sub-Child.Child_ID = MaxSubchild.Child_ID AND
Sub-Child.Version_Number = MaxSubchild.Version_Number
WHERE
SUb-Child.Sent IS NULL AND
Sub-Child.Void IS NOT NULL;
Start by getting the max version by child_id:
select child_id, max(version_number) as version_number
from subchild
group by child_id
Then join it as a subquery, with subchild and child, and apply your where condition.
Or, without subqueries, try,
SELECT DISTINCT p.parent_id
FROM sub_children sc
LEFT JOIN children c ON sc.parent_id = c.child_id
LEFT JOIN parents p ON c.parent_id = p.parent_id
WHERE sc.sent == null, but sc.void != null
You can also use Rank():
SELECT DISTINCT TOP 100 PERCENT ST.Parent_ID
FROM
(
SELECT RANK() OVER (PARTITION BY C.Parent_ID ORDER BY SC.Version_Number DESC) AS [RANK],
C.Parent_ID, SC.Sent, SC.Void
FROM Child C
INNER JOIN Sub_Child SC ON C.Child_ID = SC.Child_ID
) ST
WHERE [RANK] = 1
AND [Sent] IS NULL AND [Void] IS NOT NULL