2

让我们假设我有一个自引用分层表构建像这样的经典方式:

CREATE TABLE test
(name text,id serial primary key,parent_id integer
references test);

insert into test (name,id,parent_id) values
('root1',1,NULL),('root2',2,NULL),('root1sub1',3,1),('root1sub2',4,1),('root
2sub1',5,2),('root2sub2',6,2);

testdb=# select * from test;

   name    | id | parent_id
-----------+----+-----------
 root1     |  1 |  
 root2     |  2 |  
 root1sub1 |  3 |         1
 root1sub2 |  4 |         1
 root2sub1 |  5 |         2
 root2sub2 |  6 |         2

我现在需要的是一个函数(最好是纯 sql),它将获取测试记录的 id 并克隆所有附加记录(包括给定的记录)。克隆的记录当然需要有新的 id。例如,期望的结果是这样的:

Select * from cloningfunction(2);

   name    | id | parent_id    
-----------+----+-----------
 root2     |  7 |  
 root2sub1 |  8 |         7
 root2sub2 |  9 |         7

任何指针?我使用的是 PostgreSQL 8.3。

4

4 回答 4

6

递归地提取这个结果是很棘手的(尽管可能)。但是,它通常不是很有效,并且有更好的方法来解决这个问题。

基本上,您用一个额外的列来扩充表格,该列将树追踪到顶部——我将其称为“上行链”。它只是一个长字符串,看起来像这样:

name | id | parent_id | upchain
root1 | 1 | NULL | 1:
root2 | 2 | NULL | 2:
root1sub1 | 3 | 1 | 1:3:
root1sub2 | 4 | 1 | 1:4:
root2sub1 | 5 | 2 | 2:5:
root2sub2 | 6 | 2 | 2:6:
root1sub1sub1 | 7 | 3 | 1:3:7:

使用表上的触发器可以很容易地保持该字段的更新。(为术语道歉,但我一直使用 SQL Server 这样做)。每次添加或删除记录,或更新 parent_id 字段时,您只需要更新树的那部分上的 upchain 字段。这是一项微不足道的工作,因为您只需获取父记录的上行链并附加当前记录的 id。使用 LIKE 可以轻松识别所有子记录,以检查其上游链中具有起始字符串的记录。

当您读取数据时,您正在有效地做的是用一些额外的写入活动来节省大量资金。

当您想在树中选择一个完整的分支时,这很简单。假设您想要节点 1 下的分支。节点 1 有一个上行链“1:”,因此您知道该节点下的树分支中的任何节点都必须有一个以“1:...”开头的上行链。所以你只需这样做:

SELECT *
FROM table
WHERE upchain LIKE '1:%'

这是非常快的(当然索引 upchain 字段)。作为奖励,它还使许多活动变得非常简单,例如查找部分树、树中的关卡等。

我在跟踪大型员工报告层次结构的应用程序中使用了它,但您几乎可以将它用于任何树结构(部件分解等)

备注(有兴趣的朋友):

  • 我没有给出一步一步的SQL代码,但是一旦你掌握了原理,实现起来就很简单了。我不是一个伟大的程序员,所以我是根据经验说话的。
  • 如果表中已有数据,则需要进行一次更新以使上行链最初同步。同样,这并不难,因为代码与触发器中的 UPDATE 代码非常相似。
  • 这种技术也是识别循环引用的好方法,否则很难发现。
于 2008-09-27T09:37:04.857 回答
3

Joe Celko 的方法类似于njreed 的答案,但更通用,可以在这里找到:

于 2008-09-27T12:18:27.543 回答
1

@Maximilian:您说得对,我们忘记了您的实际要求。递归存储过程怎么样?我不确定这在 PostgreSQL 中是否可行,但这是一个有效的 SQL Server 版本:

CREATE PROCEDURE CloneNode
    @to_clone_id int, @parent_id int
AS
    SET NOCOUNT ON
    DECLARE @new_node_id int, @child_id int

    INSERT INTO test (name, parent_id) 
        SELECT name, @parent_id FROM test WHERE id = @to_clone_id
    SET @new_node_id = @@IDENTITY

    DECLARE @children_cursor CURSOR
    SET @children_cursor = CURSOR FOR 
        SELECT id FROM test WHERE parent_id = @to_clone_id

    OPEN @children_cursor
    FETCH NEXT FROM @children_cursor INTO @child_id
    WHILE @@FETCH_STATUS = 0
    BEGIN
        EXECUTE CloneNode @child_id, @new_node_id
        FETCH NEXT FROM @children_cursor INTO @child_id
    END
    CLOSE @children_cursor
    DEALLOCATE @children_cursor

您的示例由EXECUTE CloneNode 2, null(第二个参数是新的父节点)完成。

于 2008-09-28T19:41:40.233 回答
0

这听起来像是 Joe Celko 的“SQL For Smarties”中的一个练习……

我手头没有我的副本,但如果这是你需要解决的问题,我认为这本书会对你有很大帮助。

于 2008-09-27T10:31:28.923 回答