patternsqlMajor
CTE: Get all parents and all children in one statement
Viewed 0 times
childrenallstatementcteonegetandparents
Problem
I have this working CTE example.
I can select all grand-parents and all children.
But how can I select all grand-parents and all children in one statement?
In this example I want Grandfather, Father, Son as output if I give "Father" as input.
I use PostgreSQL. But I think this question should be standard SQL.
Please correct me if I use PostgreSQL specific syntax.
Above is a simplified working example. The tree can be up to 100 level deep. There can be several level of ancestors above "Father" and several level of descendants below. I want all ancestors and all descendants.
I can select all grand-parents and all children.
But how can I select all grand-parents and all children in one statement?
In this example I want Grandfather, Father, Son as output if I give "Father" as input.
I use PostgreSQL. But I think this question should be standard SQL.
Please correct me if I use PostgreSQL specific syntax.
DROP table if exists tree;
CREATE TABLE tree (
id SERIAL PRIMARY KEY,
name character varying(64) NOT NULL,
parent_id integer REFERENCES tree NULL
);
insert into tree values (1, 'Grandfather', NULL);
insert into tree values (2, 'Father', 1);
insert into tree values (3, 'Son', 2);
-- -------------------------------------
-- Getting all children works
WITH RECURSIVE rec (id) as
(
SELECT tree.id, tree.name from tree where name='Father'
UNION ALL
SELECT tree.id, tree.name from rec, tree where tree.parent_id = rec.id
)
SELECT *
FROM rec;
-- Result:
-- id | name
-- ----+--------
-- 2 | Father
-- 3 | Son
-- -------------------------------------
-- Getting all parents works
WITH RECURSIVE rec (id) as
(
SELECT tree.id, tree.name, tree.parent_id from tree where name='Father'
UNION ALL
SELECT tree.id, tree.name, tree.parent_id from rec, tree where tree.id = rec.parent_id
)
SELECT id, name
FROM rec;
-- Result
-- id | name
-- ----+-------------
-- 2 | Father
-- 1 | GrandfatherAbove is a simplified working example. The tree can be up to 100 level deep. There can be several level of ancestors above "Father" and several level of descendants below. I want all ancestors and all descendants.
Solution
If you want all ancestors and all descendants, you can combine the two queries in one. Use the two CTEs and then a simple
If we haven't got any errors above, we can them improve it:
The query becomes:
UNION:WITH RECURSIVE
-- descendants
rec_d (id, name) AS
(
SELECT tree.id, tree.name FROM tree WHERE name = 'Father'
UNION ALL
SELECT tree.id, tree.name FROM rec_d, tree where tree.parent_id = rec_d.id
),
-- ancestors
rec_a (id, name, parent_id) AS
(
SELECT tree.id, tree.name, tree.parent_id FROM tree WHERE name = 'Father'
UNION ALL
SELECT tree.id, tree.name, tree.parent_id FROM rec_a, tree WHERE tree.id = rec_a.parent_id
)
SELECT id, name FROM rec_a
UNION
SELECT id, name FROM rec_d ;If we haven't got any errors above, we can them improve it:
- change the final
UNIONtoUNION ALLby putting the starting node(s) in only one of the CTEs.
- use
JOIN .. ONinstead of the implicit joins.
- fix mismatches between SELECT and CTE column lists.
The query becomes:
WITH RECURSIVE
-- starting node(s)
starting (id, name, parent_id) AS
(
SELECT t.id, t.name, t.parent_id
FROM tree AS t
WHERE t.name = 'Father' -- this can be arbitrary
),
descendants (id, name, parent_id) AS
(
SELECT s.id, s.name, s.parent_id
FROM starting AS s
UNION ALL
SELECT t.id, t.name, t.parent_id
FROM tree AS t JOIN descendants AS d ON t.parent_id = d.id
),
ancestors (id, name, parent_id) AS
(
SELECT t.id, t.name, t.parent_id
FROM tree AS t
WHERE t.id IN (SELECT parent_id FROM starting)
UNION ALL
SELECT t.id, t.name, t.parent_id
FROM tree AS t JOIN ancestors AS a ON t.id = a.parent_id
)
TABLE ancestors
UNION ALL
TABLE descendants ;Code Snippets
WITH RECURSIVE
-- descendants
rec_d (id, name) AS
(
SELECT tree.id, tree.name FROM tree WHERE name = 'Father'
UNION ALL
SELECT tree.id, tree.name FROM rec_d, tree where tree.parent_id = rec_d.id
),
-- ancestors
rec_a (id, name, parent_id) AS
(
SELECT tree.id, tree.name, tree.parent_id FROM tree WHERE name = 'Father'
UNION ALL
SELECT tree.id, tree.name, tree.parent_id FROM rec_a, tree WHERE tree.id = rec_a.parent_id
)
SELECT id, name FROM rec_a
UNION
SELECT id, name FROM rec_d ;WITH RECURSIVE
-- starting node(s)
starting (id, name, parent_id) AS
(
SELECT t.id, t.name, t.parent_id
FROM tree AS t
WHERE t.name = 'Father' -- this can be arbitrary
),
descendants (id, name, parent_id) AS
(
SELECT s.id, s.name, s.parent_id
FROM starting AS s
UNION ALL
SELECT t.id, t.name, t.parent_id
FROM tree AS t JOIN descendants AS d ON t.parent_id = d.id
),
ancestors (id, name, parent_id) AS
(
SELECT t.id, t.name, t.parent_id
FROM tree AS t
WHERE t.id IN (SELECT parent_id FROM starting)
UNION ALL
SELECT t.id, t.name, t.parent_id
FROM tree AS t JOIN ancestors AS a ON t.id = a.parent_id
)
TABLE ancestors
UNION ALL
TABLE descendants ;Context
StackExchange Database Administrators Q#175868, answer score: 30
Revisions (0)
No revisions yet.