The simplified version of the schema I'm working with looks like this:
Nodes:
Id Name ParentId
0 Parent NULL
1 Child1 0
2 Child2 0
Attributes:
Id NodeId Value
0 1 1
1 1 2
So a Node can have many Nodes as Children and a Node can also have many attributes.
I want to create a view which maps a Node's Id to the MAX Value of all its attributes and the attributes of all its children. So for the sample data above, the view should contain:
Id Value
0 2
1 2
2 NULL
I've tried various things with CTEs, but I can't get it to work. Any ideas?
Give this a try:
;with nodeAttributes as (
select n.Id, n.Name, n.ParentId, a.Value
from Nodes n
left outer join Attributes a on n.Id = a.NodeId
),
cte as
(
select n.Id, n.Name, n.Id RootId, n.ParentId, n.Value
from nodeAttributes n
union all
select n.Id, n.Name, c.RootId, n.ParentId, n.Value
from nodeAttributes n
join cte c on c.Id = n.ParentId
)
select n.Id, x.maxValue
from Nodes n
join (select RootId, max(Value) maxValue
from cte
group by RootId) x on x.RootId = n.Id
Related
I have the following recursive CTE that starts from a row (a tree node) and goes up through ancestors to the root
with ancestor_seq (
pre, parent
) as (
select pre, parent
from form_data_stage
where pre = 16
union all
select d.pre, d.parent
from ancestor_seq a
inner join form_data_stage d on d.pre = a.parent
)
select * from ancestor_seq;
It returns the expected result
pre parent
16 14
14 13
13 10
10 9
9 3
3 2
2 1
1 0
I want to get the 4th row only. If I try the same query but with there ROWNUM condition at the end -
with ancestor_seq (
pre, parent
) as (
select pre, parent
from form_data_stage
where pre = 16
union all
select d.pre, d.parent
from ancestor_seq a
inner join form_data_stage d on d.pre = a.parent
)
select * from ancestor_seq WHERE ROWNUM = 4;
I get an empty result.
I can add a level, e.g.
with ancestor_seq (
pre, parent, lvl
) as (
select pre, parent, 1 lvl
from form_data_stage
where pre = 16
union all
select d.pre, d.parent, a.lvl + 1
from ancestor_seq a
inner join form_data_stage d on d.pre = a.parent and a.lvl <= 4
)
select pre, parent from ancestor_seq where lvl = 4;
this works -
pre parent
10 9
but it is checking the level value twice. curious if there is a better way, and why rownum does not appear to work.
why rownum does not appear to work.
This is almost the same case as my answer to this quesiton.
It is not working because: for the first row ROWNUM is 1 and since your WHERE clause is ROWNUM=4 then this reduces to 1=4 and the row is discarded. The subsequent row will then be tested against a ROWNUM of 1 (since the previous row is no longer in the output and will not have a row number), which will again fail the test and be discarded. Repeat, ad nauseum and all rows fail the WHERE filter and are discarded.
If you want to get ROWNUM to work then you need to generate it in an inner query and filter in an outer query:
with ancestor_seq (
pre, parent
) as (
select pre, parent
from form_data_stage
where pre = 16
union all
select d.pre, d.parent
from ancestor_seq a
inner join form_data_stage d on d.pre = a.parent
)
SELECT *
FROM (
select a.*,
ROWNUM AS rn
from ancestor_seq a
)
WHERE rn = 4;
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
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
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)
ID parent_id name
---------------------
1 2 first
2 4 second
3 3 third
4 5 fourth
5 - fifth
Ancestors list of first should be (2, 4, 5)
with name_tree as (
select id, parent_id, name
from the_unknown_table
where id = 1 -- this is the starting point you want in your recursion
union all
select c.id, c.parent_id, c.name
from the_unknown_table c
join name_tree p on p.parent_id = c.id -- this is the recursion
)
select *
from name_tree
where id <> 1; -- exclude the starting point from the overall result
SQLFiddle: http://sqlfiddle.com/#!3/87d0c/1
You can use something like this:
with parents as
(
select ID, parent_ID
from t
where parent_ID is not null
union all
select p.ID, t.parent_ID
from parents p
inner join t on p.parent_ID = t.ID
and t.parent_ID is not null
and t.ID <> t.parent_ID
)
select *
, parents = '(' + stuff
(
(
select ', ' + cast(p.parent_ID as varchar(100))
from parents p
where t.ID = p.ID
for xml path('')
), 1, 2, ''
) + ')'
from t
order by ID
SQL Fiddle with demo.
This combines two very common T-SQL techniques - using a CTE to get a hierarchy and using FOR XML PATH to get a CSV list.