SQL Server 通过 INSERTED 和 DELETED 表以集合方式实现行级逻辑:INSERT 操作仅填充 INSERTED 表,DELETE 仅填充 DELETED 表,UPDATE 同时填充两表(旧值在 DELETED、新值在 INSERTED),必须用 JOIN 关联处理多行变更,避免单变量赋值导致“Subquery returned more than 1 value”错误。

SQL Server 本身不支持真正的行级触发器,但可以通过 INSERTED 和 DELETED 临时表 + 集合操作,实现等效的“按行处理逻辑”——关键不是逐行游标,而是避免单值赋值错误、正确关联变化行。
为什么直接 SELECT INTO 变量会报错?
常见错误是写类似 SELECT @old_name = name FROM DELETED,这在批量操作时必然失败:SQL Server 不允许将多行结果赋给单个变量。错误信息通常是 Subquery returned more than 1 value。
- 单条 UPDATE/DELETE 时看似正常,一上生产批量操作就崩
-
IF UPDATE(col)只判断列是否被修改,不保证INSERTED/DELETED行数为 1 - 真正安全的做法是把变更当作集合来处理,而不是试图“取一行”
用 JOIN 替代游标:高效且兼容批量操作
绝大多数场景下,不需要游标遍历。直接在触发器里对 INSERTED 和 DELETED 做 JOIN 或 LEFT JOIN,就能定位每条变更记录的前后状态。
- UPDATE 场景:用
INSERTED i INNER JOIN DELETED d ON i.id = d.id关联新旧值 - INSERT 场景:只查
INSERTED,DELETED为空 - DELETE 场景:只查
DELETED,INSERTED为空 - 示例:记录姓名变更日志
INSERT INTO log_table (id, old_name, new_name) SELECT i.id, d.name, i.name FROM INSERTED i LEFT JOIN DELETED d ON i.id = d.id
什么时候才必须用游标?
仅当业务逻辑强制要求“逐行调用外部系统 API”或“每行执行不可集约化的存储过程”时,才考虑游标——但这已是反模式信号。
- 游标性能差,阻塞高,且容易因未正确处理
@@FETCH_STATUS导致死循环 如果真要用,必须配 - 更推荐方案:把变更数据先插入到中间表(如
#changes),再用应用层或 SQL Agent Job 分批处理 - 游标内禁止嵌套事务、不能用
TRY...CATCH捕获单行错误(整个触发器会回滚)
LOCAL FORWARD_ONLY READ_ONLY 减少开销
真正难的不是写出来,而是判断哪些逻辑能集合化、哪些必须拆解——多数所谓“行级需求”,其实只是没想清楚怎么用集合思维表达。

















