INSERTED 和 DELETED 在 UPDATE 中按行一一对应,行数相等、顺序一致,但必须用主键显式连接(如 ON i.ID = d.ID),不可依赖位置匹配;OUTPUT 子句轻量高效,仅返回实际修改行,不暴露中间扫描过程。

SQL Server 的 INSERTED 和 DELETED 伪表天然支持多行操作,不需要额外循环或游标——只要你写的语句是集合式(set-based),它们就自动按行一一对应。
UPDATE 多行时,INSERTED 和 DELETED 怎么对齐?
执行 UPDATE 时,SQL Server 会为每一行生成一对快照:DELETED 存旧值,INSERTED 存新值,两表行数严格相等、顺序一致(但不保证物理顺序,不能依赖 ROW_NUMBER() 默认排序)。关键点在于:
- 必须用主键或唯一列显式关联,比如
ON i.OrderID = d.OrderID,不能靠“位置相同”做隐式匹配 - 若更新涉及 JOIN 或子查询,确保
WHERE条件不会意外过滤掉某一方的行(否则可能触发空匹配) - 在触发器中,这两张表只读且仅生命周期内有效;在
OUTPUT子句中,它们只存在于当前语句上下文
在触发器里安全比较新旧值的写法
常见错误是直接 SELECT * FROM INSERTED 然后假设它和 DELETED 行序一致。正确做法是始终显式连接:
CREATE TRIGGER tr_log_salary_change ON Employees AFTER UPDATE AS
BEGIN
INSERT INTO SalaryChangeLog (EmpID, OldSalary, NewSalary, ChangeDate)
SELECT i.EmpID, d.Salary, i.Salary, GETDATE()
FROM INSERTED i
INNER JOIN DELETED d ON i.EmpID = d.EmpID -- 必须有等值连接条件
WHERE i.Salary d.Salary; -- 避免无变化也记录
END
注意:WHERE 中的比较必须带前缀(i.Salary 和 d.Salary),否则报错 “列名不明确”;如果字段允许 NULL,要用 ISNULL(i.Salary, -1) ISNULL(d.Salary, -1) 避免三值逻辑陷阱。
OUTPUT 子句中一次性捕获多行变更结果
OUTPUT 是比触发器更轻量、更可控的多行变更捕获方式,尤其适合需要返回给应用层的场景:
- 它不走事务日志额外开销,性能更好
- 输出结果可直接插入临时表或表变量,例如:
DECLARE @changes TABLE(id INT, old_email VARCHAR(100), new_status VARCHAR(20)); INSERT INTO @changes SELECT ... FROM ... OUTPUT ... - 不能在
OUTPUT中引用其他表,只能用INSERTED、DELETED及其计算列(如GETDATE()) - 若 UPDATE 影响 0 行,
OUTPUT不返回任何结果,应用层需处理空集情况
最容易被忽略的是:当 UPDATE 语句本身包含 CTE、TOP 或子查询时,OUTPUT 仍只反映最终被修改的那些行,但 INSERTED/DELETED 不会暴露中间过程——也就是说,它们永远只反映“实际写入的数据”,不是“扫描过的数据”。这点在调试性能问题或排查漏更新时特别关键。










