给定父/子键表,我们如何递归地将结构的副本插入另一个表?

时间:2016-04-26 18:09:41

标签: sql tsql recursion

我有一个递归CTE,它给出了一组父子键的列表,如下所示,让我们在一个名为[#relationtree]的临时表中说明:

Parent | Child
--------------
1      | 3
3      | 5
5      | 6
5      | 9

我想将这些关系的副本创建到一个表中,例如,使用以下结构:

CREATE TABLE [dbo].[Relations]
(
  [Id] int identity(1,1)
  [ParentId] int
)

如何插入上述记录,但递归获取先前插入的标识值,以便能够为我插入的子项的每个副本插入该值作为ParentId列?

我期待在[dbo]中结束这个。[关系](鉴于我们当前的种子价值,比如50)

Id | ParentId
-------------
... other rows present before this query ...
50 | NULL
51 | 50
52 | 51
53 | 51

我不确定scope_identity在这种情况下是否可以工作,或者创建一个包含新ID列表并手动插入标识列的新临时表是正确的方法吗?

我可以编写一个游标/循环来执行此操作,但必须有一种很好的方法来做一些递归选择魔法!

2 个答案:

答案 0 :(得分:1)

由于您尝试将树放入表格的某个部分,因此无论如何您似乎都需要使用SET IDENTITY_INSERT ON。您需要确保新树有空间。在这种情况下,我假设49是表中当前的最大id,因此我们不需要担心超出表中后面的树。

您需要能够将ID从旧树映射到新树。除非有一些围绕id的规则,否则确切的映射应该是无关紧要的,只要它是准确的,所以在这种情况下,我只是做这样的事情:

SET IDENTITY_INSERT dbo.Relations ON

;WITH CTE_MappedIDs AS
(
    SELECT
        old_id,
        ROW_NUMBER() OVER(ORDER BY old_id) + 49 AS new_id
    FROM
    (
        SELECT DISTINCT parent AS old_id FROM #relationtree
        UNION
        SELECT DISTINCT child AS old_id FROM #relationtree
    ) SQ
)
INSERT INTO dbo.Relations (Id, ParentId)
SELECT
    CID.new_id,
    PID.new_id
FROM
    #relationtree RT
INNER JOIN CTE_MappedIDs PID ON PID.old_id = RT.parent
INNER JOIN CTE_MappedIDs CID ON CID.old_id = RT.parent
-- We need to also add the root node
UNION ALL
SELECT
    NID.new_id,
    NULL
FROM
    #relationtree RT2
INNER JOIN CTE_MappedIDs NID ON NID.old_id = RT2.parent
WHERE
    RT2.parent NOT IN (SELECT DISTINCT child FROM #relationtree)

SET IDENTITY_INSERT dbo.Relations OFF

我没有对此进行过测试,但如果它没有按预期工作,那么希望它会指向正确的方向。

答案 1 :(得分:0)

我知道你已经有了一个合适的答案,但我认为你可以更简单地完成同样的事情(并不是Tom H的回答有任何错误)使用LAG函数来检查上一行,假设您有SQL Server 2012或更高版本。

设定:

CREATE TABLE #relationtree (
    Parent INT,
    Child INT
)

CREATE TABLE #relations (
    Id INT IDENTITY(1,1),
    ParentId INT
)

INSERT INTO #relationtree (Parent, Child) VALUES(1,3), (3,5), (5,6), (5,9)

INSERT INTO #relations (ParentId) values(1), (3), (5)

解决方案:

DECLARE @offset INT = IDENT_CURRENT('#relations')

;WITH relationtreeids AS (
    SELECT *,
           ROW_NUMBER() OVER(ORDER BY Parent, Child) - 2 AS UnmodifiedParentId -- Simulate an identity field
    FROM #relationtree
)
INSERT INTO #relations
-- The LAG window function allows you to inspect the previous row
SELECT  CASE WHEN LAG(Parent) OVER(ORDER BY Parent) IS NULL 
                THEN NULL
             WHEN LAG(Parent) OVER(ORDER BY Parent) = Parent 
                THEN UnmodifiedParentId + @offset ELSE UnmodifiedParentId + @offset + 1 
        END AS ParentId
FROM relationtreeids

输出:

Id  ParentId
1   1
2   3
3   5
4   NULL
5   4
6   5
7   5