使用SQL克隆数据库中表示的树结构

时间:2010-05-13 20:45:06

标签: sql sql-server sql-server-2005 tsql

给定一个表示分层树结构并具有三列的表

  1. ID(主键,非自动增量)
  2. ParentGroupID
  3. someValue中
  4. 我知道该分支中节点最低的节点,我想将其复制到一个新分支,该分支具有相同数量的父节点,也需要克隆。

    我正在尝试编写一个SQL INSERT INTO语句,该语句将使同一个main的每一行的副本成为新GroupID中的第一部分GroupID。

    示例开始表:

    ID | ParentGroupID | SomeValue
    ------------------------
    1  |      -1       |    a
    2  |       1       |    b
    3  |       2       |    c
    

    运行简单的INSERT INTO语句后的目标:

    ID | ParentGroupID | SomeValue
    ------------------------
    1  |      -1       |    a
    2  |       1       |    b
    3  |       2       |    c
    4  |      -1       |    a-cloned
    5  |       4       |    b-cloned
    6  |       5       |    c-cloned
    

    最终树结构

    +--a (1)
    |  +--b (2)
    |     +--c (3)
    |
    +--a-cloned (4)
    |  +--b-cloned (5)
    |     +--c-cloned (6)
    

    当这个演示数据显示时,ID并不总是间隔很好,所以我不能总是假设父亲的ID比父亲的行的当前ID小1。

    另外,我尝试在T-SQL(适用于Microsoft SQL Server 2005及更高版本)中执行此操作。

    这感觉就像一个经典的练习应该有一个纯粹的SQL答案,但我已经习惯了编程,我的思想在关系SQL中没有思考。

2 个答案:

答案 0 :(得分:3)

根据Quassnoi的文章Adjacency List vs Nested Sets: SQL Server中的查询尝试此操作:

WITH q AS
(
    SELECT  h.*, 1 AS level
    FROM    Table1 h
    WHERE   id = 3
    UNION ALL
    SELECT  hp.*, level + 1
    FROM    q
    JOIN    Table1 hp
    ON      hp.id = q.ParentGroupID
), q2 AS (
    SELECT
        ID,
        ParentGroupID,
        SomeValue,
        (SELECT MAX(level) FROM q) - level AS level
    FROM q
)
INSERT INTO table1
SELECT
    (SELECT MAX(ID) FROM Table1) + level + 1 AS ID,
    CASE WHEN level = 0 THEN -1
         ELSE (SELECT MAX(ID) FROM Table1) + level
    END AS ParentGroupID,
    SomeValue + '-cloned'
FROM    q2

运行测试数据时的结果:

ID  ParentGroupID  SomeValue  
1   -1             a          
2   1              b          
3   2              c          
4   -1             a-cloned
5   4              b-cloned
6   5              c-cloned

答案 1 :(得分:2)

确认ID是标识列,具有自动分配的值。 我正在做这个袖口,所以任何语法错误的应用程序。我希望这些意见能明确表达意图。

/* Find all ancestors for a given ID */
WITH Ancestors(ChildID, AncestorID) AS
(
  SELECT id AS ChildID, id As AncestorID
     FROM YourTable WHERE ParentGroupID=--1
  UNION ALL
  SELECT a.ChildID, d.ParentGroupID
     FROM  Ancestors AS a INNER JOIN
       YourTable d ON d.ID = a.AncestorID
     WHERE (b1.ParentGroupID <> -1))
),
/* Insert new rows for all ancestors of a given ID and save the results, so we have access to the new ID. we also have a column for the old ID. */
IDMap(ID, ParentGroupID, SomeValue, OldID) AS
{
    // insert, using identity column assigned ID, and save the old ID
   INSERT INTO YourTable SELECT d.ParentGroupID, d.SomeValue+"-cloned", d.ID FROM YourTable d
      INNER JOIN Ancestors a ON a.ChildID = d.ID 
      WHERE a.AncestorID=<the ID to clone>
}
/* Now update the parentID in the inserted data to the new ID */
UPDATE YourTable
SET ParentGroupID = map.ID
FROM YouTable t INNER JOIN (SELECT * FROM IDMap) map
  ON t.ParentGroupID=map.OldID