MERGE / INSERT / DELETE SQL命令中有多个OUTPUT子句?

时间:2013-06-18 09:55:58

标签: sql sql-server tsql

我有一个T-SQL脚本,使用OUTPUTMERGE中的INSERT子句实现了一些同步逻辑。

现在我在它上面添加一个日志记录层,我想添加第二个OUTPUT子句将值写入报告表。

我可以在OUTPUT语句中添加第二个MERGE子句:

MERGE TABLE_TARGET AS T
USING TABLE_SOURCE AS S
ON (T.Code = S.Code) 
WHEN MATCHED AND T.IsDeleted = 0x0
    THEN UPDATE SET ....
WHEN NOT MATCHED BY TARGET 
    THEN INSERT ....
OUTPUT inserted.SqlId, inserted.IncId
INTO @sync_table
OUTPUT $action, inserted.Name, inserted.Code;

这样可行,但只要我尝试添加目标

INTO @report_table;

我在INTO之前收到以下错误消息:

A MERGE statement must be terminated by a semicolon (;)

我找到a similar question here,但它对我没有帮助,因为我要插入的字段在两个表之间没有重叠,我不想修改工作同步逻辑(如果可能的话)。

更新

Martin Smith的答案之后,我有了另一个想法并重新编写了我的查询,如下所示:

INSERT INTO @report_table (action, name, code)
SELECT M.Action, M.Name, M.Code
FROM
(
MERGE TABLE_TARGET AS T
USING TABLE_SOURCE AS S
ON (T.Code = S.Code) 
WHEN MATCHED AND T.IsDeleted = 0x0
    THEN UPDATE SET ....
WHEN NOT MATCHED BY TARGET 
    THEN INSERT ....
OUTPUT inserted.SqlId, inserted.IncId
INTO @sync_table
OUTPUT $action as Action, inserted.Name, inserted.Code
) M

不幸的是,这种方法也不起作用,在运行时输出以下错误消息:

An OUTPUT INTO clause is not allowed in a nested INSERT, UPDATE, DELETE, or MERGE statement.

因此,在单个DML语句中绝对没有办法使用多个OUTPUT子句。

4 个答案:

答案 0 :(得分:16)

不可能。请参阅grammar

Merge语句有

[ <output_clause> ]

方括号显示它可以有一个可选的输出子句。语法是

<output_clause>::=
{
    [ OUTPUT <dml_select_list> INTO { @table_variable | output_table }
        [ (column_list) ] ]
    [ OUTPUT <dml_select_list> ]
}

此子句可以包含OUTPUT INTOOUTPUT,但不能同时包含两个[ ,...n ]

如果允许多个语法,则语法将为{{1}}

答案 1 :(得分:3)

Martin Smith是对的,在一个OUTPUT INTO声明中不可能有两个MERGE条款,但他也是正确的 可能有一个{{}> {1}}和一个OUTPUT INTO子句。 OUTPUT将其结果集直接插入到给定的表中,而简单的OUTPUT INTO将结果集返回给调用者。

因此,您可以将OUTPUT语句包装到存储过程中,然后使用MERGE将简单INSERT ... EXEC的结果集插入到第二个表中。

OUTPUT

<强>用法

CREATE PROCEDURE [dbo].[TestMerge]
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    MERGE TABLE_TARGET AS T
    USING TABLE_SOURCE AS S
    ON (T.Code = S.Code) 
    WHEN MATCHED AND T.IsDeleted = 0x0
        THEN UPDATE SET ....
    WHEN NOT MATCHED BY TARGET 
        THEN INSERT ....
    OUTPUT inserted.SqlId, inserted.IncId
    INTO sync_table
    OUTPUT $action AS MergeAction, inserted.Name, inserted.Code;
END

这会将行插入INSERT INTO report_table EXEC [dbo].[TestMerge]; sync_table

如果您检查执行计划,您会看到report_table在幕后创建临时表(另请参阅The Hidden Costs of INSERT EXEC by Adam Machanic)。

答案 2 :(得分:1)

OUTPUT子句允许选择列表。尽管这不允许有多个结果集,但确实允许有一个结果集可以解决所有操作。

<output_clause>::=
{
    [ OUTPUT <dml_select_list> INTO { @table_variable | output_table }
        [ (column_list) ] ]
    [ OUTPUT <dml_select_list> ]
}

直到第二天,我才忽略了这一点,那时我需要知道为该行执行的操作并不想在下游使用复杂的逻辑。 这意味着您在这里拥有更多的自由。我做了与以下类似的操作,使我可以通过简单的方式使用输出:

DECLARE @MergeResults TABLE (
    MergeAction VARCHAR(50),
    rowId INT NOT NULL,
    col1 INT NULL,
    col2 VARCHAR(255) NULL
    )

MERGE INTO TARGET_TABLE AS t
    USING SOURCE_TABLE AS s
    ON t.col1 = s.col1
WHEN MATCHED
    THEN
        UPDATE
        SET [col2] = s.[col2]
WHEN NOT MATCHED BY TARGET
    THEN
        INSERT (
            [col1]
            ,[col2]
            )
        VALUES (
            [col1]
            ,[col2]
            )
WHEN NOT MATCHED BY SOURCE
    THEN
        DELETE
OUTPUT $action as MergeAction, 
    CASE $action 
        WHEN 'DELETE' THEN deleted.rowId 
        ELSE inserted.rowId END AS rowId,
    CASE $action 
        WHEN 'DELETE' THEN deleted.col1 
        ELSE inserted.col1 END AS col1,
    CASE $action 
        WHEN 'DELETE' THEN deleted.col2 
        ELSE inserted.col2 END AS col2
    INTO @MergeResults;

您最终会得到类似以下结果集:

| MergeAction | rowId | col1 | col2 |
| INSERT      | 3     | 1    | new  |
| UPDATE      | 1     | 2    | foo  |
| DELETE      | 2     | 3    | bar  |

答案 3 :(得分:0)

很抱歉复活旧线程,但我遇到了这个问题并使用了一个实用而非技术性的解决方案,可能会也可能不会很明显。

如前所述,MERGE不是为此而设计的。 INSERT_INTO ... EXEC解决方案是一个很好的解决方法,但我正在使用的特定存储过程已经非常复杂了。

因此,为了让下一个必须处理此代码的人保持简单,我只使用了两个MERGE语句...一个执行插入,一个执行更新。毕竟,没有法律规定你必须只使用一个。我在日志记录表中添加了一个“action”列,我将MERGE语句插入“插入”或“更新”,具体取决于它正在做什么。

性能并不需要担心,特别是因为这不是用户进程。

提示:首先进行更新,然后进行插入。否则,当您执行第一次加载时,您将为导入的每一行获得一条插入记录和一条更新记录。