【问题标题】:Merge statement inserting instead of updating in SQL Server在 SQL Server 中合并语句插入而不是更新
【发布时间】:2013-12-10 20:33:33
【问题描述】:

我正在使用 SQL Server 2008,并且正在尝试从暂存(源)表中加载新的(目标)表。目标表为空。

我认为由于我的目标表是空的,MERGE 语句会跳过 WHEN MATCHED 部分,即 INNER JOIN 的结果为 NULL,因此没有更新,它只是继续执行 WHEN NOT MATCHED BY TARGET 部分(LEFT OUTER JOIN) an 插入临时表中的所有记录。

我的目标表看起来与我的暂存表(第 1 行和第 4 行)完全相同。目标表中应该只有 3 行(第 4 行 3 次插入和 1 次更新)。所以,我不确定发生了什么。

FileID client_id account_name account_currency creation_date last_modified 210 12345 汽车 USD 2013-11-21 2013-11-27 211 23498 卡车 USD 2013-09-22 2013-11-27 212 97652 汽车 - 1 美元 2013-09-17 2013-11-27 210 12345 汽车 JPY 2013-11-21 2013-11-29


查询

MERGE [AccountSettings] AS tgt -- RIGHT TABLE
USING
(
SELECT * FROM [AccountSettings_Staging]
) AS src -- LEFT TABLE
ON src.client_id = tgt.client_id
AND src.account_name = tgt.account_name
WHEN MATCHED -- INNER JOIN
    THEN UPDATE
       SET
         tgt.[FileID] = src.[FileID]
        ,tgt.[account_currency] = src.[account_currency]
        ,tgt.[creation_date] = src.[creation_date]
        ,tgt.[last_modified] = src.[last_modified]

WHEN NOT MATCHED BY TARGET  -- left outer join: A row from the source that has no corresponding row in the target
THEN INSERT
    (
        [FileID],   
        [client_id], 
        [account_name],
        [account_currency],
        [creation_date], 
        [last_modified] 
    )
    VALUES
    (
        src.[FileID],   
        src.[client_id], 
        src.[account_name],
        src.[account_currency], 
        src.[creation_date], 
        src.[last_modified]             
    );

【问题讨论】:

  • @AaronBertrand 谢谢。除了 IF EXISTS,你建议用什么来代替它?
  • 它现在是空的,但不会用于未来的工作运行。如果 MERGE 语句继续为任何帐户插入重复数据,则会导致问题。
  • 你应该在问题中这么说。问题中的所有内容都让我相信这是某种一次性的事情,或者目标表总是首先被清除。

标签: sql sql-server sql-server-2008


【解决方案1】:

由于目标表是空的,所以在我看来,使用MERGE 就像雇一个水管工给你倒一杯水一样。而MERGE 只为表的每一行独立地操作一个分支——它看不到键是重复的,因此执行插入然后更新——这表明你认为 SQL 总是在一行上操作——以行为基础,而实际上大多数操作是一次对整个集合执行的。

为什么不只插入最近的行:

;WITH cte AS 
(
  SELECT FileID, ... other columns ..., 
    rn = ROW_NUMBER() OVER (PARTITION BY FileID ORDER BY last_modified DESC)
  FROM dbo.AccountSettings_Staging
)
INSERT dbo.AccountSettings(FileID, ... other columns ...)
  SELECT FileID, ... other columns ...
  FROM cte WHERE rn = 1;

如果您有可能在最近的 last_modified 上打成平手,则需要找到另一个平手(从您的样本数据中看不出来)。

对于未来的版本,我会说先运行UPDATE

UPDATE a SET client_id = s.client_id /* , other columns that can change */
  FROM dbo.AccountSettings AS a
  INNER JOIN dbo.AccountSettings_Staging AS s
  ON a.FileID = s.FileID;

(当然,如果源包含具有相同 FileID 的多行,这将选择任意行 - 您可能也希望在此处使用 CTE 以使选择可预测。)

然后将此子句添加到上面的INSERT CTE:

FROM dbo.AccountSettings_Staging AS s
WHERE NOT EXISTS (SELECT 1 FROM dbo.AccountSettings 
  WHERE FileID = s.FileID);

以适当的隔离级别将其全部包装在事务中,您仍然可以避免大量复杂的MERGE 语法、潜在的错误等。

【讨论】:

  • 它现在是空的,但不会用于未来的工作运行。如果 MERGE 语句继续为任何帐户插入欺骗,则会导致问题。也许我需要在 MERGE 中使用 CTE 作为 SOURCE,然后在目标表中使用 UPDATE 或 DELETE。
  • @Chang 它只会插入欺骗如果源包含不在目标中的欺骗。我也不相信 SQL 会按照它们出现在 CTE 中的顺序更新记录。您需要以任何一种方式解决源中的欺骗(或使用光标,这将是最后的手段)
  • Aaron - UPDATE/INSERT 不是 MERGE 在幕后做的吗?不争论你的答案只是重申MERGE没有很多“魔法”@
  • @DStanley 确实如此,那么为什么要使用完全麻烦且不直观的语法呢?或者努力验证您没有受到大约十几个错误结果和其他尚未修复的严重错误的影响?你read this post了吗?
  • @AaronBertrand 我做到了,我绝对同意 - 我只是重申 MERGE 不会比 UPDATE/INSERT 批量购买你更多(+1 BTW)
【解决方案2】:

我认为由于我的目标表是空的,MERGE 语句会跳过 WHEN MATCHED 部分

嗯,没错,但这是设计使然 - MERGE 不是“渐进式”合并。它不会逐行查看作为MERGE 的一部分插入的记录现在是否应该更新。它根据是否在目标中找到匹配项来“批量”处理源。

在尝试MERGE 之前,您需要处理源中的“重复”记录。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多