SqLite:如何删除高度恒定的树中从根到叶子的节点?

问题描述 投票:0回答:1

我们在 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

但后来我了解到,

RETURNING
只能在顶级
DELETE
INSERT
UPDATE
语句
中使用,所以这似乎是一个死胡同。

我们目前使用触发器来实现此功能,但是(据我所知)如果我们需要收集已删除的叶节点,则该方法不起作用。

sql sqlite
1个回答
0
投票

也许考虑结合使用

  1. 外键可使用
    ON DELETE CASCADE
  2. 自动删除子项
  3. 用于收集删除 ID 和 AFTER DELETE TRIGGER 的表,以及
  4. AFTER DELETE 触发器自动将插入内容填充到集合表中。

这是一个演示:-

/* 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 种类型都有行
  • 据报道,根节点 1、3、5、7 和 9 已被删除(正如预期,删除是删除奇数编号的 id)。

结果4

这表明第一行输出是针对 root_nodes id 2 的,没有针对 root_nodes id 1(或 3、5、7 或 9)的行

© www.soinside.com 2019 - 2024. All rights reserved.