I'm trying to retrofit a transitive closure table into a system that currently uses adjacency lists using MySQL, based on the recipe given in SQL Antipatterns. However, I've run into a snag with the implementation of moving subtrees.
I've constructed an extreme simplification of the existing system and the closure table for the development effort, and will port this over to the real data once I've got it to work in a satisfactory manner. My tables are as follows:
CREATE TABLE `product` (
`id` BigInt( 255 ) AUTO_INCREMENT NOT NULL,
`parent` BigInt( 255 ) NOT NULL,
`description` VarChar( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY ( `id` )
)
CHARACTER SET = utf8
COLLATE = utf8_general_ci
ENGINE = InnoDB
CREATE TABLE `closure` (
`ancestor` BigInt( 255 ) NOT NULL,
`decendant` BigInt( 255 ) NOT NULL,
`depth` BigInt( 255 ) NOT NULL,
PRIMARY KEY ( `ancestor`,`decendant` )
)
CHARACTER SET = utf8
COLLATE = utf8_general_ci
ENGINE = InnoDB;
CREATE INDEX `ancestordepth` USING BTREE ON `closure`( `ancestor`, `depth` )
My test data is as follows:
Product
=======
1,0,"Test 1"
2,0,"Test 2"
3,0,"Test 3"
4,1,"Test 4"
5,1,"Test 5"
6,1,"Test 6"
7,4,"Test 7"
8,4,"Test 8"
9,4,"Test 9"
10,7,"Test 10"
Closure
=======
1,1,0
1,4,1
1,5,1
1,6,1
1,7,2
1,8,2
1,9,2
1,10,3
2,2,0
3,3,0
4,4,0
4,7,1
4,8,1
4,9,1
4,10,2
5,5,0
6,6,0
7,7,0
7,10,1
8,8,0
9,9,0
10,10,0
I've implemented triggers that will insert rows into the closure table on row creation in the product table and delete rows from the closure table when product rows are deleted and they work fine, but MySQL limitations are keeping me from getting the update case (where the parent in the product table changes) to work.
If I wanted to update node 4 so that it was the child of node 2 instead of node 1.
The SQL antipatterns book gives queries for doing this. The first one is intended to orphan the existing subtree by deleting the relevant rows from the closure table.
DELETE
FROM closure
WHERE decendant IN (
SELECT decendant
FROM closure
WHERE ancestor = 4
)
AND ancestor IN (
SELECT ancestor
FROM closure
WHERE decendant = 4
AND ancestor != decendant
)
But of course MySQL won't let you do this because of a deficiency in its design that doesn't let you alter any table you're using in a subquery.
I'm attempting to re-write the query into a self-join because then I can delete the rows from that. I've changed the original query to select instead of delete because that does work, and I can use it as a baseline for comparison. However my attempt to replicate the query with joins returns an empty set.
SELECT *
FROM closure AS a
JOIN closure AS b ON a.ancestor = b.ancestor AND a.decendant = b.decendant
JOIN closure AS c ON a.ancestor = c.ancestor AND a.decendant = c.decendant
WHERE b.ancestor = 4
AND c.decendant = 4
AND c.ancestor != c.decendant
I need to be able to keep the query that's executed relatively simple because it needs to go in a trigger. I also can't use temporary tables because the live SQL server is running in a cluster and we've had issues in the past where the use of temporary tables has broken replication. If anyone can help with re-writing the query into a form that will allow me to delete rows in MySQL I'd appreciate it.