TSQL - 带复合键的MERGE语句

时间:2012-07-04 16:58:28

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

我有表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

但是出现了语法错误。

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

2 个答案:

答案 0 :(得分:12)

只需使用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进行测试。

答案 1 :(得分: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 the MERGE`语句中的行:

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子句一起进行反半连接,以获取目标表中不受{的影响的行{1}}声明(插入或更新)但仍有源源表(MERGE)。

应该是对的...在你测试之后告诉我。

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