We are building a protected INSTEAD OF UPDATE trigger to monitor and control the updates of several tables (most of MasterData tables of the system, about 150 of them). So, as much as possible (installation, updates) we try to do the code as reusable as possible (no hard-coding field names or table names).
To control the "actual" version of a row, an _ACTIVE field exist, and this goes decreasing for each new version (ACTIVE row gets ACTIVE = 1). Sorry we can not use the temporal tables feature due to backwards compatibility (plenty of business logic is built based on this feature)
Update logic includes treating the OLD and NEW lines before they affect the table, and once everything is treated, update the table; not only the affected rows, but all with same uniqueness key fields (the identification of the uniqueness fields aimed to be done dynamically too; on the following example, the where clause gets dynamically constructed on the variable @toWhereOnClause)
The "real" table suffers two actions, first a bunch of new lines are inserted with _ACTIVE = 2, second, all rows that need to be update get the _ACTIVE -= 1, leaving the newest version of the row set to 1
The problem arise as this second action, the update, needs to be created dynamically, to avoid entering the table name, and set the @toWhereOnClause manually. And this triggers once more the TRIGGER, and because it is dynamicSQL (we believe) is not captured by the TRIGGER_NESTLEVEL() = 1
Code structure is as follow:
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
yes, we know it is complex, but legacy doesn't give many options.
SO:
Is there any way to avoid the dynamicSQL trigger again the TRIGGER ??
(system is running on WindowsServer, and Azure instances, all with 120 compatibility at least)