I'm using PostgreSQL 9.5 and I have a table representing a tree:
CREATE TABLE tree (
dependent CHAR(1) NOT NULL,
prereq CHAR(1) NOT NULL,
PRIMARY KEY (dependent, prereq),
CHECK (dependent != prereq)
);
INSERT INTO tree VALUES
('B', 'A'),
('C', 'B'),
('F', 'D'),
('F', 'E'),
('G', 'E'),
('H', 'F'),
('H', 'G'),
('J', 'I'),
('K', 'I'),
('K', 'L'),
('N', 'J'),
('N', 'M'),
('P', 'O'),
('Q', 'P');
Each row in tree defines an edge between a dependent node that depends on a prerequisite (prereq) node. When all of a dependent's prerequisites are deleted, the dependent ceases to exist. (To be clear, cycles are not allowed.) I will refer to any node without any prerequisites, only dependents, as a root node.
I am looking for a single SQL query that, given a list of root nodes to be removed, would yield the complete set of nodes that would be deleted from the tree. I will only ever remove root nodes. For example, if I were to delete root nodes A, D, E, and I, the complete set of nodes to be deleted is A, B, C, D, E, F, G, H, I, and J. Here's an illustration of this:
Root nodes shaded red are in the initial list of nodes to be deleted. Nodes with red borders and letters are nodes that are deleted as a result of deleting all of their prerequisite nodes.
I have gotten pretty close with this query:
WITH RECURSIVE deletion AS (
SELECT
tree.*
FROM
tree
WHERE
prereq IN ('A', 'D', 'E', 'I')
UNION
SELECT
tree.*
FROM
deletion
JOIN tree ON tree.prereq = deletion.dependent
)
SELECT prereq FROM deletion
UNION
SELECT dependent FROM deletion
ORDER BY 1
However, this lists too many nodes to delete:
prereq
--------
A
B
C
D
E
F
G
H
I
J
K
N
(12 rows)
K and N should not be in the list, as they both have prerequisite nodes that will not be deleted, L and M respectively.
What's a single SQL query I can use in PostgreSQL 9.5 to get the complete list of nodes that will be deleted, given an initial set of root nodes to delete?
For what it's worth, my real tree table has around 100,000 rows.
(I have a few ideas that I haven't quite been able to pan out yet, like using a couple of nested anti-joins or [ab]using COUNT as a window function somehow, but I haven't cracked this yet and I'm hoping the community can come up with something more simple/elegant.)
