【问题标题】:cloning hierarchical data克隆分层数据
【发布时间】:2010-09-13 16:21:00
【问题描述】:

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

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。

【问题讨论】:

    标签: sql database-design postgresql


    【解决方案1】:

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

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

    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:%'
    

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

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

    注意事项(给感兴趣的人):

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

    【讨论】:

    • 我认为触发器必须更新层次结构的所有子记录。例如。如果我们将记录“root2”的 parent_id 更新为 1,我们还必须更新记录“root2sub1”和“root2sub2”。
    • 这是物化路径方式...确实很方便
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      @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完成的(第二个参数是新的父节点)。

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-20
          • 2012-09-10
          • 2020-05-17
          • 2011-06-23
          • 2011-07-29
          • 1970-01-01
          相关资源
          最近更新 更多