mysql - 使用 MySQL 移动传递闭包子树

标签 mysql transitive-closure-table

我正在尝试根据 SQL Antipatterns 中给出的配方,将传递闭包表改造到当前使用 MySQL 的邻接表的系统中。然而,我在移动子树的实现方面遇到了障碍。

我已经构建了现有系统的极端简化和开发工作的闭包表,一旦我让它以令人满意的方式工作,我会将其移植到真实数据。我的表格如下:

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` )

我的测试数据如下:

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

我已经实现了触发器,这些触发器将在产品表中创建行时将行插入到闭包表中,并在删除产品行时从闭包表中删除行,并且它们工作正常,但 MySQL 的限制使我无法获取更新情况(产品表中的父级发生变化)可以正常工作。

如果我想更新节点 4,使其成为节点 2 而不是节点 1 的子节点。

SQL 反模式一书提供了执行此操作的查询。第一个旨在通过从闭包表中删除相关行来孤立现有子树。

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
)

但是MySQL当然不会让你这样做,因为它的设计存在缺陷,不允许你更改在子查询中使用的任何表。

我正在尝试将查询重写为自联接,因为这样我就可以从中删除行。我已将原始查询更改为选择而不是删除,因为这确实有效,并且我可以将其用作比较的基线。但是,我尝试使用联接复制查询会返回一个空集。

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

我需要能够使执行的查询保持相对简单,因为它需要进入触发器。我也无法使用临时表,因为实时 SQL 服务器在集群中运行,并且我们过去遇到过使用临时表破坏复制的问题。如果有人可以帮助我将查询重写为允许我删除 MySQL 中的行的表单,我将不胜感激。

最佳答案

以下似乎是在 MySQL 中执行此操作的正确方法

    DELETE a
    FROM closure AS a
    JOIN closure AS b ON a.decendant = b.decendant
    LEFT JOIN closure AS c ON b.ancestor = c.ancestor and a.ancestor = c.decendant
    WHERE b.ancestor = OLD.id
    AND c.ancestor IS NULL; 

关于mysql - 使用 MySQL 移动传递闭包子树,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28961295/

相关文章:

mysql - 使用多重内部连接在 MySQL 中塑造数据集

未创建 MySQL UNIQUE KEY 和 FOREIGN KEY 同一列

php - Google 图表从一行中获取数据

php - 使用闭包表模型的 Doctrine2 分层数据

python - 从闭包表 SELECT 语句渲染树?

mysql - 如何从我的 galera 集群中删除节点?

PHP Foreach 语句问题。返回多行

mysql - 基于表的子查询更新表

mysql - 如何获取没有孙子和曾孙的子节点?关闭表模式

python - Django ORM 和闭包表