2. Triggers and Multistatement Transactions
Now let’s look at another example. First, you need to create a trigger to enforce referential integrity between the titles table and publishers table:
--The first statement is used to disable any previously created
--DDL triggers in the database which would prevent creating a new trigger.
DISABLE TRIGGER ALL ON titles
go
create trigger tr_titles_i on titles for insert as
declare @rows int — create variable to hold @@rowcount
select @rows = @@rowcount
if @rows = 0 return
if update(pub_id) and (select count(*)
from inserted i, publishers p
where p.pub_id = i.pub_id ) != @rows
begin
rollback transaction
raiserror ('Invalid pub_id inserted', 16, 1)
end
return
go
Next, for the trigger to take care of the referential
integrity, you might first need to disable the foreign key constraint
on the titles table with a command similar to the following:
alter table titles nocheck constraint FK__titles__pub_id__0F424F67
Note
The system-generated name for the foreign key constraint may possibly be different on your database. You can use sp_helpconstraint titles to verify the name of the foreign key constraint on the pub_id column of the titles table and use it in place of the constraint name specified in this example.
Now, run a multistatement transaction with an invalid pub_id in the second insert statement:
/* transaction inserts rows into a table */
begin tran add_titles
insert titles (title_id, pub_id, title)
values ('XX1234', '0736', 'Tuning SQL Server')
insert titles (title_id, pub_id, title)
values ('XX1235', 'abcd', 'Tuning SQL Server')
insert titles (title_id, pub_id, title)
values ('XX1236', '0877', 'Tuning SQL Server')
commit tran
go
Msg 50000, Level 16, State 1, Procedure tr_titles_i, Line 10
Invalid pub_id inserted
Msg 3609, Level 16, State 1, Line 4
The transaction ended in the trigger. The batch has been aborted.
How many rows are inserted if 'abcd' is an invalid pub_id? In this example, no rows are inserted because the ROLLBACK TRAN in the trigger rolls back all modifications made by the trigger, including the insert with the bad pub_id and all statements preceding it within the transaction. After the RETURN statement is encountered in the trigger, the rest of the batch is aborted.
Caution
You should never issue a BEGIN TRAN
statement in a trigger because a transaction is already active at the
time the trigger is executed. Rolling back to a named transaction in a
trigger is illegal and generates a runtime error, rolling back the
transaction and immediately terminating processing of the trigger and
batch. The only transaction control statements you should ever consider
including in a trigger are ROLLBACK TRAN and SAVE TRAN.
3. Using Savepoints in Triggers
Although BEGIN TRAN statements are not
recommended within a trigger, you can set a savepoint in a trigger and
roll back to the savepoint. This technique rolls back only the
operations within the trigger subsequent to the savepoint. The trigger
and transaction it is a part of are still active until the transaction
is subsequently committed or rolled back. The batch continues
processing.
Savepoints can be used to avoid a trigger’s
arbitrarily rolling back an entire transaction. You can roll back to the
named savepoint in the trigger and then issue a raiserror and
return immediately to pass the error code back to the calling process.
The calling process can then check the error status of the data
modification statement and take appropriate action, either rolling back
the transaction, rolling back to a savepoint in the transaction, or
ignoring the error and committing the data modification.
The following example shows a trigger that uses a savepoint:
alter trigger tr_titles_i on titles for insert as
declare @rows int — create variable to hold @@rowcount
select @rows = @@rowcount
if @rows = 0 return
save tran titlestrig
if update(pub_id) and (select count(*)
from inserted i, publishers p
where p.pub_id = i.pub_id ) != @rows
begin
rollback transaction titlestrig
raiserror ('Invalid pub_id inserted', 16, 1)
end
return
This trigger rolls back all work since the savepoint
and returns an error number of 50000. In the transaction, you can check
for the error number and make the decision about whether to continue the
transaction, roll back the transaction, or, if savepoints were set in
the transaction, roll back to a savepoint and let the transaction
continue. The following example rolls back the entire transaction if
either of the first two inserts fail, but it rolls back to the named
savepoint only if the third insert fails, allowing the first two to be
committed:
begin tran add_titles
insert titles (title_id, pub_id, title)
values ('XX1234', '0736', 'Tuning SQL Server')
if @@error = 50000 — roll back entire transaction and abort batch
begin
rollback tran add_titles
return
end
insert titles (title_id, pub_id, title)
values ('XX1236', '0877', 'Tuning SQL Server')
if @@error = 50000 — roll back entire transaction and abort batch
begin
rollback tran add_titles
return
end
save tran keep_first_two — set savepoint for partial rollback
insert titles (title_id, pub_id, title)
values ('XX1235', 'abcd', 'Tuning SQL Server')
if @@error = 50000 — roll back to save point, continue batch
begin
rollback tran keep_first_two
end
commit tran
Tip
When you use a savepoint inside a trigger, the
trigger does not roll back the transaction. Therefore, the batch is not
automatically aborted. You must explicitly return from the batch after
rolling back the transaction to prevent subsequent statements from
executing.
Note
Don’t forget to reenable the constraint on the titles table when you are finished testing:
alter table titles check constraint FK__titles__pub_id__0F424F67