Recursive parent child problem in MariaDB - sql

I have run into this a couple of times where a client is able to import data into a catalog with parent child relationships and I run into problems with said relationships. I need to find a way to prevent the following:
Object 1 has a child of Object 2
Object 2 has a child of Object 3
Object 3 has a child of Object 1
This throws the server into an infinite recursive loop and ultimately brings it to its knees. I can't seem to wrap my head around a SQL query that I could use to detect such recursive madness. The problem is prevalent enough that I need to find some solution. I've tried queries using CTE, nested selects/sub-selects and just can't seem to write one that will solve this issue. Any help would be greatly appreciated.
with recursive parents as (
select
s.id,
s.parent_id,
1 as depth
from categories s
where s.id = <passed in id>
union all
select
t.id,
t.parent_id,
c.depth + 1 as depth
from categories t
inner join parents c
on t.id = c.parent_id
where t.id <> t.parent_id)
select distinct parent_id from parents where parent_id <> 0 order by depth desc
This is what I finally came up with to "detect" a cycle condition
with recursive find_cycle as (
select
categories_id,
parent_id,
0 depth
from
categories
where categories_id = <passed in id>
union all
select
f.categories_id,
c.parent_id,
f.depth + 1
from
categories c
inner join find_cycle f
ON f.parent_id = c.categories_id
where c.parent_id <> c.categories_id
and f.parent_id <> f.categories_id
)
select
f.parent_id as categories_id,
c.parent_id
from find_cycle f
inner join categories c
on f.parent_id = c.categories_id
where exists (
select
1
from find_cycle f
inner join categories c
on f.parent_id = c.categories_id
where f.parent_id = <passed in id>)
order by depth desc;
It will return rows with the offending path and no rows if no cycle detected. Thanks for all the tips folks.

Here is the MariaDB function I came up with that will return 0 if there is not a cycle and 1 if there is a cycle for the id passed in to the function.
create function `detect_cycle`(id int, max_depth int) RETURNS tinyint(1)
begin
declare cycle_exists int default 0;
select (case when count(*) = 1 then 0 else 1 end) into cycle_exists
from
(
with recursive find_cycle as (
select
categories_id,
parent_id,
0 depth
from
categories
where categories_id = id
union all
select
f.categories_id,
c.parent_id,
f.depth + 1
from
categories c
inner join find_cycle f
ON f.parent_id = c.categories_id
where
c.parent_id <> c.categories_id
and f.parent_id <> f.categories_id
and f.depth < max_depth
)
select
c.parent_id
from find_cycle f
inner join categories c
on f.parent_id = c.categories_id
order by depth desc
limit 1
) __temp
where parent_id = 0;
return cycle_exists;
end;
This can then be called by executing
select categories_id, detect_cycle(categories_id, 5) as cycle_exists
from categories
where categories_id = <whatever id you want to check for a cycle condition>;
Here is a stored procedure that will accomplish the same thing but is generic enough to handle any table, id column, parent column combination.
CREATE PROCEDURE `detect_cycle`(table_name varchar(64), id_column varchar(32), parent_id_column varchar(32), max_depth int)
BEGIN
declare id int default 0;
declare sql_query text default '';
declare where_clause text default '';
declare done bool default false;
declare id_cursor cursor for select root_id from __temp_ids;
declare continue handler for not found set done = true;
drop temporary table if exists __temp_ids;
create temporary table __temp_ids(root_id int not null primary key);
set sql_query = concat('
insert into __temp_ids
select
`',id_column,'`
from ',table_name);
prepare statement from sql_query;
execute statement;
drop temporary table if exists __temp_cycle;
create temporary table __temp_cycle (id int not null, parent_id int not null);
open id_cursor;
id_loop: loop
fetch from id_cursor into id;
if done then
leave id_loop;
end if;
set where_clause = concat('where `',id_column,'` = ',id);
set sql_query = concat('
insert into __temp_cycle
select
t.`',id_column,'`,
t.`',parent_id_column,'`
from
(
with recursive find_cycle as (
select
`',id_column,'`,
`',parent_id_column,'`,
0 depth
from
`',table_name,'`
',where_clause,'
union all
select
f.`',id_column,'`,
c.`',parent_id_column,'`,
f.depth + 1
from
`',table_name,'` c
inner join find_cycle f
ON f.`',parent_id_column,'` = c.`',id_column,'`
where
c.`',parent_id_column,'` <> c.`',id_column,'`
and f.`',parent_id_column,'` <> f.`',id_column,'`
and f.depth < ',max_depth,'
)
select
c.`',id_column,'`,
c.`',parent_id_column,'`
from find_cycle f
inner join `',table_name,'` c
on f.`',parent_id_column,'` = c.`',id_column,'`
order by depth desc
limit 1
) t
where t.`',parent_id_column,'` > 0');
prepare statement from sql_query;
execute statement;
end loop;
close id_cursor;
deallocate prepare statement;
select distinct
*
from __temp_cycle;
drop temporary table if exists __temp_ids;
drop temporary table if exists __temp_cycle;
END
usage:
call detect_cycle(table_name, id_column, parent_id_column, max_depth);
This will return a result set of all cycle conditions within the given table.

Looks like you have this figured out to stop a cycling event but are looking for ways to identify a cycle. In that case, consider using a path:
with recursive parents as (
select
s.id,
s.parent_id,
1 as depth,
CONCAT(s.id,'>',s.parent_id) as path,
NULL as cycle_detection
from categories s
where s.id = <passed in id>
union all
select
t.id,
t.parent_id,
c.depth + 1 as depth,
CONCAT(c.path, '>', t.parent_id),
CASE WHEN c.path LIKE CONCAT('%',t.parent_id,'>%') THEN 'cycle' END
from categories t
inner join parents c
on t.id = c.parent_id
where t.id <> t.parent_id)
select distinct parent_id, cycle_detection from parents where parent_id <> 0 order by depth desc
I may be a bit off my syntax since it's been forever since I wrote mysql/mariadb syntax, but this is the basic idea. Capture the path that the recursion took and then see if your current item is already in the path.

If the depth of the resulting tree is not extremely deep then you can detect cycles by storing the bread crumbs that the recursive CTE is walking. Knowing the bread crumbs you can detect cycles easily.
For example:
with recursive
n as (
select id, parent_id, concat('/', id, '/') as path
from categories where id = 2
union all
select c.id, c.parent_id, concat(n.path, c.id, '/')
from n
join categories c on c.parent_id = n.id
where n.path not like concat('%/', c.id, '/%') -- cycle pruning here!
)
select * from n;
Result:
id parent_id path
--- ---------- -------
2 1 /2/
3 2 /2/3/
1 3 /2/3/1/
See running example at DB Fiddle.

Related

Use Exists with a Column of Query Result?

I have 2 tables.
One is bom_master:
CHILD
PARENT
1-111
66-6666
2-222
77-7777
2-222
88-8888
3-333
99-9999
Another one is library:
FileName
Location
66-6666_A.step
S:\ABC
77-7777_C~K1.step
S:\DEF
And I want to find out if the child's parents have related files in the library.
Expected Result:
CHILD
PARENT
FileName
1-111
66-6666
66-6666_A.step
2-222
77-7777
77-7777_C~K1.step
Tried below lines but return no results. Any comments? Thank you.
WITH temp_parent_PN(parentPN)
AS
(
SELECT
[PARENT]
FROM [bom_master]
where [bom_master].[CHILD] in ('1-111','2-222')
)
SELECT s.[filename]
FROM [library] s
WHERE EXISTS
(
SELECT
*
FROM temp_parent_PN b
where s.[filename] LIKE '%'+b.[parentPN]+'%'
)
If you have just one level of dependencies use the join solution proposed by dimis164.
If you have deeper levels you could use recursive queries allowed by WITH clause (
ref. WITH common_table_expression (Transact-SQL)).
This is a sample with one more level of relation in bom_master (you could then join the result of the recursive query with library as you need).
DECLARE #bom_master TABLE (Child NVARCHAR(MAX), Parent NVARCHAR(MAX));
INSERT INTO #bom_master VALUES
('1-111', '66-666'),
('2-222', '77-777'),
('3-333', '88-888'),
('4-444', '99-999'),
('A-AAA', '1-111');
WITH
leaf AS ( -- Get the leaf elements (elements without a child)
SELECT Child FROM #bom_master b1
WHERE NOT EXISTS (SELECT * FROM #bom_master b2 WHERE b2.Parent = b1.Child) ),
rec(Child, Parent, Level) AS (
SELECT b.Child, b.Parent, Level = 1
FROM #bom_master b
JOIN leaf l ON l.Child = b.Child
UNION ALL
SELECT rec.Child, b.Parent, Level = rec.Level + 1
FROM rec
JOIN #bom_master b
ON b.Child = rec.Parent )
SELECT * FROM rec
I think you don't have to use exists. The problem is that you need to substring to match the join.
Have a look at this:
SELECT b.CHILD, b.PARENT, l.[FileName]
FROM [bom_master] b
INNER JOIN [library] l ON b.PARENT = SUBSTRING(l.FileName,1,7)

How can I check that ALL tags apply to these items in SQL?

I have four tables: items relationships tags item_to_tags. Relationships can connect two items and items_to_tags connect tags to items like this:
items
id
...
0
...
1
...
2
...
3
...
4
...
relationships
source_item_id
target_item_id
0
1
0
2
1
3
1
4
tags
id
name
0
A
1
B
items_to_tags
item_id
tag_id
1
0
1
1
2
1
3
0
3
1
4
1
The above would give a graph that looks like this
I've created a recursive function that will give me all of the descendants starting from a specific item:
CREATE OR REPLACE FUNCTION get_items_descendants(item_id int)
RETURNS SETOF items AS $$
WITH RECURSIVE descendants AS (
SELECT i.id, r.target_item_id
FROM items i
LEFT OUTER JOIN relationships r ON (i.id = r.source_item_id)
WHERE i.id = item_id
UNION
SELECT i.id, r.target_item_id
FROM descendants a
JOIN items i ON (a.target_item_id = i.id)
LEFT OUTER JOIN relationships r ON (i.id = r.source_item_id)
)
SELECT * FROM items i WHERE i.id IN (SELECT id FROM descendants WHERE id != item_id);
$$ LANGUAGE sql STABLE;
DBFiddle here: https://www.db-fiddle.com/f/teicDervXhN3AmEPfYzNn2/1
For example, if you run SELECT * FROM get_items_descendants(1); then it will return items 3 and 4 since they're the descendants of item 1.
I then updated it to allow a tag filter to be applied like this:
CREATE OR REPLACE FUNCTION get_items_descendants(item_id int, tag_filters integer[] = array[]::integer[])
RETURNS SETOF items AS $$
WITH RECURSIVE descendants AS (
SELECT i.id, r.target_item_id
FROM items i
LEFT OUTER JOIN relationships r ON (i.id = r.source_item_id)
WHERE i.id = item_id
UNION
SELECT i.id, r.target_item_id
FROM descendants a
JOIN items i ON (a.target_item_id = i.id)
LEFT OUTER JOIN relationships r ON (i.id = r.source_item_id)
LEFT OUTER JOIN items_to_tags t ON (i.id = t.item_id)
WHERE cardinality(tag_filters::integer[]) = 0 OR t.tag_id = ANY(tag_filters)
)
SELECT * FROM items i WHERE i.id IN (SELECT id FROM descendants WHERE id != item_id);
$$ LANGUAGE sql STABLE;
DBFiddle here: https://www.db-fiddle.com/f/xvKwN96kJnBqZ59QUXbYvj/1
Now calling SELECT * FROM get_items_descendants(1, ARRAY[0]); only returns item 3 because item 4 doesn't have the tag A. Passing ARRAY[0,1] or ARRAY[1] returns both 3 and 4 because they both have tag B and the t.tag_id = ANY(tag_filters) only requires one of the tag filters to exist.
What i'm struggling with is updating the function so that ALL tags must exist if they're defined in the tag_filters parameter. So ARRAY[0,1] will only return item 3.
Is this possible? The data structure above is fairly locked so can't be changed too much as it's already in production. Also, if anyone has any advice on the functions above that would be much appreciated as i'm quite new to SQL.
I ended up having to do a solution for when none of the tags apply as well as any of and all of. This is my current solution if anyone finds it useful. If anyone has any ways of improving it then please let me know:
CREATE OR REPLACE FUNCTION get_items_descendants(item_id int, tag_filters integer[] = array[]::integer[], operation text = 'any')
RETURNS SETOF items AS $$
WITH RECURSIVE descendants AS (
SELECT i.id, r.target_item_id, -1 as tag_id
FROM items i
LEFT OUTER JOIN relationships r ON (i.id = r.source_item_id)
WHERE i.id = item_id
UNION
SELECT i.id, r.target_item_id, t.tag_id
FROM descendants d
JOIN items i ON (d.target_item_id = i.id)
LEFT OUTER JOIN relationships r ON (i.id = r.source_item_id)
LEFT OUTER JOIN items_to_tags t ON (i.id = t.item_id)
WHERE (operation = 'none' AND NOT EXISTS (SELECT * FROM items_to_tags WHERE item_id = i.id AND tag_id = ANY(tag_filters)))
OR (operation != 'none' AND (cardinality(tag_filters::integer[]) = 0 OR t.tag_id = ANY(tag_filters)))
)
SELECT * FROM items i
WHERE (operation = 'all'
AND i.id IN (
SELECT id FROM (
SELECT id, array_agg(tag_id) as tag_ids
FROM descendants d
WHERE id != item_id
GROUP BY id
) as d
WHERE d.tag_ids #> tag_filters
)
)
OR (operation != 'all'
AND i.id IN (
SELECT id FROM descendants WHERE id != item_id
)
);
$$ LANGUAGE sql STABLE;

Troubles isolating target cell in recursive sql query

I have a table, let's say it looks like this:
c | p
=====
|1|3|
|2|1|
|7|5|
c stands for current and p stands for parent
Given a c value of 2 I would return its top most ancestor (which has no parent) this value is 3. Since this is a self referencing table, I figured using CTE would be the best method however I am very new to using it. Nevertheless, I gave it a shot:
WITH Tree(this, parent) AS
( SELECT c ,p
FROM myTable
WHERE c = '2'
UNION ALL
SELECT M.c ,M.p
FROM myTable M
JOIN Tree T ON T.parent = M.c )
SELECT parent
FROM Tree
However this returns:
1
3
I only want 3 though. I have tried putting WHERE T.parent <> M.c but that doesn't entirely make sense. Neadless to say, I am a little confused for how to isolate the grandparent.
DECLARE #Table AS TABLE (Child INT, Parent INT)
INSERT INTO #Table VALUES (1,3),(2,1),(7,5)
;WITH cteRecursive AS (
SELECT
OriginalChild = Child
,Child
,Parent
,Level = 0
FROM
#Table
WHERE
Child = 2
UNION ALL
SELECT
c.OriginalChild
,t.Child
,t.Parent
,Level + 1
FROM
cteRecursive c
INNER JOIN #Table t
ON c.Parent = t.Child
)
SELECT TOP 1 TopAncestor = Parent
FROM
cteRecursive
ORDER BY
Level DESC
Use a recursive cte to Recuse up the tree until you cannot. Keep track of the Level of recursion, then take the last level of recursions parent and you have the top ancestor.
And just because I wrote it I will add in if you wanted to find the top ancestor of every child. The concept is still the same but you would need to introduce a row_number() to find the last level that was recursed.
DECLARE #Table AS TABLE (Child INT, Parent INT)
INSERT INTO #Table VALUES (1,3),(2,1),(7,5),(5,9)
;WITH cteRecursive AS (
SELECT
OriginalChild = Child
,Child
,Parent
,Level = 0
FROM
#Table
UNION ALL
SELECT
c.OriginalChild
,t.Child
,t.Parent
,Level + 1
FROM
cteRecursive c
INNER JOIN #Table t
ON c.Parent = t.Child
)
, cteTopAncestorRowNum AS (
SELECT
*
,TopAncestorRowNum = ROW_NUMBER() OVER (PARTITION BY OriginalChild ORDER BY Level DESC)
FROM
cteRecursive
)
SELECT
Child = OriginalChild
,TopMostAncestor = Parent
FROM
cteTopAncestorRowNum
WHERE
TopAncestorRowNum = 1

Postgres recursive query with row_to_json

I've got a table in postgres 9.3.5 that looks like this:
CREATE TABLE customer_area_node
(
id bigserial NOT NULL,
customer_id integer NOT NULL,
parent_id bigint,
name text,
description text,
CONSTRAINT customer_area_node_pkey PRIMARY KEY (id)
)
I query with:
WITH RECURSIVE c AS (
SELECT *, 0 as level, name as path FROM customer_area_node WHERE customer_id = 2 and parent_id is null
UNION ALL
SELECT customer_area_node.*,
c.level + 1 as level,
c.path || '/' || customer_area_node.name as path
FROM customer_area_node
join c ON customer_area_node.parent_id = c.id
)
SELECT * FROM c ORDER BY path;
this seems to work to build paths like building1/floor1/room1, building1/floor1/room2, etc.
What I'd like to be able to do is easily turn that into either json that represents the tree structure which I've been told I can do with row_to_json.
As a reasonable alternative, any other way I can format the data to a more efficient mechanism such that I can actually easily turn it into an actual tree structure without having a ton of string.splits on /.
Is there a reasonably easy way to do this with row_to_json?
Sorry for the very late answer but i think i found an elegant solution that could become an accepted answer for this question.
Based on the awesome "little hack" found by #pozs, i came up with a solution that:
solves the "rogue leaves" situation with very little code (leveraging the NOT EXISTS predicate)
avoids the whole level calculation/condition stuff
WITH RECURSIVE customer_area_tree("id", "customer_id", "parent_id", "name", "description", "children") AS (
-- tree leaves (no matching children)
SELECT c.*, json '[]'
FROM customer_area_node c
WHERE NOT EXISTS(SELECT * FROM customer_area_node AS hypothetic_child WHERE hypothetic_child.parent_id = c.id)
UNION ALL
-- pozs's awesome "little hack"
SELECT (parent).*, json_agg(child) AS "children"
FROM (
SELECT parent, child
FROM customer_area_tree AS child
JOIN customer_area_node parent ON parent.id = child.parent_id
) branch
GROUP BY branch.parent
)
SELECT json_agg(t)
FROM customer_area_tree t
LEFT JOIN customer_area_node AS hypothetic_parent ON(hypothetic_parent.id = t.parent_id)
WHERE hypothetic_parent.id IS NULL
Update:
Tested with very simple data, it does work, but as posz pointed out in a comment, with his sample data, some rogue leaf nodes are forgotten. But, i found out that with even more complex data, the previous answer is not working either, because only rogue leaf nodes having a common ancestor with "max level" leaf nodes are caught (when "1.2.5.8" is not there, "1.2.4" and "1.2.5" are absent because they have no common ancestor with any "max level" leaf node).
So here is a new proposition, mixing posz's work with mine by extracting the NOT EXISTS subrequest and making it an internal UNION, leveraging UNION de-duplication abilities (leveraging jsonb comparison abilities):
<!-- language: sql -->
WITH RECURSIVE
c_with_level AS (
SELECT *, 0 as lvl
FROM customer_area_node
WHERE parent_id IS NULL
UNION ALL
SELECT child.*, parent.lvl + 1
FROM customer_area_node child
JOIN c_with_level parent ON parent.id = child.parent_id
),
maxlvl AS (
SELECT max(lvl) maxlvl FROM c_with_level
),
c_tree AS (
SELECT c_with_level.*, jsonb '[]' children
FROM c_with_level, maxlvl
WHERE lvl = maxlvl
UNION
(
SELECT (branch_parent).*, jsonb_agg(branch_child)
FROM (
SELECT branch_parent, branch_child
FROM c_with_level branch_parent
JOIN c_tree branch_child ON branch_child.parent_id = branch_parent.id
) branch
GROUP BY branch.branch_parent
UNION
SELECT c.*, jsonb '[]' children
FROM c_with_level c
WHERE NOT EXISTS (SELECT 1 FROM c_with_level hypothetical_child WHERE hypothetical_child.parent_id = c.id)
)
)
SELECT jsonb_pretty(row_to_json(c_tree)::jsonb)
FROM c_tree
WHERE lvl = 0;
Tested on http://rextester.com/SMM38494 ;)
You cannot do that with a usual recursive CTE, because it is almost impossible to set a json value deep in its hierarchy. But you can do it reversed: build up the tree starting from its leaves, until its root:
-- calculate node levels
WITH RECURSIVE c AS (
SELECT *, 0 as lvl
FROM customer_area_node
-- use parameters here, to select the root first
WHERE customer_id = 2 AND parent_id IS NULL
UNION ALL
SELECT customer_area_node.*, c.lvl + 1 as lvl
FROM customer_area_node
JOIN c ON customer_area_node.parent_id = c.id
),
-- select max level
maxlvl AS (
SELECT max(lvl) maxlvl FROM c
),
-- accumulate children
j AS (
SELECT c.*, json '[]' children -- at max level, there are only leaves
FROM c, maxlvl
WHERE lvl = maxlvl
UNION ALL
-- a little hack, because PostgreSQL doesn't like aggregated recursive terms
SELECT (c).*, array_to_json(array_agg(j)) children
FROM (
SELECT c, j
FROM j
JOIN c ON j.parent_id = c.id
) v
GROUP BY v.c
)
-- select only root
SELECT row_to_json(j) json_tree
FROM j
WHERE lvl = 0;
And this will work even with PostgreSQL 9.2+
SQLFiddle
Update: A variant, which should handle rogue leaf nodes too (which are located with a level between 1 and max-level):
WITH RECURSIVE c AS (
SELECT *, 0 as lvl
FROM customer_area_node
WHERE customer_id = 1 AND parent_id IS NULL
UNION ALL
SELECT customer_area_node.*, c.lvl + 1
FROM customer_area_node
JOIN c ON customer_area_node.parent_id = c.id
),
maxlvl AS (
SELECT max(lvl) maxlvl FROM c
),
j AS (
SELECT c.*, json '[]' children
FROM c, maxlvl
WHERE lvl = maxlvl
UNION ALL
SELECT (c).*, array_to_json(array_agg(j) || array(SELECT r
FROM (SELECT l.*, json '[]' children
FROM c l, maxlvl
WHERE l.parent_id = (c).id
AND l.lvl < maxlvl
AND NOT EXISTS (SELECT 1
FROM c lp
WHERE lp.parent_id = l.id)) r)) children
FROM (SELECT c, j
FROM c
JOIN j ON j.parent_id = c.id) v
GROUP BY v.c
)
SELECT row_to_json(j) json_tree
FROM j
WHERE lvl = 0;
This should work too on PostgreSQL 9.2+, however, I cannot test that. (I can only test on 9.5+ right now).
These solutions can handle any column in any hierarchical table, but will always append an int typed lvl JSON property to their output.
http://rextester.com/YNU7932
Developed the answer of pozs a little further to get recursive leaves with their subtrees. So this answer really returns the full tree.
CREATE OR REPLACE FUNCTION pg_temp.getTree(bigint)
RETURNS TABLE(
id bigint,
customer_id integer,
parent_id bigint,
name text,
description text,
children json
)
AS $$
WITH RECURSIVE relations AS (
SELECT
can.id,
can.customer_id,
can.parent_id,
can.name,
can.description,
0 AS depth
FROM customer_area_node can
WHERE can.id = $1
UNION ALL
SELECT
can.id,
can.customer_id,
can.parent_id,
can.name,
can.description,
relations.depth + 1
FROM customer_area_node can
JOIN relations ON can.parent_id = relations.id AND can.id != can.parent_id
),
maxdepth AS (
SELECT max(depth) maxdepth FROM relations
),
rootTree as (
SELECT r.* FROM
relations r, maxdepth
WHERE depth = maxdepth
UNION ALL
SELECT r.* FROM
relations r, rootTree
WHERE r.id = rootTree.parent_id AND rootTree.id != rootTree.parent_id
),
mainTree AS (
SELECT
c.id,
c.customer_id,
c.parent_id,
c.name,
c.description,
c.depth,
json_build_array() children
FROM relations c, maxdepth
WHERE c.depth = maxdepth
UNION ALL
SELECT
(relations).*,
array_to_json(
array_agg(mainTree)
||
array(
SELECT t
FROM (
SELECT
l.*,
json_build_array() children
FROM relations l, maxdepth
WHERE l.parent_id = (relations).id
AND l.depth < maxdepth
AND l.id NOT IN (
SELECT id FROM rootTree
)
) r
JOIN pg_temp.getTree(r.id) t
ON r.id = t.id
))
children
FROM (
SELECT relations, mainTree
FROM relations
JOIN mainTree
ON (
mainTree.parent_id = relations.id
AND mainTree.parent_id != mainTree.id
)
) v
GROUP BY v.relations
)
SELECT
id,
customer_id,
parent_id,
name,
description,
children
FROM mainTree WHERE id = $1
$$
LANGUAGE SQL;
SELECT *
FROM
customer_area_node can
JOIN pg_temp.getTree(can.id) t ON t.id = can.id
WHERE can.parent_id IS NULL;

Tree structure in SQL Server 2008

In SQL Server 2008;
I have a tree. I need to get all child nodes of node n (see diagram) and all child nodes of these child nodes, etc until the leaf nodes which is fairly trivial. I also need to be able to say 'Take node o, go up the tree until we reach m and because m is a child of node n set some property of node o to some property of node m. Node o could be 3 levels deep (as illustrated) or 45 levels deep, x levels deep.
This gets all children of a given node (or area)
--Return all sub-area structure of an area:
WITH temp_areas (ParentAreaID, AreaID, [Name], [Level]) AS
(
SELECT ParentAreaID, AreaID, [Name], 0
FROM lib_areas WHERE AreaID = #AreaID
UNION ALL
SELECT B.ParentAreaID, B.AreaID, B.[Name], A.Level + 1
FROM temp_areas AS A, lib_areas AS B
WHERE A.AreaID = B.ParentAreaID
)
INSERT INTO #files (id) SELECT fileid FROM lib_filesareasxref where areaid in (select areaid from temp_areas)
while exists (select * from #files)
begin
select top 1
#ID = id
from
#files ORDER BY id DESC
delete from #files where id = #id
This will track back from #node_o until it reaches #node_m or it reaches the top of the tree (if #node_m is not above #node_o).
WITH
parents
AS
(
SELECT
A.ParentAreaID, A.AreaID, A.[Name], 0
FROM
lib_areas AS A
WHERE
A.AreaID = #node_o
UNION ALL
SELECT
A.ParentAreaID, A.AreaID, A.[Name], B.Level + 1
FROM
lib_areas AS A
INNER JOIN
parents AS B
ON A.AreaID = B.ParentAreaID
WHERE
B.AreaID <> #node_m
)
SELECT
*
FROM
parents
I'd propose using a HierarchyID data type in your table, and using the GetAncestor method