Postgres recursive CTE with Group? - sql

I tried to find out the masterIds as Array. please find the below
create temp table location as
select 1 as id,'World' as name,null as main_id
union all
select 2,'Asia',1
union all
select 3,'Africa',1
union all
select 4,'India',2
union all
select 5,'Sri Lanka',2
union all
select 6,'Mumbai',4
union all
select 7,'South Africa',3
Table like
Id
Name
Main Id
1
World
null
2
Asia
1
3
Africa
1
4
India
2
5
Sri Lanka
2
6
Mumbai
4
7
South Africa
3
8
Cape Town
7
And My expected result mentioned below:
Id
Name
Main Id
MasterIds Arrayint
1
World
null
1
2
Asia
1
1,2
3
Africa
1
1,3
4
India
2
1,2,4
5
Sri Lanka
2
1,2,5
6
Mumbai
4
1,2,4,6
7
South Africa
3
1,3,7
8
Cape Town
7
1,3,7,8
And My code is handling only one row but i need this for entire table. please find my code below:
with recursive
cte as (
select * from location where id=4
union all
select g.* from cte join location g on g.id = cte.main_id
)
select array_agg(id) from cte l

You seem to want an array of all ancestors (including id) for each id. Here is one attempt:
with recursive cte as (
select id, name, main_id from location
union all
select cte.id, g.name, g.main_id
from cte join location g on g.id = cte.main_id
)
select id, array[id] || array_agg(main_id)
from cte l
where main_id is not null
group by id
See Fiddle
For each iteration in the recursive cte, we keep the id that we started from and traverse the tree upwards (i.e. add next ancestor to the set of ancestors). At the end we create one group for each id and aggregate all ancestors into the array.
Worth noting is that the set of ancestors is unordered, so you may end up with a "path" that is out of order. You can fix that by providing an order, here I use the level relative to the id:
with recursive
cte as (
select id, name, main_id, 0 as lvl from location
union all
select cte.id, g.name, g.main_id, lvl+1 from cte join location g on g.id = cte.main_id
)
select id, array[id] || array_agg(main_id order by lvl)
from cte l
where main_id is not null
group by id

Related

Get nested children from parent's ID with SQL Server

My table is as follows:
ID Name Parent ID
1 Joe -
2 James -
3 Mike 1
4 Lewis 3
5 Anne 2
6 Lucy 4
I'd like to get the ID of the parent and all its children. For example, if I do:
Select Name from Table where ID = 1 (and nested children)
The desired output would be:
Joe
Mike
Lewis
Lucy
You can use a recursive CTE:
with cte as (
select name, id
from t
where id = 1
union all
select t.name, t.id
from cte join
t
on t.parent_id = cte.id
)
select name
from cte;
WITH CTE_Table AS (
SELECT ID, Name, Parent
FROM MyTable
UNION ALL
SELECT MyTable.ID, MyTable.Name, MyTable.Parent
FROM CTE_Table INNER JOIN
MyTable ON CTE_Table.ID = MyTable.Parent
)
SELECT Name
FROM CTE_Table
WHERE (Parent = 1)

Using the results of a STRING_AGG function with the IN operator in a WHERE clause

I have column children_ids which contain PKs from a STRING_AGG function. I am trying to use this column within a WHERE clause with the IN operator to return the total_pets but it doesn't work. If I copy and paste the values directly into the IN operator the query returns the correct info, otherwise no reuslts are found.
Here are my data sets:
Parents
=======
id parent_name
----------------
1 Bob and Mary
2 Mick and Jo
Children
========
id child_name parent_id
-------------------------
1 Eddie 1
2 Frankie 1
3 Robbie 1
4 Duncan 2
5 Rick 2
6 Jen 2
Childrens Pets
===============
id pet_name child_id
-------------------------
1 Puppy 1
2 Piggy 2
3 Monkey 3
4 Lamb 4
5 Tiger 5
6 Bear 6
7 Zebra 6
Expected Output
===============
parent_id children_ids total_pets
-----------------------------------
1 1,2,3 3
2 4,5,6 4
Current [undesired] Output
==========================
parent_id children_ids total_pets
-----------------------------------
1 1,2,3 0
2 4,5,6 0
here is the standard sql to test for yourself
# setup data with standardSQL
WITH `parents` AS (
SELECT 1 id, 'Bob and Mary' parent_names UNION ALL
SELECT 2, 'Mick and Jo'
),
`children` AS (
SELECT 1 id, 'Eddie' child_name, 1 parent_id UNION ALL
SELECT 2, 'Frankie', 1 UNION ALL
SELECT 3, 'Robbie', 1 UNION ALL
SELECT 4, 'Duncan', 2 UNION ALL
SELECT 5, 'Rick', 2 UNION ALL
SELECT 6, 'Jen', 2
),
`childrens_pets` AS (
SELECT 1 id, 'Puppy' pet_name, 1 child_id UNION ALL
SELECT 2, 'Piggy', 2 UNION ALL
SELECT 3, 'Monkey', 3 UNION ALL
SELECT 4, 'Lamb', 4 UNION ALL
SELECT 5, 'Tiger', 5 UNION ALL
SELECT 6, 'Bear', 6 UNION ALL
SELECT 7, 'Zebra', 6
)
And the query:
#standardSQL
select
parent_id
, children_ids
-- !!! This keeps returning 0 instead of the total pets for each parent based on their children
, (
select count(p1.id)
from childrens_pets p1
where cast(p1.child_id as string) in (children_ids)
) as total_pets
from
(
SELECT
p.id as parent_id
, (
select string_agg(cast(c1.id as string))
from children as c1
where c1.parent_id = p.id
) as children_ids
FROM parents as p
join children as c
on p.id = c.parent_id
join childrens_pets as cp
on cp.child_id = c.id
)
GROUP BY
parent_id
, children_ids
... but is there a way to do it using the IN operator as my query ...
Just fix one line and it will work for you!
Replace
WHERE CAST(p1.child_id AS STRING) IN (children_ids)
with
WHERE CAST(p1.child_id AS STRING) IN (SELECT * FROM UNNEST(SPLIT(children_ids)))
Huh? This would seem to do what you want:
SELECT p.id as parent_id,
string_agg(distinct cast(c.id as string)) as children_ids
count(distinct cp.id) as num_pets
FROM parents p JOIN
children c
ON p.id = c.parent_id JOIN
children_pets cp
ON cp.child_id = c.id
GROUP BY parent_id;

Recursive calculation to form a tree using sql

I am working on a simple problem and wanted to solve it using SQL. I am having 3 tables Category, Item & a relational table CategoryItem. I need to return count of items per category but the twist is Categories are arranged in Parent-Child relationships and the count of items in child categories should be added to the count in its parent Category. Please consider the sample data below and the expected resultset using SQL.
Id Name ParentCategoryId
1 Category1 Null
2 Category1.1 1
3 Category2.1 2
4 Category1.2 1
5 Category3.1 3
ID CateoryId ItemId
1 5 1
2 4 2
3 5 2
4 3 1
5 2 3
6 1 1
7 3 2
Result:
CategoryNAme Count
Category1 7
Category1.1 5
Category2.1 4
Category1.2 1
Category3.1 2
I can do it in my business layer but performance its not optimal because of size of data. I am hoping if I can do it in data layer, I would be able to improve performance greatly.
Thanks in Advance for your reply
your tables and sample data
create table #Category(Id int identity(1,1),Name Varchar(255),parentId int)
INSERT INTO #Category(Name,parentId) values
('Category1',null),('Category1.1',1),('Category2.1',2),
('Category1.2',1),('Category3.1',3)
create table #CategoryItem(Id int identity(1,1),categoryId int,itemId int)
INSERT INTO #CategoryItem(categoryId,itemId) values
(5,1),(4,2),(5,2),(3,1),(2,3),(1,1),(3,2)
create table #Item(Id int identity(1,1),Name varchar(255))
INSERT INTO #Item(Name) values('item1'),('item2'),('item3')
Checking for all childs of parent by Recursive Commom Table Expressions
;WITH CategorySearch(ID, parentId) AS
(
SELECT ID, ID AS ParentId FROM #Category
UNION ALL
SELECT CT.Id,CS.parentId FROM #Category CT
INNER JOIN CategorySearch CS ON CT.ParentId = CS.ID
)
select * from CategorySearch order by 1,2
Output: All child records against parent
ID parentId
1 1
2 1
3 1
4 1
5 1
2 2
3 2
5 2
3 3
5 3
4 4
5 5
Final query for your result, count all items for category and its children categories.
;WITH CategorySearch(ID, parentId) AS
(
SELECT ID, ID AS ParentId FROM #Category
UNION ALL
SELECT CT.Id,CS.parentId FROM #Category CT
INNER JOIN CategorySearch CS ON CT.ParentId = CS.ID
)
SELECT CA.Name AS CategoryName,count(itemId) CountItem
FROM #Category CA
INNER JOIN CategorySearch CS ON CS.ParentId = CA.id
INNER JOIN #CategoryItem MI ON MI.CategoryId =CS.ID
GROUP BY CA.Name
Output:
CategoryName CountItem
Category1 7
Category1.1 5
Category1.2 1
Category2.1 4
Category3.1 2
with help of CTE (common table expression) with recursion, you could achieve what you are looking for.
see Microsoft help for more details retated to recursive CTEs: CTE MS SQL 2008 +
hereby you could find complete example, with your sample data:
-- tables definition
SELECT 1 as id, 'cat1' as [name],NULL as id_parent
into cat
union
select 2, 'cat1.1', 1
union
select 3, 'cat2.1', 2
union
select 4, 'cat1.2', 1
union
select 5, 'cat3.1', 3
select 1 as id , 5 as id_cat, 1 as id_item
iNTO item
UNION
select 2, 4, 2
UNION
select 3, 5, 2
UNION
select 4, 3, 1
UNION
select 5, 2, 3
UNION
select 6, 1, 1
UNION
select 7, 3, 2
-- CTE to get desired result
with childs
as
(
select c.id, c.id_parent
from cat c
UNION ALL
select s.id, p.id_parent
from cat s JOIN childs p
ON (s.id_parent=p.id)
),
category_count
AS
(
SELECT c.id, c.name, count(i.id) as items
from cat c left outer join item i
on (c.id=i.id_cat)
GROUP BY c.id,c.name
),
pairs
AS
(
SELECT id, ISNULL(id_parent,id) as id_parent
FROM childs
)
select p.id_parent, n.name, sum(items)
from pairs p JOIN category_count cc
ON (p.id=cc.id)
join cat n ON (p.id_parent=n.id)
GROUP by p.id_parent ,n.name
ORDER by 1;

SQL Database Parent/Child recursion

Here is my table:
parent_id | child_id
--------------
1 | 2
1 | 3
1 | 4
2 | 5
2 | 6
5 | 8
8 | 9
9 | 5
I need to get all of the items under parent 2. I've found a few things similar to this, but but couldn't figure out how to make it work for my case. I keep getting maximum recursion limit reached. Here's what I have:
WITH CTE AS
(
SELECT gt.[child_id]
FROM [CHSPortal].[dbo].[company_adgroupstoadgroups] gt
WHERE gt.parent_id='2'
UNION ALL
SELECT g.[child_id]
FROM [CHSPortal].[dbo].[company_adgroupstoadgroups] g
INNER JOIN CTE g2 on g.parent_id=g2.child_id
)
select distinct child_id from CTE
The desired result is going to be: 2,3,4,5,6,8,9.
What modification do I need to make to get a list of all the items under child 2. I would also prefer 2 (the parent node) to be in the list. Any help would be appreciated.
First of all, there is a loop in your example (5|8, 8|9, 9|5), that is why you reach the maximum recursion limit.
Regarding the filtering question,below you can find an example for filtering by root node:
;WITH MTree (parent_id, child_id, LEVEL) AS (
SELECT t.parent_id , t.child_id, 0 AS LEVEL
FROM table_1 t
WHERE child_id = 2 --here you can filter the root node
UNION ALL
SELECT m.parent_id , m.child_id, LEVEL + 1
FROM Table_1 m
INNER JOIN MTree t ON t.child_id = m.parent_id
)
SELECT * FROM Mtree;
Not sure what's wrong with your query, aside from not relating to the sample data you provided, but this works just fine:
;WITH src AS (SELECT 1 AS parent_id, 2 AS child_id
UNION SELECT 1, 3
UNION SELECT 1, 4
UNION SELECT 2, 5
UNION SELECT 2, 6
UNION SELECT 5, 8
UNION SELECT 8, 9
UNION SELECT 9, 5)
,cte AS (SELECT *
FROM src
WHERE child_id = 2
UNION ALL
SELECT a.*
FROM src a
JOIN cte b
ON a.parent_id = b.child_id
)
SELECT TOP 100 *
FROM cte
--Limited to top 100 because of infinite recursion problem with sample data.

Find Missing Pairs in SQL

Assume there's a relational database with 3 tables:
Courses {name, id},
Students {name, id},
Student_Course {student_id, course_id}
I want to write an SQL that gives me the student-course pairs that do NOT exist. If that is not feasible, at least it'd be good to know if there are missing pairs or not.
Also, since this is a small part of a larger problem I'd like to automate, seeing many different ways of doing it would be useful.
1st find all pairs and then remove pairs present (either by left join/not null or not exists)
select s.id as student_id, c.id as course_id
from Courses as c
cross join Students as s
left join Student_Course as sc on sc.student_id = s.id and sc.course_id = c.id
where sc.course_id is null -- any sc field defined as "not null"
with Courses as(
select 1 as id,'Math' as name union all
select 2 as id,'English' as name union all
select 3 as id,'Physics' as name union all
select 4 as id,'Chemistry' as name),
Students as(
select 1 as id,'John' as name union all
select 2 as id,'Joseph' as name union all
select 3 as id,'George' as name union all
select 4 as id,'Michael' as name
),
studcrse as(
select 1 as studid, 1 as crseid union all
select 1 as studid, 2 as crseid union all
select 1 as studid, 3 as crseid union all
select 2 as studid, 3 as crseid union all
select 2 as studid, 4 as crseid union all
select 3 as studid, 1 as crseid union all
select 3 as studid, 2 as crseid union all
select 3 as studid, 4 as crseid union all
select 3 as studid, 3 as crseid union all
select 4 as studid, 4 as crseid )
SELECT A.ID AS studentId,a.name as studentname,b.id as crseid,b.name as crsename
from Students as a
cross join
Courses as b
where not exists
(
select 1 from studcrse as c
where c.studid=a.id
and c.crseid=b.id)