克隆分层数据

时间:2008-09-27 08:26:43

标签: sql database-design postgresql

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

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 个答案:

答案 0 :(得分:6)

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

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

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字段。这是一项微不足道的工作,因为你只需要获取父记录的upchain并附加当前记录的id。使用LIKE可以轻松识别所有子记录,以检查上行链中包含起始字符串的记录。

当您来阅读数据时,您正在有效地做的是为 big 保存交换一些额外的写入活动。

当你想在树中选择一个完整的分支时,它是微不足道的。假设您想要节点1下的分支。节点1有一个上行链路'1:',因此您知道该节点下树的分支中的任何节点必须具有从'1:...'开始的上行链路。所以你只需这样做:

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

非常快(当然索引上链字段)。作为奖励,它还使许多活动变得非常简单,例如找到部分树,树内等级等。

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

备注(对任何感兴趣的人):

  • 我没有给出SQL代码的一步一步,但是一旦得到原理,它实现起来非常简单。我不是一个优秀的程序员,所以我从经验中说话。
  • 如果表中已有数据,则需要进行一次更新,以便最初同步上行链路。同样,这并不困难,因为代码与触发器中的UPDATE代码非常相似。
  • 这种技术也是识别循环引用的好方法,否则这些引用可能很难发现。

答案 1 :(得分:3)

Joe Celko的方法类似于njreed's answer但更通用的方法可以在这里找到:

答案 2 :(得分: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完成(第二个参数是新的父节点)。

答案 3 :(得分:0)

这听起来像Joe Celko的“SQL For Smarties”练习......

我没有方便的副本,但我认为如果这是你需要解决的问题,这本书会对你有所帮助。