Create an “Instead Of” Trigger in SQL Server

When you create a trigger in SQL Server, you have the option of firing it in conjunction with the triggering statement (i.e. the SQL statement that fired the trigger), or firing it instead of that statement.

To fire the trigger instead of the triggering statement, use INSTEAD OF argument.

This is in contrast to using the FOR or AFTER arguments. When you use those arguments, the trigger fires only when all operations specified in the triggering SQL statement have launched successfully.

Example

Create a sample table:

CREATE TABLE t1 (
    id int IDENTITY(1,1) NOT NULL,
    c1 int DEFAULT 0,
    c2 int DEFAULT 0,
    c3 int DEFAULT 0
);

Create the trigger:

CREATE TRIGGER trg_t1
ON t1
INSTEAD OF UPDATE
AS
UPDATE t1
SET c3 = c3 + 1
WHERE id IN (SELECT DISTINCT id FROM inserted);

Insert a sample row:

INSERT INTO t1 (c1, c2, c3) 
VALUES (1, 1, 1);

SELECT * FROM t1;

Here’s what we have so far:

+------+------+------+------+
| id   | c1   | c2   | c3   |
|------+------+------+------|
| 1    | 1    | 1    | 1    |
+------+------+------+------+

Now let’s run an UPDATE statement against the table (this will fire the trigger).

UPDATE t1 
SET c1 = c1 + 1
WHERE id = 1;

SELECT * FROM t1;

Result:

+------+------+------+------+
| id   | c1   | c2   | c3   |
|------+------+------+------|
| 1    | 1    | 1    | 2    |
+------+------+------+------+

As expected, the UPDATE statement in the triggering statement was replaced with the one in the trigger.

My trigger specified that any time there’s an attempted update to the table, update the c3 column instead.

Run Only When a Specific Column is Updated

You can also use the UPDATE() function to specify code to run only when a specified column is updated.

For example, we could alter our trigger as follows:

ALTER TRIGGER trg_t1
ON t1
INSTEAD OF UPDATE
AS
IF ( UPDATE(c1) )
BEGIN
UPDATE t1
SET c3 = c3 + 1
WHERE id IN (SELECT DISTINCT id FROM inserted)
END;

Now run the previous UPDATE statement again:

UPDATE t1 
SET c1 = c1 + 1
WHERE id = 1;

SELECT * FROM t1;

Result:

+------+------+------+------+
| id   | c1   | c2   | c3   |
|------+------+------+------|
| 1    | 1    | 1    | 3    |
+------+------+------+------+

Again, the c3 column is incremented.

But, now let’s try to update the c2 column:

UPDATE t1 
SET c2 = c2 + 1
WHERE id = 1;

SELECT * FROM t1;

Result:

+------+------+------+------+
| id   | c1   | c2   | c3   |
|------+------+------+------|
| 1    | 1    | 1    | 3    |
+------+------+------+------+

Nothing changes. The c3 column remains the same.

Not even the c2 column is updated. This is because, the trigger still runs instead of the triggering statement.

Run Trigger Instead of DELETE

We can modify the trigger to run instead of any DELETE statements.

ALTER TRIGGER trg_t1
ON t1
INSTEAD OF DELETE
AS
UPDATE t1
SET c3 = c3 + 1
WHERE id IN (SELECT DISTINCT id FROM deleted);

Now let’s try to delete all rows and then select all rows from the table.

DELETE FROM t1;

SELECT * FROM t1;

Result:

+------+------+------+------+
| id   | c1   | c2   | c3   |
|------+------+------+------|
| 1    | 1    | 1    | 4    |
+------+------+------+------+

Note that for this trigger to work properly, I had to query the deleted table in my trigger (as opposed to the inserted table in the previous examples).

These two tables are created and managed by SQL Server.

The deleted table stores copies of the affected rows during DELETE and UPDATE statements. During the execution of a DELETE or UPDATE statement, rows are deleted from the trigger table and transferred to the deleted table.

The inserted table stores copies of the affected rows during INSERT and UPDATE statements. During an insert or update transaction, new rows are added to both the inserted table and the trigger table. The rows in the inserted table are copies of the new rows in the trigger table.

Some Restrictions to Keep in Mind

You can define a maximum of one INSTEAD OF trigger per INSERT, UPDATE, or DELETE statement on a table or view.

You can’t define INSTEAD OF triggers on updatable views that use WITH CHECK OPTION.