inserted表仅在触发器内可见,是sql server自动创建的内存表,结构同基表,行数与操作匹配;update时须用主键join关联inserted与deleted获取新旧值对应关系,不可依赖顺序或null安全比较。

INSERTED 表只在触发器内可见,不能 SELECT * 直接用
inserted 不是真实表,也不支持 CREATE 或 DROP,它由 SQL Server 在触发器执行时自动创建并填充,作用域仅限于触发器体内部。直接写 SELECT * FROM inserted 虽然语法不报错,但若没配合 JOIN 或 WHERE,结果不可靠——比如多行更新时,你得不到确定的对应关系,甚至可能因未显式关联而漏数据。
常见错误:在 AFTER UPDATE 触发器里写 SELECT id, name FROM inserted 就完事,没和 deleted 关联,也没限定条件,导致日志记录错乱或重复。
- 必须用
JOIN(通常是INNER JOIN)或WHERE EXISTS显式关联主键/唯一列 - 别依赖行序——
inserted和deleted的行顺序不保证一致,TOP 1+ORDER BY (SELECT NULL)是无效对齐手段 - 宽表(50+ 列)下,只查需要的字段,避免
SELECT *带来内存和性能开销
UPDATE 场景下必须同时读 inserted 和 deleted,且用主键 JOIN
UPDATE 操作本质是“删旧 + 插新”,SQL Server 把旧值塞进 deleted,新值塞进 inserted,两表结构相同、行数相等,但**没有内置行映射关系**。靠什么知道哪条新记录对应哪条旧记录?只能靠主键或唯一约束列。
示例:要记录姓名变更,就得写:
SELECT d.id, d.name AS old_name, i.name AS new_name FROM deleted d INNER JOIN inserted i ON d.id = i.id;
如果主键是复合的(如 (order_id, line_no)),ON 条件必须写全:ON d.order_id = i.order_id AND d.line_no = i.line_no。
- 没主键的表?不能安全使用
inserted/deleted做差异比对——盲目按顺序匹配会出错 - 别用
LEFT JOIN inserted或RIGHT JOIN deleted,除非你明确处理 INSERT/DELETE 混合逻辑(比如在INSTEAD OF中) -
IF UPDATE(column)是判断字段是否被修改的正确方式,不是i.col != d.col——NULL 比较会返回 UNKNOWN,逻辑失效
INSERT 和 UPDATE 触发器中 inserted 表的行为差异
inserted 在不同操作类型下内容不同,但规则很清晰:
-
AFTER INSERT:只有inserted有数据,deleted为空 -
AFTER UPDATE:inserted和deleted都非空,且行数严格相等 -
AFTER DELETE:只有deleted有数据,inserted为空 - 误判典型:在
AFTER UPDATE里写IF NOT EXISTS(SELECT 1 FROM deleted)来“判断是不是 INSERT”,这永远为假——UPDATE 必然有deleted
注意:INSTEAD OF 触发器中 inserted 结构可能和视图列一致,而非基表;而 AFTER 触发器里它严格等于基表结构。
别在触发器里用游标遍历 inserted,除非真有必要
游标(DECLARE cur CURSOR FOR SELECT ... FROM inserted)能逐行处理,但代价高、易出错、难维护。绝大多数审计、同步、校验场景用集合操作(JOIN + INSERT SELECT)更高效可靠。
只有当你必须对每行做独立事务控制、调用外部存储过程、或依赖顺序执行副作用时,才考虑游标。即便如此,也得注意:
- 必须配对
OPEN/CLOSE/DEALLOCATE,否则游标资源泄漏 - 多行时,标量变量赋值(如
SELECT @id = id FROM inserted)只取最后一行,其余静默丢失 -
@@ROWCOUNT可用来判断影响行数,但不能替代集合思维——先想“怎么一次性处理所有行”,再考虑单行
真正容易被忽略的是:触发器里的 inserted 是内存表,但它承载的是完整行镜像。一次改 10 万行,它就存 10 万份副本——性能瓶颈常在这里,而不是逻辑本身。











