合并语句插入而不是在SQL Server中更新

时间:2013-12-10 20:33:33

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

我正在使用SQL Server 2008,我正在尝试从登台(源)表加载新的(目标)表。目标表是空的。

我认为因为我的目标表是空的,MERGE语句跳过WHEN MATCHED部分,即INNER JOIN的结果为NULL,因此没有任何更新,它只是继续到目标不匹配的部分(LEFT OUTER JOIN) a在登台表中插入所有记录。

我的目标表看起来与我的临时表(行#1和#4)完全相似。目标表中应该只有3行(3行插入和第4行更新)。所以,我不确定最新情况。

FileID  client_id account_name  account_currency  creation_date last_modified
210     12345           Cars            USD       2013-11-21    2013-11-27 
211     23498           Truck           USD       2013-09-22    2013-11-27 
212     97652           Cars - 1        USD       2013-09-17    2013-11-27 
210     12345           Cars            JPY       2013-11-21    2013-11-29 


QUERY

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]             
    );

2 个答案:

答案 0 :(得分:3)

由于目标表是空的,使用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语法,潜在的错误等。

答案 1 :(得分:2)

  

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

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

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