在 `INSERT ... SELECT` 批次寫入多筆資料時,傳統方法面臨困境:
**OUTPUT 子句** 完美解決:單一陳述式批次插入,**同步回傳所有生成的 Identity 值** (含計算欄、預設值、Timestamp)。
需求: 供應商提供完整產品清單 (`ProductsToInsert`),需將**不存在**的產品寫入主檔 (`Product`),並取得新產品的 `ProductID` 以同步寫入多個關聯表 (庫存、價格、屬性)。
USE tempdb;
GO
-- 目標主檔:含 Identity 主鍵
IF OBJECT_ID('dbo.Product') IS NOT NULL DROP TABLE dbo.Product;
CREATE TABLE dbo.Product (
ProductID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
[Name] NVARCHAR(50) NOT NULL,
ProductNumber NVARCHAR(25) NOT NULL,
ListPrice MONEY NOT NULL
);
CREATE UNIQUE INDEX IX_Product_ProductNumber ON dbo.Product(ProductNumber);
GO
-- 來源暫存表:含待同步資料與回寫 Identity 欄位
IF OBJECT_ID('dbo.ProductsToInsert') IS NOT NULL DROP TABLE dbo.ProductsToInsert;
CREATE TABLE dbo.ProductsToInsert (
RowID INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
[Name] NVARCHAR(50) NOT NULL,
ProductNumber NVARCHAR(25) NOT NULL,
ListPrice MONEY NOT NULL,
InsertedIdentityValue INT NULL -- 回寫欄位
);
GO
-- 模擬現有資料 (AdventureWorks 產品前 450 筆)
INSERT dbo.Product ([Name], ProductNumber, ListPrice)
SELECT TOP 450 [Name], ProductNumber, ListPrice
FROM AdventureWorks.Production.Product
ORDER BY SellStartDate, ProductID;
GO
-- 模擬來源資料 (含既有 + 新增)
INSERT dbo.ProductsToInsert ([Name], ProductNumber, ListPrice)
SELECT [Name], ProductNumber, ListPrice
FROM AdventureWorks.Production.Product;
GO
-- 1. 宣告表變數接收 OUTPUT 結果
DECLARE @NewProducts TABLE (
ProductID INT,
ProductNumber NVARCHAR(25),
[Name] NVARCHAR(50)
);
-- 2. MERGE 同步插入新產品,OUTPUT 捕獲 Identity
MERGE dbo.Product AS Target
USING (
SELECT [Name], ProductNumber, ListPrice
FROM dbo.ProductsToInsert
) AS Source
ON Target.ProductNumber = Source.ProductNumber
WHEN NOT MATCHED BY TARGET THEN
INSERT ([Name], ProductNumber, ListPrice)
VALUES (Source.[Name], Source.ProductNumber, Source.ListPrice)
OUTPUT inserted.ProductID, inserted.ProductNumber, inserted.[Name] INTO @NewProducts;
-- 3. 將捕獲的 Identity 回寫來源暫存表 (供後續同步關聯表使用)
UPDATE pti
SET InsertedIdentityValue = np.ProductID
FROM dbo.ProductsToInsert pti
JOIN @NewProducts np ON pti.ProductNumber = np.ProductNumber;
-- 4. 後續同步寫入關聯表 (範例:庫存表)
-- INSERT dbo.Inventory (ProductID, Qty)
-- SELECT ProductID, 0 FROM @NewProducts;
| 技術 | 作用 | 優勢 |
|---|---|---|
MERGE ... WHEN NOT MATCHED | 單一陳述式完成「存在則略過、不存在則插入」 | 原子性、無競爭條件、計畫穩定 |
OUTPUT inserted.* INTO @TableVar | 捕獲插入列的所有欄位 (含 Identity、計算欄、Default、Timestamp) | 零額外掃描、記憶體級、支援多欄位 |
表變數 @NewProducts | 暫存捕獲結果,批次結束自動清理 | 無 TempDB 壓力、無鎖升級、計畫快取友善 |
回寫 ProductsToInsert.InsertedIdentityValue | 建立來源資料與新 Identity 的對應關係 | 支援下游多表同步寫入、稽核追蹤 |
MERGE dbo.TargetTable AS Target
USING dbo.SourceTable AS Source
ON Target.Key = Source.Key
WHEN MATCHED AND (Target.Val <> Source.Val) THEN
UPDATE SET Target.Val = Source.Val
WHEN NOT MATCHED BY TARGET THEN
INSERT (Key, Val) VALUES (Source.Key, Source.Val)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
OUTPUT
$action AS ActionType,
inserted.Key AS NewKey, inserted.Val AS NewVal,
deleted.Key AS OldKey, deleted.Val AS OldVal
INTO @SyncAudit;
`MERGE + OUTPUT INTO` 是 SQL Server 2008+ 實現 **高效批次 Upsert + Identity 捕獲 + 多表同步** 的黃金組合。將「插入、取值、回寫、同步」壓縮為單一批次的原子操作,消除游標迴圈、暫存表維護、競爭條件,是現代 ETL、資料同步、主資料管理 (MDM) 管線的核心模式。