Instead of Trigger for Insert on all tables

Can someone please provide source on how to write a generic instead of trigger for insert for all the tables in a database. I would want to run the stored procedure that will create the instead of insert triggers for all the tables in a db .


Solution 1:

Without understanding exactly why you want an instead of trigger on every single table, and what else you plan to do in the resulting code aside from insert the supplied values into the base table (just like what would have happened if there was no instead of trigger at all), here is what I came up with. You'll note that it drops the trigger if it already exists, so you can run this multiple times in the same database without "already exists" errors. It ignores IDENTITY, ROWGUIDCOL, computed columns, and TIMESTAMP/ROWVERSION columns. Finally at the end I show how you can quickly inspect, instead of execute (which is commented out) the output script (up to 8K), and cast it to XML if you want to see more (up to 64K). No guarantees you can return the whole thing in SSMS depending on how many tables/columns there are. If you want to check it and/or run it manually you may want to create a table that stores the value - then you can pull it out with an app or what have you. Now if you want this to execute automatically you can follow Yuck's point - save this as a stored procedure and create a DDL trigger that responds to certain DDL events (CREATE TABLE etc).

SET NOCOUNT ON;

DECLARE 
    @cr char(2)       = char(13) + char(10),
    @t  char(1)       = char(9),
    @s  nvarchar(max) = N'';

;WITH t AS
(
    SELECT [object_id], 
     s = OBJECT_SCHEMA_NAME([object_id]),
     n = name
    FROM sys.tables WHERE is_ms_shipped = 0     
)
SELECT @s += 'IF OBJECT_ID(N''dbo.ioTrigger_' + t.s + '_' + t.n + ''') 
  IS NOT NULL
  BEGIN
    DROP TRIGGER dbo.[ioTrigger_' + t.s + '_' + t.n + '];
  END
G' + 'O
CREATE TRIGGER ioTrigger_' + t.s + '_' + t.n + '
    ON ' + QUOTENAME(t.s) + '.' + QUOTENAME(t.n) + '
    INSTEAD OF INSERT
AS 
BEGIN
    SET NOCOUNT ON;
    
    -- surely you must want to put some other code here?

    INSERT ' + QUOTENAME(t.s) + '.' + QUOTENAME(t.n) + '
    (
' + 
(
    SELECT @t + @t + name + ',' + @cr
        FROM sys.columns AS c
        WHERE c.[object_id] = t.[object_id]
        AND is_identity = 0
        AND is_rowguidcol = 0
        AND is_computed = 0
    AND system_type_id <> 189
    FOR XML PATH(''), TYPE
).value(N'./text()[1]', N'nvarchar(max)') + '--' 
+ @cr + @t + ')'
+ @cr + @t + 'SELECT 
' + 
(
    SELECT @t + @t + name + ',' + @cr
        FROM sys.columns AS c
        WHERE c.[object_id] = t.[object_id]
        AND is_identity = 0
        AND is_rowguidcol = 0
        AND is_computed = 0
    AND system_type_id <> 189
    FOR XML PATH(''), TYPE
).value(N'./text()[1]', N'nvarchar(max)') + '--'
+ @cr + @t + 'FROM
        inserted;
END' + @cr + 'G' + 'O' + @cr
FROM t
ORDER BY t.s, t.n;
  
SELECT @s = REPLACE(@s, ',' + @cr + '--' + @cr, @cr);

-- you can inspect at least part of the script by running the
-- following in text mode:

SELECT @s;

-- if you want to see more of the whole thing (but not necessarily
-- the whole thing), run this in grid mode and click on the result:

SELECT CONVERT(XML, @s);

A couple of caveats:

  1. I don't deal with sparse columns, xml collections, filestream etc. so if you have fancy tables you might run into complications with some of those features.

  2. the name of the trigger isn't really "protected" - you could have a schema called foo, another schema called foo_bar, and then a table in foo called bar_none and a table in foo_bar called none. This would lead to a duplicate trigger name because I use an underscore as the separator. I complained about this issue with CDC, but Microsoft closed the bug as "won't fix." Just something to keep in mind if you happen to use schemas with underscores.