sql-server - 避免使用动态 SQL 在 UPDATE 触发器中递归
问题描述
我们正在构建一个受保护的 INSTEAD OF UPDATE 触发器来监控和控制几个表的更新(系统的大部分 MasterData 表,其中大约 150 个)。因此,尽可能(安装、更新)我们尝试使代码尽可能可重用(没有硬编码字段名称或表名称)。
为了控制行的“实际”版本,存在一个 _ACTIVE 字段,并且每个新版本都会减少(ACTIVE 行获得 ACTIVE = 1)。抱歉,由于向后兼容性,我们无法使用时态表功能(大量业务逻辑是基于此功能构建的)
更新逻辑包括在影响表之前处理 OLD 和 NEW 行,一旦处理完所有内容,更新表;不仅受影响的行,而且都具有相同的唯一性关键字段(唯一性字段的识别也旨在动态完成;在以下示例中,where 子句在变量 @toWhereOnClause 上动态构建)
“真实”表有两个动作,首先插入一堆新行,_ACTIVE = 2,其次,所有需要更新的行都得到 _ACTIVE -= 1,将最新版本的行设置为 1
出现问题是因为需要动态创建第二个操作,即更新,以避免输入表名,并手动设置 @toWhereOnClause。这会再次触发 TRIGGER,并且因为它是动态 SQL(我们相信)不会被 TRIGGER_NESTLEVEL() = 1 捕获
代码结构如下:
CREATE OR ALTER TRIGGER [schema].[triggerName] ON [schema].table
INSTEAD OF UPDATE
AS
BEGIN
SET @tableName = '[schema].[table]' // only line to modify for diferent tables
//TRIGGER PREPARATION
SET @schema = (SELECT SUBSTRING(@tableName, 1, (CHARINDEX('].', @tableName))))
SET @table = (SELECT SUBSTRING(@tableName, (CHARINDEX('[', @tableName, 3)), LEN(@tableName)))
SET @fieldNameS = (SELECT + ',' + QUOTENAME(COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @schema
AND TABLE_NAME = @table
ORDER BY ORDINAL_POSITION
FOR XML path(''));
SET @uniqueFieldSelector = (SELECT +' AND LeftTable.'+ COLUMN_NAME + ' = #INSERTED.' + COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE TABLE_NAME = @tableName
AND COLUMN_NAME NOT LIKE '%Active%'
FOR XML PATH(''))
SET @toWhereOnClause = (SELECT (SUBSTRING(@uniqueFieldSelector, 5, LEN(@uniqueFieldSelector))))
// DUPLICATE TRIGGER TABLES INTO TEMP TABLE TO WORK ON NEW AND OLD LINES
SELECT * INTO #INSERTED FROM INSERTED -- Can't modify logic table values (INSERTED), we put it in a temp table
SELECT * INTO #DELETED FROM DELETED
// SEVERAL INSTRUCTIONS TO TREAT THE OLD AND NEW LINES (not shown here) AND CALCULATE IF THE UPDATE IS LEGAL (@CONTINUE_TRIGGER)
...
// REAL UPDATE
IF TRIGGER_NESTLEVEL() = 1 AND @CONTINUE_TRIGGER = TRUE
--https://stackoverflow.com/questions/1529412/how-do-i-prevent-a-database-trigger-from-recursing
BEGIN
SET @statementINSERT = N'INSERT INTO' + @tableName + '( ... )
SELECT ... FROM #INSERTED ';
EXECUTE sp_executesql @statementINSERT
SET @statementUPDATE = N'UPDATE TheRealTable
SET TheRealTable._ACTIVE -= 1
FROM ' + @tableName + ' AS TheRealTable
INNER JOIN #INSERTED ON ' + @toWhereOnClause;
EXECUTE sp_executesql @statementUPDATE
END
END
是的,我们知道它很复杂,但传统并没有提供太多选择。
所以:
有什么办法可以避免再次触发动态SQL 触发器?
(系统运行在 WindowsServer 和 Azure 实例上,至少兼容 120)
解决方案
推荐阅读
- c# - 为什么剂量 Request.Form["key"] 不起作用?
- javascript - 当浏览器重新加载页面时,如何将 POST 请求从 chrome 扩展(不点击)发送到 Flask?
- kubernetes - 使用 yaml 文件部署 golang 项目
- javascript - 尝试在 Android Webview 中使用 javascript 与网站“同步”交互
- php - Octobercms - 如何根据后端表单中的输入获取数据并更新其余输入字段
- c# - .NET Core DI,将参数传递给构造函数的方法
- spring-boot - JPA 集成测试的模拟安全上下文
- html - 选择下拉菜单未在 ng-model 中设置值
- html - CSS, inject line break after certain number of symbols
- swift - 如何在带有动画的 NSStackView 中添加视图?