SQL Server中帶有OUTPUT子句的INSERT,DELETE,UPDATE應用

OUTPUT是SQL SERVER2005的新特性,可以從數據修改語句中返回輸出,可以看作是"返回結果的DML"。

INSERT、DELETE、UPDATE均支持OUTPUT子句。

在OUTPUT子句中,可以引用特殊表inserted和deleted,使用inserted和deleted表與在觸發器中使用的非常相似。

在INSERT,DELETE,UPDATE中OUTPUT的區別

  • 對於INSERT,可以引用inserted表以查詢新行的屬性。
  • 對於DELETE,可以引用deleted表以查詢舊行的屬性。
  • 對於UPDATE,使用deleted表查詢被更新行在更改前的屬性,用inserted表標識被更新行在更改後的值。

輸出方式:

  • 輸出給調用方(客戶端應用程序)
  • 輸出給表

一、應用:

1、帶有OUTPUT的INSERT的應用

對於包含自增列的表執行多行insert語句,同時想知道新的標識值時,在INSERT中使用OUTPUT子句非常方便。

1、對於單行INSERT語句,這不成問題:SCOPE_IDENTITY函數即可實現。SCOPE_IDENTITY函數:返回為當前會話和當前作用域中的任何表最後生成的標識值。

-- Generating Surrogate Keys for Customers  
USE tempdb;  
GO  
IF OBJECT_ID('dbo.CustomersDim') IS NOT NULL  
  DROP TABLE dbo.CustomersDim;  
GO  

CREATE TABLE dbo.CustomersDim  
(  
  KeyCol      INT          NOT NULL IDENTITY PRIMARY KEY,  
  CustomerID  NCHAR(5)     NOT NULL,  
  CompanyName NVARCHAR(40) NOT NULL,  

);  

-- Insert New Customers and Get their Surrogate Keys  
DECLARE @NewCusts TABLE  
(  
  CustomerID NCHAR(5) NOT NULL PRIMARY KEY,  
  KeyCol     INT      NOT NULL UNIQUE  
);  

INSERT INTO dbo.CustomersDim(CustomerID, CompanyName)  
       OUTPUT inserted.CustomerID, inserted.KeyCol    INTO @NewCusts  
    -- OUTPUT inserted.CustomerID, inserted.KeyCol  
  SELECT CustomerID, CompanyName  FROM Northwind.dbo.Customers    WHERE Country = N'UK';  

SELECT CustomerID, KeyCol FROM @NewCusts;  
GO

註意代碼中被註釋掉的第二個OUTPUT子句,後面沒有INTO子句。如果還要輸出返回給調用方,取消註釋即可。這樣INSERT語句將包含兩個OUTPUT子句。

2、多行INSERT語句

USE AdventureWorks;  
GO 

CREATE TABLE TestTable (ID INT, TEXTVal VARCHAR(100))  

DECLARE @TmpTable TABLE (ID INT, TEXTVal VARCHAR(100))  

INSERT TestTable (ID, TEXTVal)  
    OUTPUT Inserted.ID, Inserted.TEXTVal INTO @TmpTable  
VALUES (1,'FirstVal')  
INSERT TestTable (ID, TEXTVal)  
     OUTPUT Inserted.ID, Inserted.TEXTVal INTO @TmpTable  
VALUES (2,'SecondVal')  

SELECT * FROM @TmpTable  
SELECT * FROM TestTable  

DROP TABLE TestTable  
GO

2、帶有OUTPUT的DELETE的應用.

如果要刪除數據的同時,還需要記錄日志,或者歸檔數據,在DELETE中使用OUTPUT子句在適合不過瞭。

USE AdventureWorks;  
GO  

CREATE TABLE TestTable (ID INT, TEXTVal VARCHAR(100))  DECLARE @TmpTable TABLE (ID INT, TEXTVal VARCHAR(100))  

INSERT TestTable (ID, TEXTVal)  VALUES (1,'FirstVal')  
INSERT TestTable (ID, TEXTVal)  VALUES (2,'SecondVal')  

DELETE  FROM TestTable  
   OUTPUT Deleted.ID, Deleted.TEXTVal INTO @TmpTable  
WHERE ID IN (1,2)  

SELECT * FROM @TmpTable  
SELECT * FROM TestTable  

DROP TABLE TestTable  
GO

3、帶有OUTPUT的UPDATE的應用

USE AdventureWorks;  
GO  

CREATE TABLE TestTable (ID INT, TEXTVal VARCHAR(100))  

DECLARE @TmpTable TABLE (ID_New INT, TEXTVal_New VARCHAR(100),ID_Old INT, TEXTVal_Old VARCHAR(100))  

INSERT TestTable (ID, TEXTVal)  VALUES (1,'FirstVal')  
INSERT TestTable (ID, TEXTVal)  VALUES (2,'SecondVal')  

UPDATE TestTable   SET TEXTVal = 'NewValue'  
  OUTPUT Inserted.ID, Inserted.TEXTVal, Deleted.ID, Deleted.TEXTVal INTO @TmpTable  
WHERE ID IN (1,2)  

SELECT * FROM @TmpTable  
SELECT * FROM TestTable  

DROP TABLE TestTable  
GO

4、在 UPDATE 語句中使用包含 from_table_name 的 OUTPUT INTO

以下示例使用指定的 ProductID 和 ScrapReasonID,針對 WorkOrder 表中的所有工作順序更新 ScrapReasonID 列。

OUTPUT INTO 子句返回所更新表 (WorkOrder) 中的值以及 Product 表中的值。 在 Product 子句中使用 FROM 表來指定要更新的行。

由於 WorkOrder 表上定義瞭 AFTER UPDATE 觸發器,因此需要 INTO 關鍵字。

USE AdventureWorks2012;
GO

DECLARE @MyTestVar TABLE (  
    OldScrapReasonID INT NOT NULL,   
    NewScrapReasonID INT NOT NULL,   
    WorkOrderID INT NOT NULL,  
    ProductID INT NOT NULL,  
    ProductName NVARCHAR(50)NOT NULL);  
  
UPDATE Production.WorkOrder  
SET ScrapReasonID = 4  
OUTPUT deleted.ScrapReasonID,  
       inserted.ScrapReasonID,   
       inserted.WorkOrderID,  
       inserted.ProductID,  
       p.Name  
    INTO @MyTestVar  
FROM Production.WorkOrder AS wo  
    INNER JOIN Production.Product AS p   
    ON wo.ProductID = p.ProductID   
    AND wo.ScrapReasonID= 16  
    AND p.ProductID = 733;  
  
SELECT OldScrapReasonID, NewScrapReasonID, WorkOrderID,   
    ProductID, ProductName   
FROM @MyTestVar;  
GO

4、MERGE語句

下面的示例捕獲從 OUTPUT 語句的 MERGE 子句返回的數據,並將這些數據插入另一個表。

MERGE 語句每天根據在 Quantity 表中處理的訂單更新 ProductInventory 表的 SalesOrderDetail 列。 如果產品的庫存降至 0 或更低,它還會刪除與這些產品對應的行。

本示例捕獲已刪除的行並將這些行插入另一個表 ZeroInventory 中,該表跟蹤沒有庫存的產品。

USE AdventureWorks2012;  
GO  
IF OBJECT_ID(N'Production.ZeroInventory', N'U') IS NOT NULL  
    DROP TABLE Production.ZeroInventory;  
GO  
--Create ZeroInventory table.  
CREATE TABLE Production.ZeroInventory (DeletedProductID int, RemovedOnDate DateTime);  
GO  
  
INSERT INTO Production.ZeroInventory (DeletedProductID, RemovedOnDate)  
SELECT ProductID, GETDATE()  
FROM  
(   MERGE Production.ProductInventory AS pi  
    USING (SELECT ProductID, SUM(OrderQty) FROM Sales.SalesOrderDetail AS sod  
           JOIN Sales.SalesOrderHeader AS soh  
           ON sod.SalesOrderID = soh.SalesOrderID  
           AND soh.OrderDate = '20070401'  
           GROUP BY ProductID) AS src (ProductID, OrderQty)  
    ON (pi.ProductID = src.ProductID)  
    WHEN MATCHED AND pi.Quantity - src.OrderQty <= 0  
        THEN DELETE  
    WHEN MATCHED  
        THEN UPDATE SET pi.Quantity = pi.Quantity - src.OrderQty  
    OUTPUT $action, deleted.ProductID) 
AS Changes (Action, ProductID)  
WHERE Action = 'DELETE';  
 
IF @@ROWCOUNT = 0  
PRINT 'Warning: No rows were inserted';  
GO  
 
SELECT DeletedProductID, RemovedOnDate FROM Production.ZeroInventory;

二、使用OUTPUT子句的註意事項:

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

  • 引用本地分區視圖、分佈式分區視圖或遠程表的 DML 語句。
  • 包含 EXECUTE 語句的 INSERT 語句。
  • 不能將 OUTPUT INTO 子句插入視圖或行集函數。
  • 參數或變量作為 UPDATE 語句的一部分進行瞭修改,則 OUTPUT 子句將始終返回語句執行之前的參數或變量的值而不是已修改的值

三、C#中使用cmd.ExecuteScalar(單列)、cmdExecuteReader(多行或多列)

返回單列:

using(SqlCommand cmd=new SqlCommand("INSERT INTO Mem_Basic(Mem_Na,Mem_Occ) output INSERTED.ID VALUES(@na,@occ)",con))
    {
        cmd.Parameters.AddWithValue("@na", Mem_NA);
        cmd.Parameters.AddWithValue("@occ", Mem_Occ);
        con.Open();
        int modified =(int)cmd.ExecuteScalar();
        if (con.State == System.Data.ConnectionState.Open) 
            con.Close();
       return modified;
    }

返回多行或者多列:

create table Suspension (pkey int not null identity(1, 1),
pallet_position int,
processing_pallet_pkey int,
datetime_created datetime,
datetime_updated datetime,
[this.created_by] int,
[this.updated_by] int);
using (var conn = new SqlConnection(connectionString))
{
    conn.Open();
    const string insertQuery = @"
INSERT INTO dbo.Suspension
(pallet_position, processing_pallet_pkey, datetime_created, datetime_updated,
[this.created_by], [this.updated_by]) 
OUTPUT INSERTED.pkey VALUES
(1, 2, '20141013 16:27:25.000', '20141013 16:27:25.000', 2, 2), 
(2, 2, '20141013 16:27:25.000', '20141013 16:27:25.000', 2, 2), 
(3, 2, '20141013 16:27:25.000', '20141013 16:27:25.000', 2, 2), 
(4, 2, '20141013 16:27:25.000', '20141013 16:27:25.000', 2, 2);";

    // 通過數據庫
    DataTable dt = new DataTable();
    using (SqlCommand cmd = new SqlCommand(insertQuery, conn))
    using (var insertedOutput = cmd.ExecuteReader())
    {
        dt.Load(insertedOutput);
    }
    Console.WriteLine(dt.Rows.Count); // 4

    // 通過手工讀取
    var list = new List<int>();
    using (SqlCommand cmd = new SqlCommand(insertQuery, conn))
    using (var insertedOutput = cmd.ExecuteReader())
    {
        while(insertedOutput.Read())
        {
            list.Add(insertedOutput.GetInt32(0));
        }
    }
    Console.WriteLine(list.Count); // 4

    // 通過dapper
    var ids = conn.Query<int>(insertQuery).ToList();
    Console.WriteLine(ids.Count); // 4
}

四、參考:

https://docs.microsoft.com/zh-cn/sql/t-sql/queries/output-clause-transact-sql?view=sql-server-ver15&viewFallbackFrom=sql-server-2014

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: