当前位置:首页 > 数据库 > 正文内容

SQLServer OUTPUT子句的用法

2024-11-29数据库2

在 SQL Server 中,OUTPUT 子句允许你捕获由 INSERT、UPDATE 或 DELETE 语句影响的行,并将这些行作为结果集返回。这对于需要同时获取修改的行和执行修改操作本身非常有用。

参考官方地址
https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql?view=sql-server-ver16

1、本文内容

  • 语法

  • 参数

  • 注解

  • 将从 OUTPUT 子句返回的数据插入表

  • 并行度

  • 触发器

  • 数据类型

  • 权限

  • 示例

  • 相关内容

适用于:

  • SQL Server

  • Azure SQL 数据库

  • Azure SQL 托管实例

返回受 INSERT、UPDATE、DELETE 或 MERGE 语句影响的各行中的信息,或返回基于受这些语句影响的各行的表达式。 这些结果可以返回到处理应用程序,以供在确认消息、存档以及其他类似的应用程序要求中使用。 也可以将这些结果插入表或表变量。 另外,你可以捕获嵌入的 INSERT、UPDATE、DELETE 或 MERGE 语句中 OUTPUT 子句的结果,然后将这些结果插入目标表或视图。

备注对于具有 OUTPUT 子句的 UPDATE、INSERT 或 DELETE 语句,即使在遇到错误需要回滚时,也会将行返回到客户端。 如果在运行语句的过程中出现任何错误,都不应使用该结果。

2、语法

1
2
3
4
5
6
7
8
9
10
11
12
<OUTPUT_CLAUSE> ::=
{
    [ OUTPUT <dml_select_list> INTO { @table_variable | output_table } [ ( column_list ) ] ]
    [ OUTPUT <dml_select_list> ]
}
<dml_select_list> ::=
{ <column_name> | scalar_expression } [ [ AS ] column_alias_identifier ]
    [ , ...n ]
 
<column_name> ::=
{ DELETED | INSERTED | from_table_name } . { * | column_name }
    | $action

3、参数

@table_variable
指定 table 变量,返回的行将插入此变量,而不是返回给调用方。 @table_variable 必须在 INSERT、UPDATE、DELETE 或 MERGE 语句前声明。

如果未指定 column_list,则 table 变量必须与 OUTPUT 结果集具有相同的列数。 标识列和计算列除外,这两种列必须跳过。 如果指定了 column_list,则任何省略的列都必须允许 NULL 值,或者都分配有默认值。

有关 table 变量的详细信息,请参阅 table (Transact-SQL)。

output_table
指定一个表,返回的行将插入该表中而不是返回到调用方。 output_table 可以为临时表。

如果未指定 column_list,则 table 必须与 OUTPUT 结果集具有相同的列数。 标识列和计算列例外, 必须跳过这两种列。 如果指定了 column_list,则任何省略的列都必须允许 NULL 值,或者都分配有默认值。

output_table 无法:

  • 具有启用的对其定义的触发器。

  • 参与 FOREIGN KEY 约束的任意一方。

  • 具有 CHECK 约束或启用的规则。

column_list
INTO 子句目标表上列名的可选列表。 它类似于 INSERT 语句中允许使用的列列表。

  • scalar_expression
    计算结果为单个值的任何符号和运算符的组合。 scalar_expression 中不允许使用聚合函数。

    对修改的表中的列的任何引用都必须使用 INSERTED 或 DELETED 前缀限定。

  • column_alias_identifier
    用于引用列名的替换名称。

  • DELETED
    指定由更新或删除操作删除的值的列前缀。 以 DELETED 为前缀的列反映了 UPDATE、DELETE 或 MERGE 语句完成之前的值。

    不能在 INSERT 语句中同时使用 DELETED 与 OUTPUT 子句。

  • INSERTED
    指定由插入作或更新操作添加的值的列前缀。 以 INSERTED 为前缀的列反映了在 UPDATE、INSERT 或 MERGE 语句完成之后但在触发器执行之前的值。

    不能在 DELETE 语句中同时使用 INSERTED 与 OUTPUT 子句。

  • from_table_name
    一个列前缀,指定 DELETE、UPDATE 或 MERGE 语句(用于指定要更新或删除的行)的 FROM 子句中包含的表。

    如果还在 FROM 子句中指定了要修改的表,则对该表中的列的任何引用都必须使用 INSERTED 或 DELETED 前缀限定。

例如,以下 DELETE 语句中的 OUTPUT DELETED.* 将返回 ShoppingCartItem 表中所有已删除的列:

1
DELETE Sales.ShoppingCartItem OUTPUT DELETED.*;
  • column_name
    显式列引用。 任何对正在修改的表的引用都必须使用相应的 INSERTED 或 DELETED 前缀正确限定,例如:INSERTED.column_name。

  • $action
    仅可用于 MERGE 语句。 在 MERGE 语句的 OUTPUT 子句中指定一个 nvarchar(10) 类型的列,该子句为每行返回以下三个值之一:“INSERT”、“UPDATE”或“DELETE”,返回哪个值取决于对该行执行的操作。

4、注解

OUTPUT <dml_select_list> 子句和 OUTPUT <dml_select_list> INTO { @table_variable | output_table } 子句可以在单个 INSERT、UPDATE、DELETE 或 MERGE 语句中定义。

备注

除非另行指定,否则,引用 OUTPUT 子句将同时引用 OUTPUT 子句和 OUTPUT INTO 子句。

OUTPUT 子句对于在 INSERT 或 UPDATE 操作之后检索标识列或计算列的值可能非常有用。

当 <dml_select_list> 中包含计算列时,输出表或表变量中的相应列并不是计算列。 新列中的值是在执行该语句时计算出的值。

无法保证将更改应用于表的顺序与将行插入输出表或表变量的顺序相对应。

如果将参数或变量作为 UPDATE 语句的一部分进行了修改,则 OUTPUT 子句将始终返回语句执行之前的参数或变量的值而不是已修改的值。

在使用 WHERE CURRENT OF 语法通过游标定位的 UPDATE 或 DELETE 语句中,可以使用 OUTPUT。

以下语句中不支持 OUTPUT 子句:

  • 引用本地分区视图、分布式分区视图或远程表的 DML 语句。

  • 包含 EXECUTE 语句的 INSERT 语句。

  • 当数据库兼容级别设置为 100 时,不允许在 OUTPUT 子句中使用全文谓词。

  • 不能将 OUTPUT INTO 子句插入视图或行集函数。

  • 如果用户定义的函数包含一个以表为目标的 OUTPUT INTO 子句,则不能创建该函数。

若要防止出现不确定的行为,OUTPUT 子句不能包含以下引用:

执行用户或系统数据访问的子查询或用户定义函数,或者被认定会执行此类访问的子查询或用户定义函数。 如果用户定义函数未绑定到架构,则认定它会执行数据访问。

视图或内嵌表值函数中的一个列(如果该列由以下方法之一定义):

如果 SQL Server 在 OUTPUT 子句中检测到了此类列,将引发错误 4186。

  • 子查询。

  • 执行用户数据访问或系统数据访问或者被认为执行此种访问的用户定义函数。

  • 定义中包含执行用户数据访问或系统数据访问的用户定义函数的计算列。

5、将从 OUTPUT 子句返回的数据插入表

在捕获嵌套的 INSERT、UPDATE、DELETE 或 MERGE 语句中 OUTPUT 子句的结果并将这些结果插入目标表时,请牢记以下信息:

整个操作是原子的。 INSERT 语句和包含 OUTPUT 子句的嵌套 DML 语句要么都执行,要么整个语句都失败。

以下限制适用于外层 INSERT 语句的目标:

  • 目标不能为远程表、视图或公用表表达式。

  • 目标不能有 FOREIGN KEY 约束,或者被 FOREIGN KEY 约束所引用。

  • 不能对目标定义触发器。

  • 目标不能参与合并复制或事务复制的可更新订阅。

对于嵌套的 DML 语句有以下限制:

  • 目标不能为远程表或分区视图。

  • 源本身不能包含 <dml_table_source> 子句。

  • 包含 <dml_table_source> 子句的 INSERT 语句中不支持 OUTPUT INTO 子句。

  • @@ROWCOUNT 返回仅由外层 INSERT 语句插入的行。

  • @@IDENTITY、SCOPE_IDENTITY 和 IDENT_CURRENT 仅返回由嵌套的 DML 语句生成的标识值,而不返回由外层 INSERT 语句生成的标识值。

  • 查询通知将语句作为单个实体进行处理,并且即使重大更改是来自外层 INSERT 语句本身,所创建的任何消息的类型也将是嵌套 DML 的类型。

  • 在 <dml_table_source> 子句中,SELECT 和 WHERE 子句不能包括子查询、聚合函数、排名函数、全文谓词、执行数据访问的用户定义函数或 TEXTPTR() 函数。

6、并行度

可将结果返回到客户端或表变量的 OUTPUT 子句将始终使用串行计划。

在兼容性级别设置为 130 或更高的数据库的上下文中,如果 INSERT…SELECT 操作使用 SELECT 语句的 WITH (TABLOCK) 提示,并且使用 OUTPUT…INTO 插入临时表或用户表,则 INSERT…SELECT 的目标表将可以进行并行操作(具体取决于子树成本)。 OUTPUT INTO 子句中引用的目标表不能进行并行操作。

7、触发器

从 OUTPUT 返回的列反映 INSERT、UPDATE 或 DELETE 语句完成之后、触发器执行之前的数据。

对于 INSTEAD OF 触发器,即使没有因为触发器的操作而发生修改,也会如同实际执行 INSERT、UPDATE 或 DELETE 那样生成返回的结果。 如果在触发器的主体内使用包含 OUTPUT 子句的语句,则必须使用表别名来引用插入的触发器和删除的表,以免使用与 OUTPUT 关联的 INSERTED 和 DELETED 表复制列引用。

如果指定了 OUTPUT 子句但未同时指定 INTO 关键字,则对于给定的 DML 操作,DML 操作的目标不能启用对其定义的任何触发器。 例如,如果在 UPDATE 语句中定义了 OUTPUT 子句,则目标表不能具有任何启用的 UPDATE 触发器。

如果设置了 sp_configure 选项 disallow results from triggers,则从触发器内调用语句时,不带 INTO 子句的 OUTPUT 子句将导致该语句失败。

8、数据类型

OUTPUT 子句支持大型对象数据类型:nvarchar(max)、varchar(max)、varbinary(max)、text、ntext、image 和 xml。 当在 UPDATE 语句中使用 .WRITE 子句修改 nvarchar(max)、varchar(max) 或 varbinary(max) 列时,如果引用了值的全部前像和后像,则将其返回。 在 OUTPUT 子句中,TEXTPTR() 函数不能作为 text、ntext 或 image 列的表达式的一部分出现。

9、权限

要求对通过 <dml_select_list> 检索或在 <scalar_expression> 中使用的任何列具有 SELECT 权限。

要求对 <output_table> 中指定的任何表具有 INSERT 权限。

10、示例

10.1、 将 OUTPUT INTO 用于 INSERT 语句

下面的示例将行插入到 T_ScrapReason 表中,并使用 OUTPUT 子句将语句的结果返回到 @MyTableVar 表变量。 由于 T_ScrapReason列使用 IDENTITY 属性定义,因此未在 INSERT 语句中为该列指定一个值。 但是,将在列 INSERTED.ScrapReasonID 内的 OUTPUT 子句中返回由数据库引擎为该列生成的值。

1
2
3
CREATE TABLE T_ScrapReason(ID SMALLINT IDENTITY(1,1) PRIMARY KEY, UnitMeasureCode  NCHAR(8),NameInfo NVARCHAR(25),ModifiedDate DATETIME);
INSERT INTO T_ScrapReason VALUES(N'Car00001',N'问界M5','2024-07-01'),(N'Car00004',N'问界M9','2024-07-01');
SELECT * FROM T_ScrapReason

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DECLARE @MyTableVar TABLE (
    NewScrapReasonID SMALLINT,
    UnitMeasureCode  NCHAR(8),
    NameInfo NVARCHAR(50),
    ModifiedDate DATETIME);
     
INSERT T_ScrapReason
    OUTPUT INSERTED.ID, INSERTED.UnitMeasureCode,INSERTED.NameInfo, INSERTED.ModifiedDate
        INTO @MyTableVar
VALUES (N'Car00002',N'宝马X5','2024-08-03'),(N'Car00003',N'蔚来ET5','2024-07-21');
   
--Display the result set of the table variable.
SELECT NewScrapReasonID,UnitMeasureCode, NameInfo, ModifiedDate FROM @MyTableVar;
 
--Display the result set of the table.
SELECT ID,UnitMeasureCode, NameInfo, ModifiedDate FROM T_ScrapReason;

10.2、将 OUTPUT 与 DELETE 语句一起使用

以下示例将删除 T_ScrapReason表中的所有行。 子句 OUTPUT DELETED.* 指定 DELETE 语句的结果(即已删除的行中的所有列)将返回到执行调用的应用程序。 后面的 SELECT 语句验证对 T_ScrapReason表所执行的删除操作的结果。

1
2
3
4
5
6
7
DELETE T_ScrapReason
OUTPUT DELETED.*
WHERE ID >=3;
   
--Verify the rows in the table matching the WHERE clause have been deleted.
SELECT COUNT(*) AS [Rows in Table] FROM T_ScrapReason WHERE ID >=3;
GO

10.3、将 OUTPUT INTO 与 UPDATE 语句一起使用

下面的示例将 T_ScrapReason 表中 NameInfo 列的更新为NameInfo=丰田皇冠。
OUTPUT 子句将返回 NameInfo 值,该值在将 UPDATE 列中的 DELETED.NameInfo 语句和 INSERTED.NameInfo 列中的已更新值应用于 @MyTableVar 表变量之前存在。

在它后面的两个 SELECT 语句返回 @MyTableVar 中的值以及 T_ScrapReason 表中更新操作的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
DECLARE @MyTableVar TABLE (
    ID INT NOT NULL,
    OldNameInfo NVARCHAR(32),
    NewNameInfo NVARCHAR(32),
    ModifiedDate DATETIME);
   
UPDATE TOP (10) T_ScrapReason
SET NameInfo = N'丰田皇冠'
OUTPUT INSERTED.ID,
       DELETED.NameInfo,
       INSERTED.NameInfo,
       INSERTED.ModifiedDate
INTO @MyTableVar;
   
--Display the result set of the table variable.
SELECT ID,OldNameInfo,NewNameInfo, ModifiedDate FROM @MyTableVar;
 
--Display the result set of the table.
SELECT ID,UnitMeasureCode, NameInfo, ModifiedDate FROM T_ScrapReason;

10.4、在 UPDATE 语句中使用包含 from_table_name 的 OUTPUT INTO

以下示例针对 T_ScrapReason表更新NameInfo 列。 OUTPUT INTO 子句返回所更新表 (T_ScrapReason) 中的值以及 T_UnitMeasure 表中的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DECLARE @MyTestVar TABLE (
    ID INT NOT NULL,
    UnitMeasureCode NCHAR(8) NOT NULL,
    OldNameInfo NVARCHAR(32),
    NewNameInfo NVARCHAR(32),
    ProductName NVARCHAR(32)NOT NULL);
   
UPDATE T_ScrapReason
SET NameInfo = T2.NameInfo
OUTPUT DELETED.ID,
       DELETED.UnitMeasureCode,
       DELETED.NameInfo,
       INSERTED.NameInfo,
       T2.NameInfo
    INTO @MyTestVar
FROM T_ScrapReason AS T1
    INNER JOIN T_UnitMeasure AS T2 ON T2.UnitMeasureCode = T1.UnitMeasureCode;
   
SELECT ID, UnitMeasureCode, OldNameInfo,NewNameInfo, ProductName FROM @MyTestVar;
 
SELECT * FROM T_ScrapReason;

10.5、在 DELETE 语句中使用包含 from_table_name 的 OUTPUT INTO

以下示例将按照在 ProductProductPhoto 语句的 FROM 子句中所定义的搜索条件删除 DELETE 表中的行。 OUTPUT 子句返回所删除表(DELETED.ProductID、DELETED.ProductPhotoID)中的列以及 Product 表中的列。 在 FROM 子句中使用该表来指定要删除的行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DECLARE @MyTableVar TABLE (
    ID INT NOT NULL,
    UnitMeasureCode NCHAR(8) NOT NULL,
    NameInfo NVARCHAR(32),
    Code NCHAR(8) NOT NULL,
    ProductName NVARCHAR(32)NOT NULL);
 
DELETE T_ScrapReason
OUTPUT DELETED.ID,
       DELETED.UnitMeasureCode,
       DELETED.NameInfo,
       T2.UnitMeasureCode,
       T2.NameInfo     
    INTO @MyTableVar
FROM T_ScrapReason AS T1
    INNER JOIN T_UnitMeasure AS T2 ON T2.UnitMeasureCode = T1.UnitMeasureCode;;
   
--Display the results of the table variable.
SELECT ID, UnitMeasureCode, NameInfo,Code, ProductName FROM @MyTableVar;
 
SELECT * FROM T_ScrapReason;

10.6、在 INSTEAD OF 触发器中使用 OUTPUT

下例在触发器中使用 OUTPUT 子句来返回触发器操作的结果。 首先,创建一个 ScrapReason 表的视图,然后对该视图定义 INSTEAD OF INSERT 触发器,从而使用户只修改基表的 Name 列。 由于 ScrapReasonID 列在基表中是 IDENTITY 列,因此触发器忽略用户提供的值。 这允许数据库引擎自动生成正确的值。 同样,用户为 ModifiedDate 提供的值也被忽略并设置为正确的日期。 OUTPUT 子句返回实际插入 ScrapReason 表中的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CREATE TRIGGER dbo.TR_ScrapReason ON dbo.T_ScrapReason
INSTEAD OF INSERT
AS
BEGIN
    --ID is not specified in the list of columns to be inserted
    --because it is an IDENTITY column.
    INSERT INTO T_ScrapReason (UnitMeasureCode,NameInfo,ModifiedDate)
    OUTPUT INSERTED.ID,
        INSERTED.UnitMeasureCode,
        INSERTED.NameInfo,
        INSERTED.ModifiedDate
    SELECT UnitMeasureCode,NameInfo,GETDATE()
    FROM INSERTED;
END
GO
 
INSERT INTO T_ScrapReason (UnitMeasureCode,NameInfo,ModifiedDate)
VALUES (N'Car00001',N'宝马X5','2024-08-03');
GO

10.7、插入从 OUTPUT 子句返回的数据

下面的示例捕获从 OUTPUT 语句的 MERGE 子句返回的数据,并将这些数据插入另一个表。 MERGE 语句根据在 T_UnitMeasure 表中NameInfo列信息更新 T_ScrapReason 表的 NameInfo列。 本示例捕获已删除的行并将这些行插入另一个表 T_ZeroInventory中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CREATE TABLE T_ZeroInventory (RemovedOnDate DATETIME,UnitMeasureCode NCHAR(8) NOT NULL);
GO
 
INSERT INTO T_ZeroInventory(RemovedOnDate,UnitMeasureCode)
SELECT GETDATE(),UnitMeasureCode
FROM (
    MERGE T_ScrapReason AS T1
    USING T_UnitMeasure AS T2
        ON (T1.UnitMeasureCode = T2.UnitMeasureCode)
    WHEN MATCHED THEN
      DELETE
    WHEN NOT MATCHED THEN
      INSERT (UnitMeasureCode,NameInfo,ModifiedDate) VALUES (T2.UnitMeasureCode, T2.NameInfo,GETDATE())
    OUTPUT $ACTION,
        DELETED.UnitMeasureCode
    ) AS Changes(Action,UnitMeasureCode)
WHERE Action = 'DELETE';
 
IF @@ROWCOUNT = 0
    PRINT 'Warning: No rows were inserted';
GO
 
SELECT RemovedOnDate,UnitMeasureCode FROM T_ZeroInventory;
GO

相关内容

DELETE (Transact-SQL)
INSERT (Transact-SQL)
UPDATE (Transact-SQL)
表 (Transact-SQL)
CREATE TRIGGER (Transact-SQL)
sp_configure (Transact-SQL)