Firstly we need to build the recursions.
WITH nodes(node, family) AS (
SELECT *
FROM VALUES
('A',1),
('B',1),
('C',1),
('D',1),
('E',1),
('F',1),
('G',1),
('H',1),
('I',2),
('J',2)
), edges(_from, _to) AS (
SELECT *
FROM VALUES
('A','B'),
('B','C'),
('B','D'),
('D','E'),
('F','G'),
('G','H')
), recursive_wrapper AS (
WITH recur AS (
-- Anchor Clause
select
family,
node,
node as tail,
array_construct(node) path,
1 as depth
from nodes
union all
-- Recursive Clause
select r.family,
r.node,
e._to as tail,
ARRAY_APPEND(r.path, e._to) as path,
r.depth + 1 as depth
from recur AS r
join edges AS e
on r.tail = e._from
)
SELECT *
FROM recur
)
SELECT *
FROM recursive_wrapper
WHERE node = 'A'
gives:
| FAMILY |
NODE |
TAIL |
PATH |
DEPTH |
| 1 |
A |
A |
[ "A" ] |
1 |
| 1 |
A |
B |
[ "A", "B" ] |
2 |
| 1 |
A |
C |
[ "A", "B", "C" ] |
3 |
| 1 |
A |
D |
[ "A", "B", "D" ] |
3 |
| 1 |
A |
E |
[ "A", "B", "D", "E" ] |
4 |
Then we need to prune the sublimated rows (in above we want to remove depth 1,2,4 as they are sub parts of another row), we can do this by swapping from an array to a string for path
), recursive_wrapper AS (
WITH recur AS (
-- Anchor Clause
select
family,
node,
node as tail,
node::text path, /* this cast it to max size, verse the largest size of the input */
1 as depth
from nodes
union all
-- Recursive Clause
select r.family,
r.node,
e._to as tail,
r.path || '_' || e._to as path,
r.depth + 1 as depth
from recur AS r
join edges AS e
on r.tail = e._from
)
SELECT *
FROM recur
)
SELECT a.*
,b.path as b_path
,b.depth as b_depth
,startswith(b.path, a.path) as starts
,sum(iff(starts,1,0)) over(partition by a.family, a.node, a.path) as dominated_count
FROM recursive_wrapper as a
LEFT JOIN recursive_wrapper as b
ON a.family = b.family AND a.node = b.node AND a.depth < b.depth
WHERE a.node = 'A'
ORDER BY 1,2,4;
gives:
| FAMILY |
NODE |
TAIL |
PATH |
DEPTH |
B_PATH |
B_DEPTH |
STARTS |
DOMINATED_COUNT |
| 1 |
A |
A |
A |
1 |
A_B |
2 |
TRUE |
4 |
| 1 |
A |
A |
A |
1 |
A_B_C |
3 |
TRUE |
4 |
| 1 |
A |
A |
A |
1 |
A_B_D |
3 |
TRUE |
4 |
| 1 |
A |
A |
A |
1 |
A_B_D_E |
4 |
TRUE |
4 |
| 1 |
A |
B |
A_B |
2 |
A_B_C |
3 |
TRUE |
3 |
| 1 |
A |
B |
A_B |
2 |
A_B_D |
3 |
TRUE |
3 |
| 1 |
A |
B |
A_B |
2 |
A_B_D_E |
4 |
TRUE |
3 |
| 1 |
A |
C |
A_B_C |
3 |
A_B_D_E |
4 |
FALSE |
0 |
| 1 |
A |
D |
A_B_D |
3 |
A_B_D_E |
4 |
TRUE |
1 |
| 1 |
A |
E |
A_B_D_E |
4 |
|
|
|
0 |
so we want to just keep those with a count of zero, which we can do in a QUALIFY
SELECT a.*
--,b.path as b_path
--,b.depth as b_depth
--,startswith(b.path, a.path) as starts
--,sum(iff(starts,1,0)) over(partition by a.family, a.node, a.path) as dominated_count
FROM recursive_wrapper as a
LEFT JOIN recursive_wrapper as b
ON a.family = b.family AND a.node = b.node AND a.depth < b.depth
WHERE a.node = 'A'
QUALIFY sum(iff(startswith(b.path, a.path),1,0)) over(partition by a.family, a.node, a.path) = 0
ORDER BY 1,2,4;
gives:
| FAMILY |
NODE |
TAIL |
PATH |
DEPTH |
| 1 |
A |
C |
A_B_C |
3 |
| 1 |
A |
E |
A_B_D_E |
4 |
This will explode with loops, which we can protect against by put the array back, and checking for the value already being in the array
WITH nodes(node, family) AS (
SELECT *
FROM VALUES
('A',1),
('B',1),
('C',1),
('D',1),
('E',1),
('F',1),
('G',1),
('H',1),
('I',2),
('J',2)
), edges(_from, _to) AS (
SELECT *
FROM VALUES
('A','B'),
('B','C'),
('B','D'),
('D','E'),
('F','G'),
('G','H')
), recursive_wrapper AS (
WITH recur AS (
-- Anchor Clause
select
family,
node,
node as tail,
node::text path, /* this cast it to max size, verse the largest size of the input */
array_construct(node) a_path,
1 as depth
from nodes
union all
-- Recursive Clause
select r.family,
r.node,
e._to as tail,
r.path || '_' || e._to as path,
ARRAY_APPEND(r.a_path, e._to) as a_path,
r.depth + 1 as depth
from recur AS r
join edges AS e
on r.tail = e._from AND ARRAY_CONTAINS( e._to::variant , r.a_path ) = FALSE
)
SELECT *
FROM recur
)
SELECT a.*
FROM recursive_wrapper as a
LEFT JOIN recursive_wrapper as b
ON a.family = b.family AND a.node = b.node AND a.depth < b.depth
QUALIFY sum(iff(startswith(b.path, a.path),1,0)) over(partition by a.family, a.node, a.path) = 0
ORDER BY 1,2,4;