在《SQL反模式》中介绍了多种把树存到关系数据库中的方法,下面介绍一下其中闭包表的用法。
建表与插入数据(SQLite3)
CREATE TABLE NODE(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);
CREATE TABLE TREE_PATH(
anc INTEGER NOT NULL, --祖先节点
des INTEGER NOT NULL, --子孙节点
pc INTEGER NOT NULL, --是否为父子节点 1/0
PRIMARY KEY (anc,des)
);
--示例树
-- root(0)
-- |
-- A(1)
-- |___A1(2)
-- |___A2(3)
-- B(4)
-- |___B1(5)
-- |___B2(6)
-- |___C(7)
-- |___C1(8)
-- |___D(9)
-- |___E(10)
INSERT INTO NODE(id,name) VALUES (0,'ROOT');
INSERT INTO NODE(id,name) VALUES (1,'A');
INSERT INTO NODE(id,name) VALUES (2,'A1');
INSERT INTO NODE(id,name) VALUES (3,'A2');
INSERT INTO NODE(id,name) VALUES (4,'B');
INSERT INTO NODE(id,name) VALUES (5,'B1');
INSERT INTO NODE(id,name) VALUES (6,'B2');
INSERT INTO NODE(id,name) VALUES (7,'C');
INSERT INTO NODE(id,name) VALUES (8,'C1');
INSERT INTO NODE(id,name) VALUES (9,'D');
INSERT INTO NODE(id,name) VALUES (10,'E');
UPDATE sqlite_sequence SET seq=10 WHERE name='NODE';
INSERT INTO TREE_PATH(anc,des,pc) VALUES (0,1,1);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (0,2,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (0,3,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (0,4,1);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (0,5,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (0,6,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (0,7,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (0,8,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (0,9,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (0,10,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (1,1,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (2,2,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (3,3,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (4,4,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (5,5,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (6,6,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (7,7,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (8,8,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (9,9,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (10,10,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (1,2,1);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (1,3,1);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (4,5,1);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (4,6,1);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (4,7,1);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (4,8,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (4,9,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (4,10,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (7,8,1);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (7,9,1);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (7,10,0);
INSERT INTO TREE_PATH(anc,des,pc) VALUES (9,10,1);
删除子树
假设要删除子树#7
DELETE
FROM TREE_PATH
WHERE TREE_PATH.des IN (SELECT t.des FROM TREE_PATH t WHERE t.anc=7)
移动子树
假设我们要把子树#7从节点#4移动到节点#1
1.分离子树,删除子树节点与其祖先的关系
DELETE FROM
TREE_PATH
WHERE
des IN (SELECT d.des FROM TREE_PATH d WHERE d.anc=7)
AND
anc IN (SELECT a.anc FROM TREE_PATH a WHERE a.des=7 AND a.anc!=a.des)
2.将上一步分离出的子树用笛卡尔积嫁接到#1下
INSERT INTO TREE_PATH(anc,des,pc)
SELECT
super.anc,
sub.des,
CASE
WHEN
super.anc=1 AND sub.des=7 THEN 1
ELSE 0
END pc
FROM
TREE_PATH super
CROSS JOIN
TREE_PATH sub
WHERE
super.des=1 AND sub.anc=7