Get all parent rows and each row followed by their child's rows - sql

I've two tables one of them called
Main-Level
another one called
Sub-level
Sub-level has a foreign key from the Main level (the relation between them Main-Level has one or Many Sub-levels )
what I want is to create a query to show the Main-level row followed by all Sub-level rows such as below screen-shot either by native SQL query or LINQ.
Update:
I used below but the problem is it the result such as Full OUTer JOIN !
select * from Sublevel
right join Mainlevel
on Sublevel.mainlevelID=Mainlevel.id
order by coalesce(Sublevel.mainlevelID, Mainlevel.id),
(case when Sublevel.mainlevelID is null then 1 else 0 end),Mainlevel.id;
Update 2:
Also, I tried below query but with no luck :
SELECT
s.name,
s.Id,
CASE WHEN s.is_child = 1 THEN s.parentID END AS parent_id,
m.name
FROM
Mainlevel m
INNER JOIN (
SELECT id, name, parentID, 1 AS is_child
FROM Sublevel
UNION ALL
SELECT id, name,Null, 0 AS is_child
FROM Mainlevel
) s on m.id = s.mainlevelID
ORDER BY m.id,is_child, s.mainlevelID
My problem in simple language is How to make the child rows appeared below parent row

The overall plan is to have parent join (parent + child) order by (parent ID, child ID)
SELECT
c.level_id,
c.level_name,
c.level_code,
CASE WHEN c.is_child = 1 THEN c.parent_id END AS parent_id,
FROM
mainLevel p
INNER JOIN (
SELECT level_id, level_name, level_code, parent_id, 1 AS is_child
FROM subLevel
UNION ALL
SELECT level_id, level_name, level_code, level_id, 0 AS is_child
FROM mainLevel
) c on p.level_id = c.parent_id
ORDER BY p.level_id, is_child, c.level_id
Additional version to adopt to the newly clarified column availability
SELECT
w.name,
w.id,
CASE WHEN w.is_child = 1 THEN w.mid END AS parent_id
FROM
Mainlevel m
INNER JOIN (
SELECT id, name, parentID AS mid, 1 AS is_child
FROM Sublevel
UNION ALL
SELECT id, name, id AS mid, 0 AS is_child
FROM Mainlevel
) w on m.id = w.mid
ORDER BY m.id, is_child, w.id

You can use order by:
order by coalesce(parentid, id),
(case when parentid is null then 1 else 0 end),
id

Related

PostgreSQL recursive query for calculation parent value

Here is entry's hierarchy.
_________Milky Way (30)________
/ | \
Alpha(10) Beta(20) Delta(null)
/ \ |
Mars(7) Jupiter(3) Delta-child(44)
Parents value is a sum of it's children's values.
Ex.
Alpha = Mars + Jupiter = 7 + 3 = 10
Milky Way = Alpha + Beta + Delta = 10 + 20 + null = 30
The task: recalculate parents up to the root in case any child is updated. Let's even simplify the task: select all entries up to the root with recalculated values.
Imagine that Mars is updated. Now Mars value is 2.
_________Milky Way (?)________
/ | \
Alpha(?) Beta(20) Delta(null)
/ \ |
Mars(2) Jupiter(3) Delta-child(44)
It means that all parents should be updated:
Alpha = Mars + Jupiter = 2 + 3 = 5
Milky Way = Alpha + Beta + Delta = 5 + 20 + null = 25.
Note: Delta -> Delta-child coupling is broken and it's fine. It can happen lets leave it out of scope here. 've added this sample just to be sure that it won't be counted during calculation as hierarchy can be huge enough and tehre is no task to recalculate all children leaf, just parents up to the root.
As a result of some "select .. from hierarchy.."
I'd like to receive recalculated parents' values.
Ex.
id
name
value
1
Milky Way
25
2
Alpha
5
Code samples with already updated Mars (sqlfiddle links are below):
Schema
CREATE TABLE hierarchy
(
id int4,
parent_id int4,
name varchar(255),
value int4
);
Values
insert into hierarchy
values
(1, null, 'Milky Way', 30),
(2, 1, 'Alpha', 10),
(3, 1, 'Beta', 20),
(4, 1, 'Delta', null),
(5, 2, 'Mars', 2),
(6, 2, 'Jupiter', 3),
(7, 4, 'Delta-child', 44);
What I have tried:
I was able to list all leafs which should be used in calculation
sqlfiddle 1
WITH RECURSIVE cte AS (
SELECT h1.id, h1.parent_id, h1.name , h1.value from hierarchy h1
where h1.id = 5
UNION
SELECT h2.id, h2.parent_id, h2.name , h2.value from hierarchy h2
JOIN cte cte ON (cte.parent_id = h2.parent_id or cte.parent_id = h2.id )
where cte.id != h2.id
) select * from cte
order by id
When I tried to sum values, query goes in infinite loop for some reason
sqlfiddle 2
WITH RECURSIVE cte AS (
SELECT h1.id, h1.parent_id, h1.name , h1.value from hierarchy h1
where h1.id = 5
UNION
SELECT h2.id, h2.parent_id, h2.name , (h2.value + cte.value) as value from hierarchy h2
JOIN cte cte ON (cte.parent_id = h2.parent_id or cte.parent_id = h2.id )
where cte.id != h2.id
) select * from cte
order by id
There is one more query that I have tried, unfortunately it doesn't count sibling of parents.
sqlfiddle 3
WITH RECURSIVE cte AS (
SELECT h1.id, h1.parent_id, h1.name , h1.value from hierarchy h1
where h1.parent_id = (select parent_id from hierarchy where id = 5)
UNION
SELECT h2.id, h2.parent_id, h2.name , cte.value as value from hierarchy h2
JOIN cte cte ON (cte.parent_id = h2.parent_id or cte.parent_id = h2.id )
where cte.id != h2.id
) select id, parent_id, name, sum(value) from cte
group by id, parent_id, name
order by id
I'd appreciate any assistance. :-)
It took me a while, but good work on your trials.
What i did is that I split the problem in parts.
go down the hierarchy with the recursive query
with recursive base_qry as (
select id,
parent_id,
value,
ARRAY[id] as id_array
from hierarchy
union all
select h.id,
h.parent_id,
h.value,
id_array || h.id as id_array
from hierarchy h join base_qry b on h.parent_id = b.id and h.value is not null
)
Understand the nodes affected filtering the last id of the array containing all the nodes with id_array[array_length(id_array,1)] = 5
nodes_affected as (
select * from base_qry
where id_array[array_length(id_array,1)] = 5
order by array_length(id_array,1) desc
LIMIT 1)
Find all the tree breanches than contribute to changed nodes (check here the filter for node id=5)
all_combinations as (
select b.id, b.parent_id, b.value, b.id_array from
base_qry b join nodes_affected n
on ARRAY[n.id_array] && ARRAY[b.id_array]
where (b.id_array[array_length(b.id_array,1)] = 5
or b.id_array #> ARRAY[5] = false)
)
aggregating up
select id_array[1]::int id, sum(value)
from all_combinations where id not in (select parent_id from all_combinations where parent_id is not null)
group by id_array[1]::int
order by 1
Whole query
with recursive base_qry as (
select id,
parent_id,
value,
ARRAY[id] as id_array
from hierarchy
union all
select h.id,
h.parent_id,
h.value,
id_array || h.id as id_array
from hierarchy h join base_qry b on h.parent_id = b.id and h.value is not null
),
nodes_affected as (
select * from base_qry
where id_array[array_length(id_array,1)] = 5
order by array_length(id_array,1) desc
LIMIT 1),
all_combinations as (
select b.id, b.parent_id, b.value, b.id_array from
base_qry b join nodes_affected n
on ARRAY[n.id_array] && ARRAY[b.id_array]
where (b.id_array[array_length(b.id_array,1)] = 5
or b.id_array #> ARRAY[5] = false)
)
select id_array[1]::int id, sum(value)
from all_combinations where id not in (select parent_id from all_combinations where parent_id is not null)
group by id_array[1]::int
order by 1
;
Start from the leafs and traverse all the nodes the leaf contributes to up the hierachy. Then just sum all contributions to a node.
WITH RECURSIVE cte AS (
SELECT id, parent_id, value
FROM hierarchy h1
WHERE not exists (select 1 from hierarchy h2 where h2.parent_id = h1.id)
UNION ALL
SELECT h.id, h.parent_id, case when h.value is null then 0 else cte.value end
FROM hierarchy h
JOIN cte ON (cte.parent_id = h.id)
)
select h.id, h.name, v.value
from (
select id, sum(value) as value
from cte
group by id
) v
join hierarchy h on h.id = v.id
order by h.id;
db<>fiddle
You can use a recursive CTE to get all nodes and their paths, and then for every non-leaf node in hierarchy, you can join the CTE back to hierarchy on the condition that the current hierarchy row id exists in the path and sum the values:
with recursive cte(id, p, n, v, t) as (
select h.*, concat('[', h.id::text) from hierarchy h where h.parent_id is null
union all
select h.id, h.parent_id, h.name, h.value, concat(c.t, ', ', h.id)
from cte c join hierarchy h on c.id = h.parent_id
)
select h.id, sum(c.v)
from hierarchy h join cte c on c.id != h.id and exists (select 1 from jsonb_array_elements(concat(c.t, ']')::jsonb) v where v.value::text = h.id::text)
group by h.id order by h.id
Output:
id sum
1 79
2 5
4 44

SQL Get lowest level child and root node

I have database schema: [Id], [ParrentId], [some more tables]
I have hierarchy like:
1. a
2. aa
3. aaa_1
3. aaa_2
1. b
2. bb
1. c
2. cc
3. ccc_1
4. cccc
3. ccc_2
I want a (select * where X) => [X, lowest leve child] like:
[a, aaa_1] [a, aaa_2]; [cc, cccc] etc.
I can get lowest child with
SELECT t1.name FROM
category AS t1 LEFT JOIN category as t2
ON t1.category_id = t2.parent
WHERE t2.category_id IS NULL;
but I don't know how to join it with root node.
Given:
The DBMS is SQL Server;
The highest level nodes of the tree have parent = NULL;
You want all the lowest leaves for all levels of the trees, not just the roots;
You want to have all the nodes at a lowest level, not just one;
This query would do it:
WITH r ( category_id, name, root, depth )
-- finds the root relationship
AS (
SELECT category_id, name, category_id, 0
FROM category
-- WHERE parent IS NULL -- this would only look at root nodes
UNION ALL
SELECT c.category_id, c.name, r.root, r.depth + 1
FROM r
JOIN category c
ON c.parent = r.category_id
), s ( category_id, name, root, window_id )
-- finds the lowest leaves
AS (
SELECT category_id, name, root, RANK() OVER(partition by root order by depth DESC)
FROM r
)
SELECT c.name AS NodeName, s.Name AS DeepLeafName
FROM category c
JOIN s
ON c.category_id = s.root
WHERE s.window_id = 1;
Here is the result set:
With SQL Server, you can try this :
With CTE as
(
Select ID as Child, lev = 1
from category
where ID = X
UNION ALL
Select category.ID, CTE.lev + 1
from category
inner join CTE ON category.ParentID = CTE.Child
)
select CTE_1.Child, CTE_2.Child
from CTE as CTE_1
inner join CTE as CTE_2
where CTE_1.lev = 1 AND CTE_2.lev = (select MAX(CTE.lev) from CTE)

Complex sorting based on next and previous records in SQL

This is a follow-up question on Sorting based on next and previous records in SQL
But now it gets a little more complex, for example:
If any letter of 1 matches any letter of 2, I want to change the ordering, so that the letter matches with the following record.
If no matches are found the normal ordering by letter should be done.
The IDs are potentially not succeeding and the records are not persé in the correct order. [SQLFiddle Demo]
[Create script and SQL Fiddle demo]
create table Parent (
id [bigint] IDENTITY(1,2),
number bigint NOT NULL,
PRIMARY KEY (id)
)
GO
create table Child (
id [bigint] IDENTITY(1,2),
parentId BIGINT,
letter VARCHAR(1) NOT NULL,
PRIMARY KEY (id),
UNIQUE (parentId, Letter),
FOREIGN KEY (parentId) REFERENCES Parent(id)
)
GO
DECLARE #ParentIdentity BIGINT
INSERT Parent (number) VALUES (2)
SET #ParentIdentity = ##IDENTITY
INSERT Child (parentId, letter) VALUES (#ParentIdentity, 'C')
INSERT Child (parentId, letter) VALUES (#ParentIdentity, 'B')
INSERT Parent (number) VALUES (3)
SET #ParentIdentity = ##IDENTITY
INSERT Child (parentId, letter) VALUES (#ParentIdentity, 'D')
INSERT Child (parentId, letter) VALUES (#ParentIdentity, 'B')
INSERT Parent (number) VALUES (1)
SET #ParentIdentity = ##IDENTITY
INSERT Child (parentId, letter) VALUES (#ParentIdentity, 'C')
INSERT Child (parentId, letter) VALUES (#ParentIdentity, 'A')
GO
Current query
Currently I am sorting with this query:
;WITH CTE AS
(
SELECT id,ParentID,letter,
ROW_NUMBER() OVER (ORDER BY ID) seq_id,
ROW_NUMBER() OVER (PARTITION BY parentId ORDER BY ID) first_element,
ROW_NUMBER() OVER (PARTITION BY parentId ORDER BY ID DESC) Last_element
FROM Child
), CTE2 AS
(
SELECT c1.id, c1.parentid, c1.letter, c2.parentid as c2parentid
FROM CTE c1
INNER JOIN CTE c2
ON c1.last_element = 1
AND c2.first_element = 1
AND c1.seq_id + 1 = c2.seq_id
), CTE3 AS
(
SELECT C.parentid, C.id
FROM CTE2
INNER JOIN child C ON CTE2.c2parentid = C.parentid
AND C.letter = CTE2.letter
)
SELECT P.number, C.letter
FROM Child C
JOIN Parent P ON C.parentId = P.id
LEFT JOIN CTE3 ON CTE3.id = C.id
ORDER BY P.number, ISNULL(CTE3.id,0) DESC, C.letter
Current result set
number letter
-------------------- ------
1 A
1 C
2 B
2 C
3 B
3 D
Expected result set
To clarify what I actually want to do, here is the expected result set:
number letter
-------------------- ------
1 A
1 C
2 C
2 B
3 B
3 D
Other requirements and question
It has to work in SQL Server 2005.
There is a scenario where 3 letters per number are used, I am happy if it just uses the best match.
Can anyone point me in the right direction on how to deal with this scenario?
If I understand your requirement right, You have some parts of parentId and you want each part to start with the letters those are in previous part And end with letters those are in next part, If yes try this:
;WITH t AS (
SELECT
c.id,
c.parentId,
c.letter,
dt.parentSeq
FROM
Child c
JOIN (
SELECT
ci.parentId, ROW_NUMBER() OVER (ORDER BY p.number) parentSeq
FROM
Child ci
JOIN
Parent p ON ci.parentId = p.id
GROUP BY
ci.parentId, p.number) dt ON c.parentId = dt.parentId
)
SELECT
p.number,
t.letter
FROM
t
JOIN
Parent p ON t.parentId = p.id
ORDER BY
p.number,
CASE WHEN t.letter IN (SELECT ti.letter FROM t ti WHERE ti.parentSeq = t.parentSeq - 1) THEN 0
WHEN t.letter IN (SELECT ti.letter FROM t ti WHERE ti.parentSeq = t.parentSeq + 1) THEN 2
ELSE 1 END,
t.letter
I am not sure if this will work for some complex real data, but you can check:
;WITH cte AS(SELECT ci.id, ci.parentId, ci.letter, p.number,
DENSE_RANK() OVER (ORDER BY p.number) rn
FROM Child ci
JOIN Parent p ON ci.parentId = p.id)
SELECT t1.number, t1.letter
FROM cte t1
LEFT JOIN cte t2 ON t2.rn = t1.rn - 1 AND t1.letter = t2.letter
LEFT JOIN cte t3 ON t1.rn = t3.rn - 1 AND t1.letter = t3.letter
ORDER BY t1.number, t1.letter + t2.letter DESC, t1.letter + t3.letter, t1.letter
If this doesn't work you can switch to #shA.t`s clever ordering:
;WITH cte AS(SELECT ci.id, ci.parentId, ci.letter, p.number,
DENSE_RANK() OVER (ORDER BY p.number) rn
FROM Child ci
JOIN Parent p ON ci.parentId = p.id)
SELECT number, letter
FROM cte
ORDER BY
number,
CASE WHEN letter IN (SELECT ti.letter FROM cte ti WHERE ti.rn = cte.rn - 1) THEN 0
WHEN letter IN (SELECT ti.letter FROM cte ti WHERE ti.rn = cte.rn + 1) THEN 2
ELSE 1 END,
letter
I use Row_Number function for this problem I hope it will help you .
SELECT Number, Letter
FROM (
SELECT number, letter, ROW_NUMBER()over(Partition by letter,Number order by Number) as R
FROM Child C
JOIN Parent P
ON P.id = C.parentId) x
ORDER BY number, R desc

Error using CTE in SQLITE3

I use these codes to get level(depth) of my parent/child categories table. and the isLeaf attribute:
with cteCat as (
select
id, parent,
[cteLevel] = 1
from Categories
where 1=1 and parent=0
union all
select
c.id, c.parent,
[cteLevel] = cc.cteLevel+1
from Categories c
join cteCat cc
on c.parent = cc.id
where 1=1
and c.parent <> 0
)
select
*
, CASE WHEN EXISTS (SELECT * FROM Categories c2 WHERE c2.parent = c1.id) THEN 0 ELSE 1 END AS IsLeaf
, (SELECT COUNT(*) FROM Categories c2 WHERE c2.parent = c1.id) AS Leafs
from cteCat c1
order by c1.id
result is:
id parent cteLevel IsLeaf Leafs
1 0 1 0 2
....
it's OK in SQLSERVER. but when i execute at sqlite, I get Error:
Error while executing SQL query on database 'ado':
no such column: cc.cteLevel
Any help? thanks.
SQLite does not support variable assignments like this.
However, you should be able to do the same with standard SQL:
WITH cteCat AS (
SELECT id,
parent,
1 AS cteLevel
FROM ...
WHERE ...
UNION ALL
SELECT c.id,
c.parent,
cc.cteLevel + 1
FROM ...
WHERE ...
)
SELECT ...

Finding a Top Level Parent in SQL

I have got two tables as following
Table Person
Id Name
1 A
2 B
3 C
4 D
5 E
Table RelationHierarchy
ParentId ChildId
2 1
3 2
4 3
This will form a tree like structure
D
|
C
|
B
|
A
ParentId and ChildId are foreign keys of Id column of Person Table
I need to write SQL that Can fetch me Top Level Parent i-e Root. Can anyone suggest any SQL that can help me accomplish this
You can use recursive CTE to achieve that:
DECLARE #childID INT
SET #childID = 1 --chield to search
;WITH RCTE AS
(
SELECT *, 1 AS Lvl FROM RelationHierarchy
WHERE ChildID = #childID
UNION ALL
SELECT rh.*, Lvl+1 AS Lvl FROM dbo.RelationHierarchy rh
INNER JOIN RCTE rc ON rh.CHildId = rc.ParentId
)
SELECT TOP 1 id, Name
FROM RCTE r
inner JOIN dbo.Person p ON p.id = r.ParentId
ORDER BY lvl DESC
SQLFiddle DEMO
EDIT - for updated request for top level parents for all children:
;WITH RCTE AS
(
SELECT ParentId, ChildId, 1 AS Lvl FROM RelationHierarchy
UNION ALL
SELECT rh.ParentId, rc.ChildId, Lvl+1 AS Lvl
FROM dbo.RelationHierarchy rh
INNER JOIN RCTE rc ON rh.ChildId = rc.ParentId
)
,CTE_RN AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY r.ChildID ORDER BY r.Lvl DESC) RN
FROM RCTE r
)
SELECT r.ChildId, pc.Name AS ChildName, r.ParentId, pp.Name AS ParentName
FROM CTE_RN r
INNER JOIN dbo.Person pp ON pp.id = r.ParentId
INNER JOIN dbo.Person pc ON pc.id = r.ChildId
WHERE RN =1
SQLFiddle DEMO
EDIT2 - to get all persons change JOINS a bit at the end:
SELECT pc.Id AS ChildID, pc.Name AS ChildName, r.ParentId, pp.Name AS ParentName
FROM dbo.Person pc
LEFT JOIN CTE_RN r ON pc.id = r.CHildId AND RN =1
LEFT JOIN dbo.Person pp ON pp.id = r.ParentId
SQLFiddle DEMo
I've used this pattern to associate items in a hierarchy with the item's root node.
Essentially recursing the hierarchies maintaining the values of the root node as additional columns appended to each row. Hope this helps.
with allRows as (
select ItemId, ItemName, ItemId [RootId],ItemName [RootName]
from parentChildTable
where ParentItemId is null
union all
select a1.ItemId,a1.ItemName,a2.[RootId],a2.[RootName]
from parentChildTable a1
join allRows a2 on a2.ItemId = a1.ParentItemId
)
select * from allRows
To find all top-level parents, use a query like:
select p.Name
from Person p
where not exists
(select null
from RelationHierarchy r
where r.ChildId = p.Id)
SQLFiddle here.
To find the top-level parent of a specific child, use:
with cte as
(select t.ParentId TopParent, t.ChildId
from RelationHierarchy t
left join RelationHierarchy p on p.ChildId = t.ParentId
where p.ChildId is null
union all
select t.TopParent TopParent, c.ChildId
from cte t
join RelationHierarchy c on t.ChildId = c.ParentId)
select p.name
from cte h
join Person p on h.TopParent = p.Id
where h.ChildId=3 /*or whichever child is required*/
SQLFiddle here.
Try this.
The recursive CTE will find the person and walk up the hierarchy until it finds no parent.
-- This CTE will find the ancestors along with a measure of how far up
-- the hierarchy each ancestor is from the selected person.
with ancestor as (
select ParentId as AncestorId, 0 as distance
from RelationHierarchy
where CHildId = ?
union all
select h.ParentId, a.distance + 1
from ancestor a inner join RelationHierarchy rh on a.AncestorId = rh.ChildId
)
select AncestorId
from ancestor
where distance = (select max(distance) from ancestor)
Something like this will work for above example:
SELECT ParentId FROM RelationHierarchy
WHERE ParentId NOT IN (SELECT CHildId FROM RelationHierarchy)
The only way you can do this in "standard" SQL is to assume a maximum depth for the tree, and then do joins for each level. The following gets the top level id:
select rh1.ChildId,
coalesce(rh4.parentid, rh3.parentid, rh2.parentid, rh1.parentid) as topLevel
from RelationshipHierarchy rh1 left outer join
RelationshipHierarchy rh2
on rh1.parentId = rh2.childId left outer join
RelationshipHierarchy rh3
on rh2.parentId = rh3.childId left outer join
RelationshipHierarchy rh4
on rh3.parentId = rh4.childId;
If you want the name, you can just join it in:
select rh1.ChildId,
coalesce(rh4.parentid, rh3.parentid, rh2.parentid, rh1.parentid) as topLevel,
p.name
from RelationshipHierarchy rh1 left outer join
RelationshipHierarchy rh2
on rh1.parentId = rh2.childId left outer join
RelationshipHierarchy rh3
on rh2.parentId = rh3.childId left outer join
RelationshipHierarchy rh4
on rh3.parentId = rh4.childId left outer join
Person p
on p.id = coalesce(rh4.parentid, rh3.parentid, rh2.parentid, rh1.parentid);
Get all top parents using path
path format: rootId/.../parentId/nodeId/
select t1.path from nodes t1 inner join nodes t2
on t1.path like t2.path+'%'
group by t1.path
having len(t1.path)-len(replace(t1.path, '/', ''))
=min(len(t2.path)-len(replace(t2.path, '/', '')))
Give this a go:
select id,name
from person p
where not exists
(
select 1
from relationhierarchy r
where r.childid= p.id
)
and exists
(
select 1
from relationhierarchy r
where r.parentid= p.id
)
It is not enough to just see if a child id exists as in your example E is present in the person table but not in the relationshiphierarchy table.
WITH CTE_MyTable AS (
SELECT Id, ParentId, NULL As RootParent, 1 As Lvl
FROM dbo.Ministry
UNION ALL
SELECT a.id, b.ParentId, a.ParentId As RootParent, Lvl + 1
FROM CTE_MyTableAS a INNER JOIN
dbo.MyTableAS b ON a.ParentId = b.Id
)
, CTE_Ministry_RN AS (
SELECT Id, RootParent, ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Lvl DESC) RN
FROM CTE_Ministry
)
SELECT Id, ISNULL(RootParent, Id) As RootParent
FROM CTE_Ministry_RN
WHERE RN = 1