【问题标题】:TSQL - MERGE statement with composite keyTSQL - 带有复合键的 MERGE 语句
【发布时间】:2012-07-05 04:35:17
【问题描述】:

我有相同结构的表 OrderLines(OrderID int, LineIndex int, ) 和表值参数定义一个订单的新订单行。

如果我有以下 OrderLines

1000   1   bread
1000   2   milk
1001   1   oil
1001   2   yogurt
1002   1   beef
1002   2   pork

还有下面的TVP

1001   1   yogurt

我想获得以下 OrderLines

1000   1   bread
1000   2   milk
1001   1   yogurt
1002   1   beef
1002   2   pork

即仅针对一个订单触摸行。

所以我这样写了我的查询

MERGE
    [OrderLines] AS [Target]
USING
(
    SELECT
        [OrderID], [LineIndex], [Data]
    FROM
        @OrderLines
)
AS [Source] ([OrderID], [LineIndex], [Data])
ON ([Target].[OrderID] = [Source].[OrderID]) AND ([Target].[LineIndex] = [Source].[LineIndex])
WHEN MATCHED THEN
    UPDATE
    SET
        [Target].[Data] = [Source].[Data]
WHEN NOT MATCHED BY TARGET THEN
    INSERT
        ([OrderID], [LineIndex], [Data])
    VALUES
        ([Source].[OrderID], [Source].[LineIndex], [Source].[Data])
WHEN NOT MATCHED BY SOURCE THEN
    DELETE;

它会删除其他订单的所有其他(未提及的)OrderLines。

我试过了

WHEN NOT MATCHED BY SOURCE AND ([Target].[OrderID] = [Source].[OrderID]) THEN

但出现语法错误。

我应该如何重写我的查询?

【问题讨论】:

    标签: sql-server tsql sql-server-2012


    【解决方案1】:

    对于初学者,只有目标表中的列才能用于WHEN NOT MATCHED BY SOURCE 附加合并条件(位于MSDN)。

    而且我认为丢失目标表中的所有额外条目是正常的,因为它们与源表中的任何内容都不匹配。

    您应该重写您的查询,首先删除WHEN NOT MATCHED BY SOURCE 子句,然后分别删除额外/不需要的行。

    然后,您需要通过添加:

    来获取目标表中更新或插入的所有条目:
    DECLARE @OutputTable table( OrderId INT, OrderLine INT);
    
    ...Your entire MERGE
    WHEN NOT MATCHED BY TARGET THEN
        INSERT
            ([OrderID], [LineIndex], [Data])
        VALUES
            ([Source].[OrderID], [Source].[LineIndex], [Source].[Data])
    OUTPUT INSERTED.OrderId, INSERTED.LineIndex INTO @OutputTable
    

    现在在@OutputTable 中,您拥有所有已更新或输入到目标表中的键(注意OUTPUT 子句)。

    您现在只需要查看目标表中的哪些行,仅匹配来自@OrderLines 的键,不在@OutputTable' and delete them (so they haven't been updated nor inserted by theMERGE` 语句中):

    DELETE A
    FROM [OrderLines] AS A
    INNER JOIN @OrderLines AS B
     ON B.OrderId = A.OrderId AND B.LineIndex = A.LineIndex
    LEFT OUTER JOIN @OutputTable AS C
     ON C.OrderId = A.OrderId AND C.OrderLine = A.LineIndex
    WHERE C.OrderId IS NULL AND C.OrderLine IS NULL 
    

    您在这里所做的(认为它是正确的)实际上是您首先要删除的内容。内连接将结果集过滤为@OrderLines(因此只有具有这些键的行),左连接与where子句一起进行反半连接,以获取目标表中不受MERGE影响的行语句(插入或更新)但仍有键在源表 (@OrderLines) 中。

    应该是对的……测试后告诉我。

    如果您决定采用这种方法,您可能希望将所有这些 (MERGE + DELETE) 包装在事务中。

    【讨论】:

      【解决方案2】:

      只需使用OrderLines 的相关子集作为目标:

      WITH AffectedOrderLines AS (
          SELECT *
          FROM OrderLines
          WHERE OrderID IN (SELECT OrderID FROM @OrderLines)
      )
      MERGE
          AffectedOrderLines AS [Target]
      USING
      (
          SELECT
              [OrderID], [LineIndex], [Data]
          FROM
              @OrderLines
      )
      AS [Source] ([OrderID], [LineIndex], [Data])
      ON ([Target].[OrderID] = [Source].[OrderID]) AND ([Target].[LineIndex] = [Source].[LineIndex])
      WHEN MATCHED THEN
          UPDATE
          SET
              [Target].[Data] = [Source].[Data]
      WHEN NOT MATCHED BY TARGET THEN
          INSERT
              ([OrderID], [LineIndex], [Data])
          VALUES
              ([Source].[OrderID], [Source].[LineIndex], [Source].[Data])
      WHEN NOT MATCHED BY SOURCE THEN
          DELETE;
      

      还有here's a SQL Fiddle 进行测试。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-01-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-03
        相关资源
        最近更新 更多