SQL Server – Generating Generic INSERT/UPDATE Trigger for Data Conformity

sql servertrigger

I am trying to generate triggers on a number of tables to force data to upper-case and to trim leading and trailing spaces when data is inserted or updated. The issue I am having is determining in the trigger a general way to tie the INSERTED pseudotable to the base table. Not all of the tables have the same PK name although many do have a unique column called _DataChanges_RowID in each table which can be used. I know that I can inspect the PK and generate teh triggers using those columns, but before I look at that, I wanted to know if there is a generic way to join the INSERTED pseudotable to the base table which is simpler and column agnostic.

DECLARE @TriggerTemplate AS VARCHAR(MAX) = 
'CREATE TRIGGER <<SchemaName>>.<<TriggerName>> ON <<FullTableName>>
AFTER INSERT, UPDATE
AS BEGIN
    IF (ROWCOUNT_BIG() = 0)
        RETURN;
    IF TRIGGER_NESTLEVEL(( SELECT object_id FROM sys.triggers WHERE QUOTENAME(name) = ''<<TriggerName>>'' ), ''AFTER'', ''DML'') < 1
    UPDATE <<FullTableName>>
    SET <<SQLColumnUpdate>>
    WHERE <<FullTableName>>._DataChanges_RowID IN (SELECT _DataChanges_RowID FROM INSERTED);
END;
GO
sp_settriggerorder <<SchemaName>>.<<TriggerName>>, ''FIRST'', ''INSERT'';
sp_settriggerorder <<SchemaName>>.<<TriggerName>>, ''FIRST'', ''UPDATE'';
GO
';

WITH cols AS 
    (
        SELECT
            FullTableName = QUOTENAME(S.name) + '.' + QUOTENAME(T.name),
            SchemaName = QUOTENAME(S.name),
            TableName = QUOTENAME(T.name),
            ColumnName = QUOTENAME(C.name)
        FROM
            sys.columns C
            INNER JOIN sys.tables T
                ON C.object_id = T.object_id
            INNER JOIN sys.schemas S
                ON T.schema_id = S.schema_id
        WHERE
            C.is_computed = 0
            AND C.system_type_id IN (
                    167,  -- varchar
                    175,  -- char
                    231,  -- nvarchar
                    239   -- nchar
                )
    )
, TablesAndColumns AS (
    SELECT
        cols.FullTableName
        , cols.TableName
        , cols.SchemaName
        , TriggerName = QUOTENAME('TRG_DATA_CONFORM_' + cols.FullTableName)
        , SQLColumnUpdate = STUFF((SELECT ', ' + c2.ColumnName + ' = UPPER(LTRIM(RTRIM(' + c2.ColumnName + ')))' 
               FROM cols c2
              WHERE c2.FullTableName = cols.FullTableName
              ORDER BY c2.ColumnName
                FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 2, '')
    FROM cols
    GROUP BY cols.FullTableName, cols.SchemaName, cols.TableName
)
SELECT FullTableName
    , SQLTrigger = REPLACE(REPLACE(REPLACE(REPLACE(@TriggerTemplate, '<<SQLColumnUpdate>>', SQLColumnUpdate), '<<TriggerName>>', TriggerName), '<<FullTableName>>', FullTableName), '<<SchemaName>>', SchemaName)
    , KeyOK = CASE WHEN EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE QUOTENAME(TABLE_SCHEMA) = SchemaName AND QUOTENAME(TABLE_NAME) = TableName AND COLUMN_NAME = '_DataChanges_RowID') THEN 1 ELSE 0 END
FROM TablesAndColumns
;

Best Answer

No, there is no generic, column-agnostic way to join the INSERTED pseudo-table to the base table. You will need to use a conventional inner join and reference the PK column (or columns if the key is composite) explicitly in the join predicate.

Therefore, if the PK column is not the same in each of the tables you are generating the triggers for, there is no way around querying metadata to find out the column name to use. You can do that using the SQL Standard-compliant (well, more or less) INFORMATION_SCHEMA views (example) or using the native SQL Server system catalogues (example).