提问人:Peter Jankuliak 提问时间:11/7/2023 更新时间:11/7/2023 访问量:48
SqLite:如何去除一棵树中从根到叶的节点,高度恒定?
SqLite: How to remove nodes from root to leafs in a tree with constant height?
问:
我们在 SqLite 中有三个表表示的树结构:
CREATE TABLE IF NOT EXISTS "root_nodes" (
"id" INTEGER NOT NULL,
-- other stuff
PRIMARY KEY("id")
);
CREATE TABLE IF NOT EXISTS "inner_nodes" (
"id" INTEGER NOT NULL,
"parent" INTEGER NOT NULL,
-- other stuff
PRIMARY KEY("id")
);
CREATE TABLE IF NOT EXISTS "leaf_nodes" (
"id" INTEGER NOT NULL,
"parent" INTEGER NOT NULL,
-- other stuff
PRIMARY KEY("id")
);
例如,在本例中,树的高度为 3(根节点、内部节点和叶节点层)。但在实践中,内部节点层可能有几个但数量不变的。
给定一个根节点,我需要删除其所有后代内部节点和叶节点,并返回已删除的叶节点。理想情况下,在单个 SQL 语句中。
我的第一次尝试是做嵌套的DELETE FROM语句,如下所示:
DELETE FROM leaf_nodes WHERE parent IN (
DELETE FROM inner_nodes WHERE parent IN (
DELETE FROM root_nodes WHERE id = ? RETURNING id
) RETURNING id
) RETURNING id
但后来我了解到,这仅在顶级 DELETE、
INSERT
和 UPDATE
语句中可用,所以这似乎是一条死胡同。RETURNING
我们目前使用触发器实现了这一点,但是(AFAIK)如果我们需要收集已删除的叶节点,则不起作用。
答:
1赞
MikeT
11/7/2023
#1
也许可以考虑结合使用
- 外键,用于使用 自动删除子项,以及
ON DELETE CASCADE
- 用于收集删除 ID 的表,以及
- 3 个触发器,用于自动填充插入到收集表中。
AFTER DELETE
这将允许在需要时获取已删除的 ID
这是一个演示:-
/* just in case cleanup */
DROP TABLE IF EXISTS leaf_nodes;
DROP TABLE IF EXISTS inner_nodes;
DROP TABLE IF EXISTS root_nodes;
/* Make sure that Foreign Key handling is enabled */
PRAGMA foreign_keys = on;
/* The original tables */
CREATE TABLE IF NOT EXISTS "root_nodes" (
"id" INTEGER NOT NULL,
-- other stuff
PRIMARY KEY("id")
);
CREATE TABLE IF NOT EXISTS "inner_nodes" (
"id" INTEGER NOT NULL,
"parent" INTEGER NOT NULL /*>>>>>>>>>> ADDED >>>>>>>>>>*/ REFERENCES root_nodes(id) ON DELETE CASCADE ON UPDATE CASCADE,
-- other stuff
PRIMARY KEY("id")
);
CREATE TABLE IF NOT EXISTS "leaf_nodes" (
"id" INTEGER NOT NULL,
"parent" INTEGER NOT NULL /*>>>>>>>>>> ADDED >>>>>>>>>>*/ REFERENCES inner_nodes(id) ON DELETE CASCADE ON UPDATE CASCADE,
-- other stuff
PRIMARY KEY("id")
);
/*<<<<<<<<<< NEW TABLE FOR THE COLLECTION OF DELETED IDs >>>>>>>>>>*/
CREATE TABLE IF NOT EXISTS deletion_log (timestamp INTEGER DEFAULT (strftime('%s','now')), type TEXT, deletedId INTEGER);
/*<<<<<<<<< 3 AFTER DELETE TRIGGERS >>>>>>>>>>*/
CREATE TRIGGER IF NOT EXISTS leaf_nodes_delete_trigger AFTER DELETE ON leaf_nodes
BEGIN
INSERT INTO deletion_log (type,deletedid) VALUES('3LN',old.id);
END
;
CREATE TRIGGER IF NOT EXISTS inner_nodes_delete_trigger AFTER DELETE ON inner_nodes
BEGIN
INSERT INTO deletion_log (type,deletedid) VALUES('2IN',old.id);
END
;
CREATE TRIGGER IF NOT EXISTS root_nodes_delete_trigger AFTER DELETE ON root_nodes
BEGIN
INSERT INTO deletion_log (type,deletedid) VALUES('1RN',old.id);
END
;
/* DEMONSTRATE THE ABOVE */
/* 1 Load some root_nodes */
WITH cte_count(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM cte_count LIMIT 10)
INSERT OR IGNORE INTO root_nodes SELECT * FROM cte_count;
/* 2 Load some inner_nodes (approx 10 per root) */
WITH cte_count(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM cte_count LIMIT 100)
INSERT OR IGNORE INTO inner_nodes SELECT *,(abs(random() % 10)) + 1 FROM cte_count;
/* 3 Load some leaf_nodes again approx 10 fir inner node */
WITH cte_count(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM cte_count LIMIT 1000)
INSERT OR IGNORE INTO leaf_nodes SELECT *,(abs(random() % 100)) + 1 FROM cte_count;
/* Show the data after the initial load */
SELECT * FROM deletion_log ORDER BY type ASC; /*<<<<<<<<<< RESULT 1*/
SELECT * FROM root_nodes
JOIN inner_nodes ON inner_nodes.parent = root_nodes.id
JOIN leaf_nodes ON leaf_nodes.parent = inner_nodes.id
ORDER BY root_nodes.id,inner_nodes.id,leaf_nodes.id /*<<<<<<<<<< RESULT 2*/
;
/*!!!!!!!!!! DELETE SOME ROOT NODES !!!!!!!!!!*/
DELETE FROM root_nodes WHERE (id % 2) = 1;
/* Show the data after the deletions */
SELECT * FROM deletion_log ORDER BY type ASC; /*<<<<<<<<<< RESULT 3*/
SELECT * FROM root_nodes
JOIN inner_nodes ON inner_nodes.parent = root_nodes.id
JOIN leaf_nodes ON leaf_nodes.parent = inner_nodes.id
ORDER BY root_nodes.id,inner_nodes.id,leaf_nodes.id /*<<<<<<<<<< RESULT 4 */
;
/* Cleanup after demo */
DROP TABLE IF EXISTS leaf_nodes;
DROP TABLE IF EXISTS inner_nodes;
DROP TABLE IF EXISTS root_nodes;
DROP TABLE IF EXISTS deletion_log;
运行结果
- 请注意,由于数据是随机生成的,因此每次运行的结果会有所不同
结果 1
- 正如预期的那样,delete_log表是空的,即尚未进行任何删除。
结果 2
- 除了显然正在加载一些数据之外,不是很有用(但请注意,第一个 ID 是 root_nodes 的 ID,并且存在 1)
结果 3
大约60行后
- 显然已根据演示进行排序(root_nodes ID,inner_nodes ID,然后是 leaf_node ID)
- 可以看出,所有 3 种类型都有行
- 据报道,root_nodes 1、3、5、7 和 9 已被删除(正如预期的那样,删除是为了删除奇数编号的 ID)。
结果 4
所有这些都表明,第一行输出是针对 root_nodes id 2 的,没有针对 root_nodes id 1(或 3、5、7 或 9)的行
评论