Recursive relationship on a many to many table - sql

I set up a many to many recursive table like this:
CREATE TABLE PROD
(
IDPROD INT NOT NULL PRIMARY KEY,
NAME VARCHAR(3)
);
CREATE TABLE COMP
(
IDPARENT INT REFERENCES PROD(IDPROD),
IDCHILD INT REFERENCES PROD(IDPROD)
);
INSERT INTO PROD (IDPROD, NAME) VALUES (1, 'abc');
INSERT INTO PROD (IDPROD, NAME) VALUES (2, 'def');
INSERT INTO PROD (IDPROD, NAME) VALUES (3, 'ghi');
INSERT INTO PROD (IDPROD, NAME) VALUES (4, 'jkl');
INSERT INTO PROD (IDPROD, NAME) VALUES (5, 'mno');
INSERT INTO COMP (IDPARENT, IDCHILD) VALUES (1, 2);
INSERT INTO COMP (IDPARENT, IDCHILD) VALUES (3, 4);
INSERT INTO COMP (IDPARENT, IDCHILD) VALUES (4, 5);
With a recursivs CTE I can get all the children of a specific node from the second table.
WITH RECURSIVE TEST (IDPARENT, IDCHILD) AS
(SELECT P0.IDPARENT, P0.IDCHILD
FROM COMP AS P0
WHERE P0.IDPARENT = 3
UNION ALL
SELECT P1.IDPARENT, P1.IDCHILD
FROM COMP AS P1, TEST AS T
WHERE T.IDCHILD = P1.IDPARENT)
SELECT * FROM TEST
But I need a query that will give me the entire structure, not just for one node. Like in the classic adjacency list where you get all the root nodes where IDPARENT IS NULL and their children listed below. I use Firebird.

I'm not familiar with Firebird, but this works in SQL Server, so is hopefully similar / enough to get you on track:
WITH TEST (IDRoot, IDPARENT, IDCHILD) AS
(
SELECT P0.IDPROD, C0.IDParent, C0.IDCHILD
FROM PROD AS P0
left outer join COMP C0 on C0.IDParent = P0.IDPROD
WHERE P0.IDProd not in (select IDChild from COMP)
UNION ALL
SELECT T.IDRoot, C1.IDPARENT, C1.IDCHILD
FROM COMP AS C1
inner join TEST AS T on T.IDCHILD = C1.IDPARENT
)
SELECT * FROM TEST
Hope that helps.
SQL Fiddle Version: http://sqlfiddle.com/#!6/22f84/7
Notes
Includ a column to denote the root of the tree as well as parent/child - since there may be multiple trees if we're not specifying a particular root:
WITH TEST (IDRoot, IDPARENT, IDCHILD) AS
Treat any product which is not a child as a ROOT (i.e. first item in a tree).
WHERE P0.IDProd not in (select IDChild from COMP)
EDIT: Answer to comments
Query on any node to see all of its relatives:
The simple way to filter on any node would be to amend the above statement's WHERE P0.IDProd not in (select IDChild from COMP) with WHERE P0.IDProd = IdImInterestedIn. However if you want to use the CTE for a view, then run queries over this static query you could use the code below - you can then filter on IDProd (select * from test where IDProd = IdImInterestedIn) to see that item's ancestors and descendants.
WITH TEST (IDProd, IDRelation, Generation) AS
(
SELECT IDPROD
, IDPROD
, 0
FROM PROD
UNION ALL
SELECT T.IDPROD
, C.IdParent
, T.Generation - 1
FROM TEST AS T
inner join Comp as C
on C.IdChild = T.IDRelation
where t.Generation <= 0
UNION ALL
SELECT T.IDPROD
, C.IdChild
, T.Generation + 1
FROM TEST AS T
inner join Comp as C
on C.IdParent = T.IDRelation
where t.Generation >= 0
)
SELECT *
FROM TEST
order by IDProd, Generation
SQL Fiddle: http://sqlfiddle.com/#!6/22f84/15
See a root node's full tree in a single column
WITH TEST (IDRoot, IDPARENT, IDCHILD, TREE) AS
(
SELECT P0.IDPROD, C0.IDParent, C0.IDCHILD, cast(P0.IDPROD as nvarchar(max)) + coalesce(', ' + cast(C0.IDCHILD as nvarchar(max)),'')
FROM PROD AS P0
left outer join COMP C0 on C0.IDParent = P0.IDPROD
WHERE P0.IDProd not in (select IDChild from COMP)
UNION ALL
SELECT T.IDRoot, C1.IDPARENT, C1.IDCHILD, TREE + coalesce(', ' + cast(C1.IDCHILD as nvarchar(max)),'')
FROM COMP AS C1
inner join TEST AS T on T.IDCHILD = C1.IDPARENT
)
SELECT *
FROM TEST
order by IDRoot
SQL Fiddle: http://sqlfiddle.com/#!6/22f84/19
EDIT: Answer to Additional Comments
with cte (tree_root_no, tree_row_no, relation_sort, relation_chart, Name, id, avoid_circular_ref) as
(
select row_number() over (order by p.idprod)
, 1
, cast(row_number() over (order by p.idprod) as nvarchar(max))
, cast('-' as nvarchar(max))
, p.NAME
, p.IDPROD
, ',' + cast(p.IDPROD as nvarchar(max)) + ','
from PROD p
where p.IDPROD not in (select IDCHILD from COMP) --if it's nothing's child, it's a tree's root
union all
select cte.tree_root_no
, cte.tree_row_no + 1
, cte.relation_sort + cast(row_number() over (order by p.idprod) as nvarchar(max))
, replace(relation_chart,'-','|') + ' -'
, p.NAME
, p.IDPROD
, cte.avoid_circular_ref + cast(p.IDPROD as nvarchar(max)) + ','
from cte
inner join COMP c on c.IDPARENT = cte.id
inner join PROD p on p.IDPROD = c.IDCHILD
where charindex(',' + cast(p.IDPROD as nvarchar(max)) + ',', cte.avoid_circular_ref) = 0
)
select tree_root_no, tree_row_no, relation_sort, relation_chart, id, name
from cte
order by tree_root_no, relation_sort
SQL Fiddle: http://sqlfiddle.com/#!6/4397f/9
Update to show each path
This one's a nasty hack, but the only way I could think of to solve your puzzle; this gives each path through the trees its own number:
;with inner_cte (parent, child, sequence, treePath) as (
select null
, p.IDPROD
, 1
, ',' + CAST(p.idprod as nvarchar(max)) + ','
from #prod p
where IDPROD not in
(
select IDCHILD from #comp
)
union all
select cte.child
, c.IDCHILD
, cte.sequence + 1
, cte.treePath + CAST(c.IDCHILD as nvarchar(max)) + ','
from inner_cte cte
inner join #comp c on c.IDPARENT = cte.child
)
, outer_cte (id, value, pathNo, sequence, parent, treePath) as
(
select icte.child, p.NAME, ROW_NUMBER() over (order by icte.child), icte.sequence, icte.parent, icte.treePath
from inner_cte icte
inner join #prod p on p.IDPROD = icte.child
where icte.child not in (select coalesce(icte2.parent,-1) from inner_cte icte2)
union all
select icte.child, p.NAME, octe.pathNo,icte.sequence, icte.parent, icte.treePath
from outer_cte octe
inner join inner_cte icte on icte.child = octe.parent and CHARINDEX(icte.treePath, octe.treePath) > 0
inner join #prod p on p.IDPROD = icte.child
)
select id, value, pathNo
from outer_cte
order by pathNo, sequence
SQL Fiddle here: http://sqlfiddle.com/#!6/5a16e/1

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 server CTE get parent's children then update children with parent value

How do I loop through all children and update one column for all the nodes? For example if I am on the parent level with column "OrganisationLevelID". If I update the "OrganisationLevelID" on the parent row it should propogate to the children hence all children of parent as well as the childrens' children should then have the same value in "OrganisationLevelID".
I have the following cte:
WITH cte as
(select h.Name, h.ID, h.OrganisationLevelID, h.parentId
From organisations h
WHERE ID = '3eea8c17-1bfd-46d5-9ea9-2d11652d23a4'
UNION ALL
select p.Name, p.ID, p.OrganisationLevelID, p.parentId
FROM organisations p
INNER JOIN cte c ON c.Id = p.parentId)
SELECT * FROM cte
This gets the parent child hierarchy and works fine.
How do I update the children values so that they all have the parent's "OrganisationLevelID"?
WITH cte as
(select h.Name, h.ID, h.OrganisationLevelID, h.parentId
From organisations h
WHERE ID = '3eea8c17-1bfd-46d5-9ea9-2d11652d23a4'
UNION ALL
select p.Name, p.ID, p.OrganisationLevelID, p.parentId
FROM organisations p
INNER JOIN cte c ON c.Id = p.parentId),
cteupdate as(**Some magic here**)
If you're looking for updating actual records in the Organisations table, I believe you can try this:
;WITH cte
AS (
SELECT h.Name
, h.ID
, h.OrganisationLevelID
, h.parentId
FROM organisations h
WHERE ID = '3eea8c17-1bfd-46d5-9ea9-2d11652d23a4'
UNION ALL
SELECT p.Name
, p.ID
, c.OrganisationLevelID -- note that I changed the alias from "p" to "c" to use the parent's value
, p.parentId
FROM organisations p
INNER JOIN cte c
ON c.Id = p.parentId
)
UPDATE org
SET OrganisationLevelID = cte.OrganisationLevelID
FROM organisations org
INNER JOIN cte
ON org.id = cte.id
You can use the below sample code to see what will happen, specifically the value in orgValueId from the parent row will "propagate" to all of its children and descendants:
create table #t (id int, parentid int, orgValueId varchar(10));
insert into #t values (1, null, 'x'), (2, 1, 'o'), (3, 1, 'm'), (4, 2, 'v'), (5, 3, 'p');
select *
from #t;
with cte as (
select id
, orgValueId
from #t
where parentid is null
union all
select t.id
, c.orgValueId
from cte c
inner join #t t on c.id = t.parentid)
update t
set orgValueId = c.orgValueId
from #t t
inner join cte c
on t.id = c.id;
select *
from #t;

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

get last node given the full path of all ancestor's node attributes using cte

Given the following PostgreSQL table:
items
integer id
integer parent_id
string name
unique key on [parent_id, name]
parent_id is null for all root nodes
Currently I build the sql query manually, doing a join for every path element. But is seems quite ugly to me and of course it limits the possible depth.
Example:
path: holiday,images,spain
SELECT i3.*
FROM items AS i1
, items AS i2
, items AS i3
WHERE i1.parent_id IS NULL AND i1.name = 'holiday'
AND i2.parent_id=i1.id AND i2.name = 'images'
AND i3.parent_id=i2.id AND i3.name = 'spain'
I wonder if there's a better way, probably using CTE?
You can see how my current code works and what the expected output is here:
http://sqlfiddle.com/#!1/4537c/2
update2 here's a function, it peforms well, because search goes only within the path, starting from parent:
create or replace function get_item(path text[])
returns items
as
$$
with recursive cte as (
select i.id, i.name, i.parent_id, 1 as level
from items as i
where i.parent_id is null and i.name = $1[1]
union all
select i.id, i.name, i.parent_id, c.level + 1
from items as i
inner join cte as c on c.id = i.parent_id
where i.name = $1[level + 1]
)
select c.id, c.parent_id, c.name
from cte as c
where c.level = array_length($1, 1)
$$
language sql;
sql fiddle demo
update I think you can do recursive traversal. I've written sql version of this, so it's a bit messy because of cte, but it's possible to write a function:
with recursive cte_path as (
select array['holiday', 'spain', '2013'] as arr
), cte as (
select i.id, i.name, i.parent_id, 1 as level
from items as i
cross join cte_path as p
where i.parent_id is null and name = p.arr[1]
union all
select i.id, i.name, i.parent_id, c.level + 1
from items as i
inner join cte as c on c.id = i.parent_id
cross join cte_path as p
where i.name = p.arr[level + 1]
)
select c.*
from cte as c
cross join cte_path as p
where level = array_length(p.arr, 1)
sql fiddle demo
or you can build path for all of the elements using recursive cte for that and accumuate your path into array or string:
with recursive cte as (
select i.id, i.name, i.parent_id, i.name::text as path
from items as i
where i.parent_id is null
union all
select i.id, i.name, i.parent_id, c.path || '->' || i.name::text as path
from items as i
inner join cte as c on c.id = i.parent_id
)
select *
from cte
where path = 'holiday->spain->2013';
or
with recursive cte as (
select i.id, i.name, i.parent_id, array[i.name::text] as path
from items as i
where i.parent_id is null
union all
select i.id, i.name, i.parent_id, c.path || array[i.name::text] as path
from items as i
inner join cte as c on c.id = i.parent_id
)
select *
from cte
where path = array['holiday', 'spain', '2013']
sql fiddle demo
This should perform very well, as it eliminates impossible paths immediately:
WITH RECURSIVE cte AS (
SELECT id, parent_id, name
,'{holiday,spain,2013}'::text[] AS path -- provide path as array here
,2 AS lvl -- next level
FROM items
WHERE parent_id IS NULL
AND name = 'holiday' -- being path[1]
UNION ALL
SELECT i.id, i.parent_id, i.name
,cte.path, cte.lvl + 1
FROM cte
JOIN items i ON i.parent_id = cte.id AND i.name = path[lvl]
)
SELECT id, parent_id, name
FROM cte
ORDER BY lvl DESC
LIMIT 1;
Assuming you provide a unique path (only 1 result).
->SQLfiddle demo
Too late to post my answer (very equivalent to Roman's and Erwin's) But an improvement on the table definition instead:
CREATE TABLE items
( id integer NOT NULL PRIMARY KEY
, parent_id integer REFERENCES items(id)
, name varchar
, UNIQUE (parent_id,name) -- I don't actually like this one
); -- ; UNIQUE on a NULLable column ...
INSERT INTO items (id, parent_id, name) values
(1, null, 'holiday')
, (2, 1, 'spain'), (3, 2, '2013')
, (4, 1, 'usa'), (5, 4, '2013')
;

SQL recursive query that gets all ancestors of an item

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.