sql server 2019 中 update 触发器必须用 after update 并正确使用 inserted 和 deleted 虚拟表:inserted 存新值、deleted 存旧值,须 join 处理多行,用 update(column) 判断字段是否被改,避免变量赋值和 null 比较错误。

直接说结论:SQL Server 2019 中创建 UPDATE 触发器,用 CREATE TRIGGER + AFTER UPDATE 即可,关键不是语法本身,而是必须理解 inserted 和 deleted 这两个虚拟表怎么用——写错它们,触发器就查不到新旧值,甚至不生效。
UPDATE 触发器必须依赖 inserted 和 deleted 表
SQL Server 在执行 UPDATE 时,会自动把旧数据放进 deleted 表、新数据放进 inserted 表。这两个表只在触发器内部可见,且结构和目标表完全一致。
-
inserted:包含更新后的新行(即SET后的值) -
deleted:包含更新前的旧行(即原记录) - UPDATE 操作必然同时有
inserted和deleted,哪怕只改了一个字段 - 不能直接 SELECT * FROM inserted 或 deleted —— 必须 JOIN 或 WHERE 关联,否则可能多行或空结果
AFTER UPDATE 和 INSTEAD OF UPDATE 别混用
绝大多数业务场景该用 AFTER UPDATE:原 UPDATE 语句先成功执行,再触发逻辑。而 INSTEAD OF UPDATE 是替代原操作,你自己得手动做 UPDATE,稍有遗漏就会丢数据。
- 审计日志、同步写另一张表、校验后抛错 → 用
AFTER UPDATE - 需要拦截并重写更新逻辑(比如把 NULL 转成默认值)→ 才考虑
INSTEAD OF UPDATE -
AFTER触发器里不能再对原表做 UPDATE(容易死循环),但可以 INSERT/DELETE 其他表 - 如果触发器里用了事务控制(
BEGIN TRAN),记得配对COMMIT或ROLLBACK,否则可能锁住表
常见错误:WHERE 条件漏写、没处理多行、误判字段是否被更新
UPDATE 可能一次改多行,inserted 和 deleted 都是结果集,不是单行变量。直接 SELECT TOP 1 或赋值给标量变量,会丢失数据。
- 别写
DECLARE @id INT; SELECT @id = id FROM inserted—— 多行时只取一行,其余静默丢弃 - 要用
JOIN inserted ON ...或WHERE EXISTS (SELECT 1 FROM inserted WHERE ...)处理集合 - 判断某字段是否真被修改,用
UPDATE(column_name)函数,而不是比较inserted.column != deleted.column(NULL 对比会出错) - 例如:防止改姓名,正确写法是
IF UPDATE(Name) BEGIN ROLLBACK; THROW 50000, 'Name 不允许修改', 1; END
一个可用的审计日志触发器示例
假设你有一张 Products 表,想记录每次价格变动:
CREATE TRIGGER trg_ProductPriceAudit
ON Products
AFTER UPDATE
AS
BEGIN
IF UPDATE(Price)
BEGIN
INSERT INTO PriceAuditLog (ProductID, OldPrice, NewPrice, UpdatedAt)
SELECT
d.ProductID,
d.Price AS OldPrice,
i.Price AS NewPrice,
GETDATE()
FROM deleted d
INNER JOIN inserted i ON d.ProductID = i.ProductID;
END
END;
注意这里没用变量、没用 TOP、没忽略 NULL,靠 JOIN 确保每行都处理。如果 Products 主键不是 ProductID,就得换成对应列名。
真正麻烦的从来不是写第一行 CREATE TRIGGER,而是后续维护时发现触发器在批量更新下漏日志、或某个字段为 NULL 导致整个触发器报错中断 —— 这些问题不会在单行测试里暴露出来。











