在 SQL 中复制父子结构

nav*_*tor 6 sql sql-server stored-procedures recursive-query sql-server-2008

我有一张桌子MODELS,有几个ITEMS可以属于。该ITEMS表是一个分层表,PARENT列上有一个自联接。根级别的项目将NullPARENT. 项目可以深入任何级别。

create table MODELS (
   MODELID              int                  identity,
   MODELNAME            nvarchar(200)         not null,
   constraint PK_MODELS primary key (MODELID)
)
go

create table ITEMS (
   ITEMID               int                  identity,
   MODELID              int                  not null,
   PARENT               int                  null,
   ITEMNUM              nvarchar(20)         not null,
   constraint PK_ITEMS primary key (ITEMID)
)
go

alter table ITEMS
   add constraint FK_ITEMS_MODEL foreign key (MODELID)
      references MODELS (MODELID)
go

alter table ITEMS
   add constraint FK_ITEMS_ITEMS foreign key (PARENT)
      references ITEMS (ITEMID)
go
Run Code Online (Sandbox Code Playgroud)

我希望创建存储过程以将MODELS表中的一行复制到新行中,并将整个结构也复制进去ITEMS

例如,如果我有以下内容ITEMS

ITEMID    MODELID    PARENT    ITEMNUM
  1          1        Null       A
  2          1        Null       B
  3          1        Null       C
  4          1          1        A.A
  5          1          2        B.B
  6          1          4        A.A.A
  7          1          4        A.A.B
  8          1          3        C.A
  9          1          3        C.B
 10          1          9        C.B.A
Run Code Online (Sandbox Code Playgroud)

我想创建新的模型行和 10 个项目的副本,如下所示:

ITEMID    MODELID    PARENT    ITEMNUM
  11          2       Null       A
  12          2       Null       B
  13          2       Null       C
  14          2        11        A.A
  15          2        12        B.B
  16          2        14        A.A.A
  17          2        14        A.A.B
  18          2        13        C.A
  19          2        13        C.B
  20          2        19        C.B.A
Run Code Online (Sandbox Code Playgroud)

我将MODELID要复制的作为参数传递给存储过程。棘手的部分是PARENT正确设置列。我认为这需要递归完成。

有什么建议?

Vla*_*nov 7

此处描述的解决方案将在多用户环境中正常工作。您不需要锁定整个表。您不需要禁用自引用外键。你不需要递归。

(ab)MERGEOUTPUT子句一起使用。

MERGE可以INSERTUPDATEDELETE行。在我们的例子中,我们只需要INSERT. 1=0 始终为假,因此NOT MATCHED BY TARGET始终执行该部分。通常,可能还有其他分支,请参阅文档。WHEN MATCHED通常用于UPDATE; WHEN NOT MATCHED BY SOURCE通常用于DELETE,但我们这里不需要它们。

这种复杂的形式MERGE相当于 simple INSERT,但与 simple 不同的是,INSERT它的OUTPUT子句允许引用我们需要的列。它允许从源表和目标表中检索列,从而保存旧 ID 和新 ID 之间的映射。

样本数据

DECLARE @Items TABLE (
   ITEMID               int                  identity,
   MODELID              int                  not null,
   PARENT               int                  null,
   ITEMNUM              nvarchar(20)         not null
)

INSERT INTO @Items (MODELID, PARENT, ITEMNUM) VALUES
(1, Null, 'A'),
(1, Null, 'B'),
(1, Null, 'C'),
(1,   1 , 'A.A'),
(1,   2 , 'B.B'),
(1,   4 , 'A.A.A'),
(1,   4 , 'A.A.B'),
(1,   3 , 'C.A'),
(1,   3 , 'C.B'),
(1,   9 , 'C.B.A');
Run Code Online (Sandbox Code Playgroud)

我省略了复制Model行的代码。最终您将拥有原始模型和新模型的 ID。

DECLARE @SrcModelID int = 1;
DECLARE @DstModelID int = 2;
Run Code Online (Sandbox Code Playgroud)

声明一个表变量(或临时表)来保存新旧项目 ID 之间的映射。

DECLARE @T TABLE(OldItemID int, NewItemID int);
Run Code Online (Sandbox Code Playgroud)

制作一份Items记住表变量中 ID 的映射并保留旧PARENT值的副本。

MERGE INTO @Items
USING
(
    SELECT ITEMID, PARENT, ITEMNUM
    FROM @Items AS I
    WHERE MODELID = @SrcModelID
) AS Src
ON 1 = 0
WHEN NOT MATCHED BY TARGET THEN
INSERT (MODELID, PARENT, ITEMNUM)
VALUES
    (@DstModelID
    ,Src.PARENT
    ,Src.ITEMNUM)
OUTPUT Src.ITEMID AS OldItemID, inserted.ITEMID AS NewItemID
INTO @T(OldItemID, NewItemID)
;
Run Code Online (Sandbox Code Playgroud)

PARENT用新 ID更新旧值

WITH
CTE
AS
(
    SELECT I.ITEMID, I.PARENT, T.NewItemID
    FROM
        @Items AS I
        INNER JOIN @T AS T ON T.OldItemID = I.PARENT
    WHERE I.MODELID = @DstModelID
)
UPDATE CTE
SET PARENT = NewItemID
;
Run Code Online (Sandbox Code Playgroud)

检查结果

SELECT * FROM @Items;
Run Code Online (Sandbox Code Playgroud)

  • `OUTPUT Src.ITEMID AS OldItemID, insert.ITEMID AS NewItemID` 来获取旧 ID 和新 ID - 这个技巧救了我的命。+1,谢谢。 (2认同)

dci*_*lak 0

您无需递归即可完成。但您可能需要先锁定表才能确保它正常工作。

insert into items (Modelid, Parent, ITEMNUM)
 select  2 as modelId, 
          MAP.currId as Parent,
          MO.ITEMNUM  
  from (
        ( select * from items where MODELID = 1) MO
  left join 
        ( select IDENT_CURRENT('ITEMS') + ROW_NUMBER() OVER(ORDER BY itemid ) currID , 
                 i.ItemID 
          from ITEMS i
         where modelid = 1 ) MAP
  on MO.Parent= MAP.ItemID 
   )  ORDER BY MO.ItemID
Run Code Online (Sandbox Code Playgroud)

其背后的想法是,我们从 ITEM 表中的原始模型中选择所有行,并为它们生成假 ID。

假身份证是:

Row 1 = current identity + 1,
Row 2 = current identity + 2, 
etc.
Run Code Online (Sandbox Code Playgroud)

之后我们有映射:oldid -> newid

然后我们将原始模型插入到 ITEM 表中,但我们用映射中的记录替换 Parent。

我看到的问题是,当我们插入行时,父级的某些 ItemID 可能仍然不存在(即,我们插入 ItemID 为 20 但其父级为 21 的行)。为此,我们可能需要在执行此插入时禁用对 Parent 的约束。之后我们应该再次启用它。数据当然是正确的。