觸發器永遠要假設 INSERTED/DELETED 包含多筆資料。 批次匯入、合併陳述式、應用程式批次更新都會觸發多筆觸發。單筆處理邏輯會導致資料不一致或效能災難。
CREATE TRIGGER [TRI_TableName] ON [TableName]
FOR INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON; -- 減少網路傳輸
-- 1. 宣告變數
DECLARE
@CHECK INT, -- 檢查標記
@TODAY DATETIME, -- 共用變數
@Field1 INT, @Field2 INT, -- 基表欄位變數
@Field3 VARCHAR(20), @FieldN INT,
@Table1_Field1 INT, -- 跨表查詢變數
@TableN_FieldM INT;
-- 2. 定義游標遍歷 INSERTED
DECLARE cur_Trigger CURSOR FOR
SELECT Field1, Field2, Field3, FieldN
FROM INSERTED; -- 關鍵:從 INSERTED 讀取所有異動行
-- 3. 開啟游標並取第一筆
SET @TODAY = CONVERT(VARCHAR(10), GETDATE(), 120);
OPEN cur_Trigger;
FETCH NEXT FROM cur_Trigger INTO @Field1, @Field2, @Field3, @FieldN;
-- 4. 迴圈處理每一筆
WHILE (@@FETCH_STATUS = 0)
BEGIN
--------------------------------------------------------
-- 業務規則區塊(每筆獨立判斷)
--------------------------------------------------------
-- 規則1:符合條件直接跳過此筆
IF (/* 條件1 */)
BEGIN
FETCH NEXT FROM cur_Trigger INTO @Field1, @Field2, @Field3, @FieldN;
CONTINUE;
END
-- 規則2:嚴重錯誤 → 回滾整批交易
IF (/* 條件2 */)
BEGIN
RAISERROR('業務規則違反:xxx', 16, 1);
ROLLBACK TRANSACTION;
CLOSE cur_Trigger;
DEALLOCATE cur_Trigger;
RETURN;
END
-- 規則3:跨表檢查與值衍生
SELECT TOP 1 @Table1_Field1 = Field1
FROM Table1
WHERE /* 關聯條件 */;
IF (@Table1_Field1 IS NOT NULL)
BEGIN
-- 依跨表結果更新當前處理值
SET @Field1 = @Table1_Field1;
END
-- 規則4:條件式更新基表(避免無異動寫入)
UPDATE TableName
SET
Field1 = @Field1,
Field2 = @Field2
WHERE
PrimaryKey = @PrimaryKey
AND (ISNULL(Field1,'') <> ISNULL(@Field1,'')
OR ISNULL(Field2,0) <> ISNULL(@Field2,0));
-- 取下一筆
FETCH NEXT FROM cur_Trigger INTO @Field1, @Field2, @Field3, @FieldN;
END
-- 5. 清理資源
CLOSE cur_Trigger;
DEALLOCATE cur_Trigger;
SET NOCOUNT OFF;
END
GO
| 設計點 | 說明 | 最佳實務 |
|---|---|---|
| 游標遍歷 INSERTED | 逐筆處理多筆異動 | 使用 FAST_FORWARD 唯讀順向游標提升效能 |
@@FETCH_STATUS = 0 | 迴圈終止條件 | 標準游標迴圈模式 |
| 條件式 UPDATE | WHERE ISNULL(old,'') <> ISNULL(new,'') | 避免觸發巢狀觸發器、減少日誌寫入、保持統計資料準確 |
RAISERROR + ROLLBACK | 業務驗證失敗回滾 | 錯誤嚴重度 16 為使用者可修正錯誤,觸發用戶端捕獲 |
跨表查詢 TOP 1 | 防止子查詢回傳多行錯誤 | 確保關聯條件唯一或明確取第一筆 |
| 資源釋放 | CLOSE/DEALLOCATE 在所有離開點 | 包含正常結束、RETURN、ROLLBACK 所有路徑 |
游標雖符合邏輯但有開銷,現代版本(SQL 2005+)建議優先考慮:
JOIN INSERTED 進行 UPDATE/INSERT,無需游標MERGE 陳述式:單一陳述式處理 INSERT/UPDATE/DELETEINSTEAD OF 觸發器:完全接管 DML 邏輯,適用複雜驗證CREATE TRIGGER TRI_TableName_SetBased ON TableName
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
-- 一次性處理所有異動行
UPDATE t
SET t.Field1 = i.Field1,
t.Field2 = i.Field2,
t.ModifiedDate = GETDATE()
FROM TableName t
INNER JOIN INSERTED i ON t.PrimaryKey = i.PrimaryKey
WHERE ISNULL(t.Field1,'') <> ISNULL(i.Field1,'')
OR ISNULL(t.Field2,0) <> ISNULL(i.Field2,0);
-- 跨表驗證失敗整批回滾
IF EXISTS (
SELECT 1 FROM INSERTED i
WHERE NOT EXISTS (SELECT 1 FROM RefTable r WHERE r.Key = i.RefKey)
)
BEGIN
RAISERROR('參考資料不存在', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END