【问题标题】:How to copy tables avoiding cursors in SQL?如何在 SQL 中复制表以避免游标?
【发布时间】:2011-05-30 09:15:15
【问题描述】:

我想在 SQL 中编写脚本,将这 2 个表(A,B)复制到其他 2 个表(C,D),相应地具有与 A,B 相同的结构。

重要提示

  1. C、D 表必须为空
  2. 多个进程可能同时调用脚本

表 A 有表 B 的外键(fk_a_b)

   ________________________  _________________
   |        Table A       |  |   Table B     |  
   |______________________|  |_______________|
   | id     FK_A_B   name |  | id    visible |
   | ----- -------- ------|  | ----- --------|
   | 1      21       n1   |  | 21     true   |
   | 5      32       n2   |  | 32     false  |
   ------------------------  -----------------

假设将表 B 复制到 D 后,这就是我得到的

   ________________
   |   Table D    |  
   |______________|
   | id   visible |
   | ----- -------|
   | 51    true   |
   | 52    false  |
   ----------------

现在,当我将表 A 复制到 C 时,我需要知道,ID=21 现在映射到 ID=51,ID=32 映射到 ID=52。最后,表 C 将是:

   ________________________
   |        Table C       |
   |______________________|
   | id     FK_C_D   name |
   | ----- -------- ------|
   | 61      51       n1  |
   | 62      52       n2  |
   ------------------------

由于多个进程可能同时调用脚本,我无法更改表 A、B 以添加一些辅助列。因此,为了实现这一点,我使用了 CURSOR。我逐行复制表B和托管临时表以将OldId映射到NewId(21->51,32->52),然后使用此临时表复制表A。

我听说 CURSOR 是不好的做法。那么,还有其他方法吗?

谢谢

【问题讨论】:

  • 如果需要,您可以从源表中保留相同的标识值。你有什么理由要创建新的 id 吗?
  • @d-live,我写的那个表是 NOT 空的。源表中的标识值可以在目标表中使用

标签: sql sql-server tsql sql-server-2008 stored-procedures


【解决方案1】:

您可以使用带有合并语句的输出子句来获取源 id 和目标 id 之间的映射。 在这个问题中描述。 Using merge..output to get mapping between source.id and target.id

这里有一些你可以测试的代码。我使用表变量而不是真实表。

设置示例数据:

-- @A and @B is the source tables
declare @A as table
(
  id int,
  FK_A_B int,
  name varchar(10)
)

declare @B as table
(
  id int,
  visible bit
)  

-- Sample data in @A and @B
insert into @B values (21, 1),(32, 0)
insert into @A values (1, 21, 'n1'),(5, 32, 'n2')


-- @C and @D is the target tables with id as identity columns
declare @C as table
(
  id int identity,
  FK_C_D int not null,
  name varchar(10)
)

declare @D as table
(
  id int identity,
  visible bit
)  

-- Sample data already in @C and @D
insert into @D values (1),(0)
insert into @C values (1, 'x1'),(1, 'x2'),(2, 'x3')

复制数据:

-- The @IdMap is a table that holds the mapping between
-- the @B.id and @D.id (@D.id is an identity column)
declare @IdMap table(TargetID int, SourceID int)

-- Merge from @B to @D.
merge @D as D             -- Target table
using @B as B             -- Source table
on 0=1                    -- 0=1 means that there are no matches for merge
when not matched then
  insert (visible) values(visible)    -- Insert to @D
output inserted.id, B.id into @IdMap; -- Capture the newly created inserted.id and
                                      -- map that to the source (@B.id)

-- Add rows to @C from @A with a join to
-- @IdMap to get the new id for the FK relation
insert into @C(FK_C_D, name)
select I.TargetID, A.name 
from @A as A
  inner join @IdMap as I
    on A.FK_A_B = I.SourceID

结果:

select *
from @D as D
  inner join @C as C
    on D.id = C.FK_C_D

id          visible id          FK_C_D      name
----------- ------- ----------- ----------- ----------
1           1       1           1           x1
1           1       2           1           x2
2           0       3           2           x3
3           1       4           3           n1
4           0       5           4           n2

您可以在这里测试代码:https://data.stackexchange.com/stackoverflow/q/101643/using-merge-to-map-source-id-to-target-id

【讨论】:

  • 我会更深入地阅读它,但乍一看似乎更复杂。它是否比游标更有效?
  • @theateist - 性能很难一概而论,但在这种情况下,我认为可以肯定地说合并的性能比游标好得多。
  • @theateist - 在链接的问题中可能并不明显,但使用合并时不需要使用循环。在您的情况下,您将有一个 B->D 的合并语句与映射 id 的输出。和一个插入语句 A-C,它连接来自合并的输出表以找到新的 id。
  • 它看起来确实很复杂,但值得了解。它非常有用。我会冒险说它会比光标更有效
  • @Alex - 合并中 B 和 D 之间的映射为 on 0=1,这意味着合并中没有匹配项,因此来自 B 的所有行都将插入到 D。跨度>
【解决方案2】:

例如SQL Server 将 rowguid 字段添加到包含在某些发布中的表中以进行合并复制。我认为,这种方法可以用于您的任务。这个想法是添加几个 GUID 字段来扮演全局标识符的角色,这样我们就可以在两对主数据表中使用它们

【讨论】:

    【解决方案3】:

    你可以这样做:

    if object_id('tempdb..#TableB') is not null
        drop table #TableB
    
    select identity(int) RowId, *
    into #TableB
    from TableB
    
    if object_id('tempdb..#TableDIds') is not null
        drop table #TableDIds
    create table  #TableDIds (RowId int identity(1,1), Id int)
    
    insert TableD
    output inserted.Id into #TableDIds
    select Visible
    from #TableB
    order by RowId
    
    insert TableC
    select tdi.Id, ta.name
    from TableA ta
        join #TableB tb on
            ta.FK_A_B = tb.Id
        join #TableDIds tdi on
            tdi.RowId = tb.RowId
    

    我使用了以下设置:

    create table TableB
    (
        Id int not null primary key,
        Visible bit not null
    )
    
    create table TableA
    (
        Id int not null, 
        FK_A_B int not null foreign key references TableB(Id), 
        Name varchar(10) not null
    )
    
    create table TableD
    (
        Id int identity(1,1) primary key,
        Visible bit not null
    )
    
    create table TableC
    (
        Id int identity(1,1), 
        FK_C_D int not null references TableD(Id), 
        Name varchar(10) not null
    )
    
    insert TableB
    values
        (21, 1),
        (32, 0)
    
    insert TableA
    values
        (1, 21, 'n1'),
        (5, 32, 'n2')
    

    【讨论】:

      【解决方案4】:

      【讨论】:

        猜你喜欢
        • 2010-09-06
        • 2014-12-22
        • 2023-03-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多