【问题标题】:TSQL Parent > Child > Sub-Child duplication without cursorTSQL Parent > Child > Sub-Child 没有游标的重复
【发布时间】:2011-04-06 12:38:23
【问题描述】:

我正在创建一个 SQL 2008 R2 存储过程来复制一行及其所有子行。

这是一个包含父、子和子子的 3 层设置 给定父母的 ID,我需要创建一个副本。

我已经使用fast_forward cursor 解决了这个问题。

我知道我也可以使用 while 循环遍历行来做到这一点,但我认为这不会比这种游标方法更快。你有什么想法?

有没有更好的方法在不使用光标的情况下完成这项任务?

编辑:我考虑的另一个选项是创建一个临时表,其中包含 TBLACStages 记录的旧/新 PKID。

TBLACStages 可能有 1 到 20 个对应的行(并且 TBLACUpgrade 可能每个 TBLACStages 行有 3 行)

CREATE PROCEDURE [dbo].[spDuplicateACUnit]
@pACUnitID bigint = 0 
AS BEGIN
SET NOCOUNT ON;

DECLARE @NewACUnitID bigint = 0

INSERT INTO TBLACUnits ([col1] ,[col2] ,[...] ,[coln]) SELECT [col1] ,[col2] ,[...] ,[coln] FROM TBLACUnits WHERE ACUnitID = @pACUnitID

SELECT @NewACUnitID = SCOPE_IDENTITY()

DECLARE @ACStageID bigint = 0 
    DECLARE @NewACStageID bigint = 0

DECLARE @ACUnitCursor CURSOR

SET @ACUnitCursor = CURSOR LOCAL FAST_FORWARD FOR SELECT ACStageID FROM TBLACStages WHERE TBLACStages.ACUnitID = @pACUnitID

OPEN @ACUnitCursor

FETCH NEXT FROM @ACUnitCursor INTO @ACStageID

WHILE @@FETCH_STATUS = 0 
BEGIN

INSERT INTO TBLACStages ([ACUnitID] ,[col1] ,[col2] ,[...] ,[coln]) SELECT @NewACUnitID ,[col1] ,[col2] ,[...] ,[coln] FROM TBLACStages WHERE TBLACStages.ACStageID = @ACStageID

SELECT @NewACStageID = SCOPE_IDENTITY()

INSERT INTO TBLACUpgrade ([ACStageID] ,[col1] ,[col2] ,[...] ,[coln]) SELECT @NewACStageID ,[col1] ,[col2] ,[...] ,[coln] FROM TBLACUpgrade WHERE TBLACUpgrade.[ACStageID] = @ACStageID

FETCH NEXT FROM @ACUnitCursor INTO @ACStageID 
END

CLOSE @ACUnitCursor DEALLOCATE @ACUnitCursor

END

GO

【问题讨论】:

  • 对不起,格式看起来很糟糕,复制/粘贴到 SO 中并不容易。
  • 除了 ACStageID 之外,TBLACStages 中是否有任何候选键(唯一列)?
  • 不,唯一保证唯一的列是 PK,即 ACStageID
  • 复合键怎么样(多列组合时是唯一的)
  • 除非与 PK 结合使用,否则没有什么是绝对独一无二的。

标签: sql-server tsql cursors fast-forward


【解决方案1】:

我看到这篇文章的复杂性几乎让我喘不过气来,但它确实看起来不错。当我需要克隆或复制带有子或孙子的表时,我只需在表中添加一个名为PreCloneControl 的新列,然后在新表的子查询中引用此字段以快速轻松地查找旧的父数据。简单。但是,如果您无权向表中添加列,则通常可以使用快速破解。一个示例是 Last Modified User 字段,通常是 100 个左右字符的 nvarchar。通常我们无论如何都需要更新这个字段,所以把你的旧控制号码放在那里然后离开。完成后请记住对 Last Modified User 字段执行快速更新。这是示例,我正在使用临时表进行测试,但您应该使用真实表。

Declare @OldControl int = 123456

Declare @TT1 Table
(
TT1Control [int] IDENTITY(1,1) NOT NULL,
SomeData nvarchar(20)
)

insert into @TT1
(
SomeData
)
Select SomeDate from LiveTable where LTControl = @OldControl


Declare @NewControl int = SCOPE_IDENTITY()


Declare @TempTT2 Table
(
TT2Control int IDENTITY(1,1) NOT NULL,
TT2TT1FKControl int,
TT2ChildData nvarchar(20),
TT2ModUser nvarchar(100)
)

insert into @TempTT2
(
TT2TT1FKControl,TT2ChildData,TT2ModUser
)
Select @NewControl, TT2ChildData, Cast(TT2Control as nvarchar(100))
From TT2 where TT2TT1FKControl = @OldControl

Select * from @TempTT2

Declare @TT3 Table
(
TT3Control int IDENTITY(1,1) NOT NULL,
TT3TT2FKControl int,
TT3GrandChildData nvarchar(50),
TT3OldTT2Control int
)

Insert Into @TT3
(
TT3TT2FKControl,TT3GrandChildData,TT3OldTT2Control
)
Select t.TT2Control, BookItemItemNumber,TT2.TT2Control
From TT2 inner join GrandChildTable on TT2Control = GCTFKControl
       ,@TempTT2 as t
Where
TT2TT1FKControl = @OldControl
and  t.TT2ModUser = Cast(TT2Control as nvarchar(100))

Select * From @TT3

Update @TempTT2 set TT2ModUser = 'UserName' Where TT2TT1FKControl = @NewControl

Select * from @TempTT2

【讨论】:

    【解决方案2】:

    好的,这是我根据 Quassnoi 的解决方案提出的MERGE。如果没有CURSOR,我应该可以正常工作

    DECLARE @parent TABLE (oid BIGINT, nid BIGINT)
    DECLARE @child TABLE (oid BIGINT, nid BIGINT)
    
    MERGE
    INTO    TBLACUnits T
    USING   (SELECT [col1], [...], [coln] FROM TBLACUnits WHERE ID = @pID) S
    
    ON      1 = 0
    WHEN NOT MATCHED THEN
    INSERT  ([ACUnitID]
       ,[col1]
       ,[...]
       ,[coln])
    VALUES  (S.[ACUnitID]
       ,S.[col1]
       ,S.[...]
       ,S.[coln]])
    OUTPUT  S.ACUnitID, INSERTED.ACUnitID
    INTO    @parent;
    
    MERGE
    INTO    TBLACStages T
    USING   (
      SELECT  tt.[nid] 
                           ,TBLACStages.[col1]
                           ,TBLACStages.[...]
                           ,TBLACStages.[coln]
      FROM TBLACStages
      JOIN @parent tt ON tt.oid = TBLACStages.ACUnitID
      ) S
    ON      1 = 0
    WHEN NOT MATCHED THEN
    INSERT  ([ACUnitID]
       ,[col1]
       ,[...]
       ,[coln])
    VALUES  ([nid]
       ,[col1]
       ,[...]
       ,[coln])
    OUTPUT  S.[ACStageID], INSERTED.[ACStageID]
    INTO    @child;
    
    INSERT INTO TBLACUpgrade 
    ([ACStageID]
       ,[col1]
       ,[...]
       ,[coln])
    SELECT  c.[nid]
       ,TBLACUpgrade.[col1]
       ,TBLACUpgrade.[...]
       ,TBLACUpgrade.[coln]
    FROM    @child c
    JOIN    TBLACUpgrade
     ON      TBLACUpgrade.ACStageID  = c.oid
    

    【讨论】:

      【解决方案3】:

      这应该给你的想法:

      CREATE TABLE t_parent (id INT NOT NULL PRIMARY KEY IDENTITY, value VARCHAR(100))
      CREATE TABLE t_child (id INT NOT NULL PRIMARY KEY IDENTITY, parent INT NOT NULL, value VARCHAR(100))
      CREATE TABLE t_grandchild (id INT NOT NULL PRIMARY KEY IDENTITY, child INT NOT NULL, value VARCHAR(100))
      
      INSERT
      INTO    t_parent (value)
      VALUES  ('Parent 1')
      
      INSERT
      INTO    t_parent (value)
      VALUES  ('Parent 2')
      
      INSERT
      INTO    t_child (parent, value)
      VALUES  (1, 'Child 2')
      
      INSERT
      INTO    t_child (parent, value)
      VALUES  (2, 'Child 2')
      
      INSERT
      INTO    t_grandchild (child, value)
      VALUES  (1, 'Grandchild 1')
      
      INSERT
      INTO    t_grandchild (child, value)
      VALUES  (1, 'Grandchild 2')
      
      INSERT
      INTO    t_grandchild (child, value)
      VALUES  (2, 'Grandchild 3')
      
      DECLARE @parent TABLE (oid INT, nid INT)
      DECLARE @child TABLE (oid INT, nid INT)
      
      MERGE
      INTO    t_parent
      USING   (
              SELECT  id, value
              FROM    t_parent
              ) p
      ON      1 = 0
      WHEN NOT MATCHED THEN
      INSERT  (value)
      VALUES  (value)
      OUTPUT  p.id, INSERTED.id
      INTO    @parent;
      SELECT  *
      FROM    @parent
      MERGE
      INTO    t_child
      USING   (
              SELECT  c.id, p.nid, c.value
              FROM    @parent p
              JOIN    t_child c
              ON      c.parent = p.oid
              ) c
      ON      1 = 0
      WHEN NOT MATCHED THEN
      INSERT  (parent, value)
      VALUES  (nid, value)
      OUTPUT  c.id, INSERTED.id
      INTO    @child;
      SELECT  *
      FROM    @child;
      INSERT
      INTO    t_grandchild (child, value)
      SELECT  c.nid, gc.value
      FROM    @child c
      JOIN    t_grandchild gc
      ON      gc.child = c.oid
      SELECT  *
      FROM    t_grandchild
      

      【讨论】:

      • 您认为三个嵌套的MERGE 语句会比CURSOR 最多20 行更快吗?特别是现在考虑到两个读入临时表(父母和孩子)而不是我的解决方案中的一个(孩子)的开销?
      • @Matthew:MERGE 语句实际上只是INSERTSINSERT 不允许返回旧的id,所以你必须使用MERGE)。
      • @Matthew:您也可以在我的解决方案中重用临时表。针对20-record 表的查询肯定比针对单独记录的20 查询要快。如果可能,您应该始终使用基于集合的解决方案。
      • 是的,我看到您使用 MERGE 创建了 INSERT 只是因为您希望他们使用 OUTPUT 一些东西。但是,您的解决方案使用MERGE 声明并读入(使用OUTPUT)两个临时表,然后JOIN 以方便下一次合并。当记录集和我一样小时,我不相信这是比CURSOR 更快的解决方案。
      • @Matthew:根据MERGE 声明,将有1(一)INSERT0(零)EXISTS1 = 0 将被优化掉。
      【解决方案4】:

      要提高 SP 的速度,您可以添加另一个语句 FOR READ ONLY

      所以你的 SP 会是这样的:

          ...
      
      SET @ACUnitCursor = CURSOR LOCAL FAST_FORWARD FOR 
      
      SELECT ACStageID FROM TBLACStages WHERE TBLACStages.ACUnitID = @pACUnitID
      
      FOR READ ONLY  -- add this to increase the speed
      
      OPEN @ACUnitCursor
      
      FETCH NEXT FROM @ACUnitCursor INTO @ACStageID
      
      ...
      

      【讨论】:

      • 也许我不明白你的答案...FAST_FORWARD 根据定义是只读的
      • 你说得对,我输入得太快了,我没有注意到你将光标设置为FAST_FORWARD。顺便说一句,我建议将您的记录直接插入 TBLACStages、TBLACUpgrade、...,将它们插入 temp table as @TBLACStages,然后当循环完成时,只需 1 SELECT INTO,您就可以将所有记录从 @TBLACStages > TBLACStages一口气。这大大减少了 SQL Server 的工作量,因为 TEMP TABLE 在内存中而不是写在磁盘上。我在你的情况下使用了这种技术。这只是一个提示。
      猜你喜欢
      • 1970-01-01
      • 2014-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-12
      • 2016-03-22
      • 2013-07-30
      • 2013-11-20
      相关资源
      最近更新 更多