【问题标题】:T SQL Looping on insert or updateT SQL 循环插入或更新
【发布时间】:2010-11-19 17:38:27
【问题描述】:

我有两张桌子。

表 A 和表 B。列相同。

create table TableA (
    id int
    , name varchar
    , last datetime
)

create table TableB (
    id int
    , name varchar
    , last datetime
)

我正在用大量数据填充表 A。我想将表 A 中的数据插入或更新到表 B 中。

我想从表 A 中获取数据,如果 id 和 name 不匹配,则插入到表 B 中,或者如果 id 和 name 匹配,则更新。

我尝试了一些 ETL 工具,但结果很慢。我对 id 和 name 进行了索引,我想用 SQL 尝试一下。

我有以下但工作不正确:

SELECT      @id = ID, 
      @name = name, 
      @LSDATE = LastSeen_DateTime   
            FROM DBO.A
IF EXISTS (SELECT ID, name FROM DBO.A
WHERE  @ID = ID AND @name = Name)

开始 - 更新 结尾 别的 开始 - 插入 结束

我想我需要把它放在一个循环中,但我不太确定如何让它运行。

谢谢。

【问题讨论】:

  • 问题是将表 A 填充到表 B,A 到 B 中的所有行,而不是单行。它应该在一个循环中。对于 A 中的每一行,要么插入 B,要么更新 B 中的一行。

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


【解决方案1】:

执行两条语句,一条更新一条插入,而不是循环,可能更快

此语句使用来自 A 的 ID 相同但名称不同的数据更新所有 B 行

更新

Update 
    tableB
SET
   name = a.Name
From
   tableB a
   INNER JOIN tableA a
   on b.ID = a.ID 
      and A.Name <> b.Name

此语句将所有 B 行插入到 A 中,其中 id 在 A 中不存在

插入

INSERT INTO
   tableB
(   ID,
    Name
)
SELECT
   a.ID
   a.Name
FROM 
   tableA b
WHERE
   not exists (Select A.ID From tableB a WHERE a.ID = b.ID)

更新(将其从 A 反转为 B,而不是 B 反转为 A)

【讨论】:

  • 问题是将表 A 填充到表 B,A 到 B 中的所有行,而不是单行。它应该在一个循环中。
  • +1 2 bulk statements 是我要走的路,而不是不必要的和较慢的循环。虽然听起来应该在 Id 和 name 上匹配行,而不仅仅是 id
  • @user177883 抱歉,我将 B 反转为 A。我已解决此问题,但这些 SQL 语句不是所有行。
  • @user177883 - 提供的 SQL 适用于所有行。 SQL 是基于 set 的,而不是基于行的,除非另有明确编码。第一条语句更新 ID 匹配且名称不同的所有 B.name 值,然后 INSERT 将所有 A 行复制到 B 中,其中 A.ID 值不在 B 中。最终结果是所有 A 数据最终都在B. 也许可以将“最后一个”列添加到语句中,但这很简单。
  • 当发布者移到 2008 年时,他可以使用 MErge 语句代替,但这是 2005 年的最佳选择。远比游标或循环好得多,这种类型甚至不应该考虑任务。
【解决方案2】:

如果您使用的是 SQL Server 2008(或 Oracle 或 DB2),那么您可以使用合并语句。

MERGE B
USING A AS source 
ON (B.ID = source.ID and B.Name = source.Name)
WHEN MATCHED THEN 
    UPDATE SET Last = source.Last
WHEN NOT MATCHED BY TARGET THEN
    INSERT (ID, Name, Last) VALUES (source.ID, source.Name, source.Last)

   -- the following is optional, if you remove it, add a semicolon to the end of the above line. 
   OUTPUT $action, 
   inserted.ID AS SourceID, inserted.Name AS SourceName, 
   inserted.Last AS SourceLast, 
   deleted.ID AS TargetID, deleted.Name AS TargetName, 
   deleted.Last AS TargetLast ;   

带有“输出 $action”的位将显示正在更新的行以及正在更新的行。

weasel words:我知道这不是完全您要查找的内容,但是由于其他人可能会搜索此主题,因此将来可能对其他人有所帮助。

【讨论】:

  • @Conrad,我对性能一无所知。我们在开发中使用 2008,在 QA 和生产中使用 2005,因此我必须让生产运行的代码有时看起来像您的答案,有时像 Will 的答案。生产服务器位于大约 2k 英里外的数据中心,由相当敌对的人运行,他们必须在运行之前了解我发送给他们的所有内容。我们的开发和 QA 服务器都没有足够大的硬盘来显示任何真正的差异。
【解决方案3】:
DECLARE @id int
DECLARE @name nvarchar
DECLARE @last datetime
DECLARE TableA_Cursor CURSOR FOR
    select id
            , name
            , last
        from TableA;

OPEN TableA_Cursor;

FETCH NEXT from TableA_Cursor 
INTO @id, @name, @last;

WHILE @@FETCH_STATUS = 0
    BEGIN
        IF (EXISTS select 1 from TableB b where b.Id = @id)
            update TableB
                set Name = @name
                    , Last = @last
        ELSE
            insert into TableB (Id, Name, Last) 
                values (@id, @name, @last)

        FETCH NEXT from TableA_Cursor
        INTO @id, @name, @last
    END

CLOSE TableA_Cursor;

DEALLOCATE TableA_Cursor;

可能存在一些语法错误,尤其是在 IF 条件附近,但您可能明白这一点。

【讨论】:

  • +1 用于提供循环,即使它可能不是要走的路
  • 这不是您应该考虑使用光标的任务。
  • @Conrad Frix:就个人而言,如果我必须按照 OP 的要求去做,我肯定会按照您的建议使用两个单独的语句。事实上,这就是我收到新答案更新时所写的内容。看到您的建议后,我无法跟上我的解决方案,因为如果您明白我的意思,那将是“副本”。所以,我想出了另一个带循环的方法。
  • @HLGEM:是的,这种方法是另一种想法,因为我的第一个选择是康拉德弗里克斯的回答。但是 OP 似乎坚持他的循环,所以我提供了一个循环。这肯定会起作用,这完全取决于数据服务器的可用资源以及要处理的数据量。但这会起作用,所以它是一个可用的解决方案。
  • 别忘了你必须在WHILE 循环中FETCH NEXT
猜你喜欢
  • 2011-01-17
  • 1970-01-01
  • 2016-05-25
  • 1970-01-01
  • 1970-01-01
  • 2020-07-07
  • 1970-01-01
  • 2016-04-03
相关资源
最近更新 更多