Like AFTER triggers, each INSTEAD OF trigger gives you access to two virtual tables called Inserted and Deleted.. For a DELETE trigger, the virtual table Deleted holds all the deleted ro
Trang 1Types of Triggers
AFTER Triggers
After triggers are executed after the action of the INSERT, UPDATE, or DELETE
statement is performed Specifying AFTER is the same as specifying FOR, which is the only option available in earlier versions of SQL Server AFTER triggers can be specified only on tables
INSTEAD OF Triggers
INSTEAD OF triggers are executed in place of the usual triggering action INSTEAD OF triggers can also be defined on views with one or more base tables, where they can extend the types of updates a view can support
INSTEAD OF triggers give you the ability to evaluate the changes that would have taken place if the data modification statement had actually executed Like AFTER triggers, each INSTEAD OF trigger gives you access to two virtual tables called Inserted and Deleted For a DELETE trigger, the virtual table Deleted holds all the deleted rows, and for an INSERT trigger, the virtual table Inserted holds all the new rows (For INSTEAD
OF triggers, the Deleted table holds the rows that would have been deleted and the
Inserted table holds the rows that would have been inserted had the modification taken place.) An UPDATE trigger populates both the Inserted and Deleted tables; the Deleted table stores the old version of the rows, and the Inserted table stores the new version With all triggers, the Inserted and Deleted tables change their structure to reflect the columns and data types of the table the trigger is attached to For example, if you have a trigger on the Titles table in the Pubs database, the Inserted and Deleted tables will have the same columns as the Titles table with the same names and data types
Table 11.1 compares an INSTEAD OF trigger with an AFTER trigger Following is a simple example Suppose you want to make sure that when someone updates any prices
in the Titles table in the Pubs database, the change is no more than 10 percent of the original price You need to look at both the old price value and the new value to see whether the change is more than 10 percent You can get the old value from the Deleted table and the new value from Inserted You must also make sure that the primary key title_id doesn't change so that you can use that key to relate rows in Deleted to the
corresponding rows in Inserted
T-SQL Code 1 contains the AFTER trigger that performs the price check In SQL Server
2000, you can use the word AFTER instead of FOR; the meaning is equivalent If your application includes both kinds of triggers, you might use the word AFTER for
consistency and to clearly distinguish the two kinds of triggers And triggers you created with the word FOR in previous SQL Server releases will still work Because the AFTER trigger fires after a client updates the Titles table, the trigger must force a rollback if it
Trang 2encounters an error If no ROLLBACK TRANSACTION occurs, SQL Server has accepted the change and lets it persist
Table 11.1 Comparison of AFTER and INSTEAD OF triggers
Function AFTER trigger INSTEAD OF trigger
Applicability Tables Tables and views
Quantity per table or view Multiple per triggering
action (UPDATE, DELETE, and INSERT)
One per triggering action (UPDATE, DELETE, and INSERT)
Cascading references No restrictions apply Not allowed on tables that are
targets of cascaded referential integrity constraints
Constraint processing
Declarative referential actions
Inserted and deleted tables creation
Triggering action
Before:
Constraint processing
In place of:
After:
The triggering action inserted and deleted tables creation
Order of execution First and last execution
may be specified
Not applicable
text, ntext, and image
column references in inserted
and deleted tables
Not allowed Allowed
CREATE TRIGGER AFTER_UPDATE_TITLES
ON titles
FOR update
AS
We will assume that more than one row could have been updated The inserted and deleted tables will have to be joined on their primary key, so we can't allow changes to the primary key
IF @@rowcount = 0 RETURN
IF UPDATE(title_id) BEGIN
PRINT 'updates to primary key title_id are not allowed'
ROLLBACK TRAN
RETURN
END
IF UPDATE (price)
IF (SELECT MAX(ABS((i.price - d.price)*100/d.price))
FROM inserted i JOIN deleted d
ON i.title_id = d.title_id) > 10
BEGIN
PRINT 'Price Change not allowed'
ROLLBACK TRAN
Trang 3RETURN
END
GO
Now look at the INSTEAD OF trigger that T-SQL Code 2 shows This trigger fires before the changes happen, but the rows that would have been affected are available in the Inserted and Deleted tables The basic comparison for price changes greater than 10 percent is the same in this trigger as in the AFTER trigger
CREATE TRIGGER INSTEAD_OF_UPDATE_TITLES
ON titles
INSTEAD OF update
AS
IF @@rowcount = 0 RETURN
IF UPDATE(title_id) BEGIN
PRINT 'updates to primary key title_id are not allowed'
RETURN
END
IF UPDATE (price)
BEGIN
IF (SELECT MAX(ABS((i.price - d.price)*100/d.price))
FROM inserted i JOIN deleted d
ON i.title_id = d.title_id) > 10
BEGIN
PRINT 'Too big of a price change'
RETURN
END
Because the update passed the tests, we have to
actually do the update; this section updates only
the price field; if other columns also changed,
we would have to account for them
UPDATE t
SET price = i.price
FROM titles t join inserted i
ON i.title_id = t.title_id
WHERE t.title_id = i.title_id
END
RETURN
The most difficult aspect of using the INSTEAD OF trigger is determining what to have SQL Server do if it deems the update acceptable If you want to carry out the intended update, you could delete all the rows that match the rows in the Deleted table and insert all the rows from the Inserted table But in this case, the Titles table has foreign-key references from other tables, so you can't delete rows from Titles My simple solution, which the T-SQL Code 2 shows, works on the premise that the price was updated, so the trigger can update the price values in the Titles table based on the Inserted table's
contents If the original UPDATE had set many columns to new values, this trigger would be more difficult to write
Trang 4Both AFTER and INSTEAD OF triggers prevent updates to the primary key of the Titles table, and both triggers prevent updates to the price column that would change the price
by more than 10 percent Of course, you probably wouldn't have both these triggers on the Titles table; you would choose to have one or the other because you need to validate the price change only once
Core Difference Between INSTEAD OF and AFTER Triggers
Besides using the words INSTEAD OF in place of FOR or AFTER, INSTEAD OF triggers behave differently from AFTER triggers For example, you can have only one INSTEAD OF trigger on each table for each action (INSERT, UPDATE, and DELETE), and you can't set a firing order for INSTEAD OF triggers (In SQL Server 2000, you can specify which AFTER trigger should execute first and which should execute last.)
Also, you can't combine INSTEAD OF triggers and foreign keys with CASCADE on a table For example, if the Sales table has a foreign-key constraint that references the Titles table and specifies CASCADE as the response to DELETE operations on Titles, you'll get an error message if you try to create an INSTEAD OF trigger for DELETE on Sales However, you can have INSTEAD OF triggers for INSERT or UPDATE
Similarly, if you already have an INSTEAD OF trigger on Sales, you can't alter the table
to add a foreign-key constraint with the CASCADE action for the same data-modification operation
Another difference is that INSTEAD OF triggers can never be recursive, regardless of the setting of the Recursive Triggers database option For example, if you execute an
INSTEAD OF trigger for INSERT into Titles and the trigger performs an INSERT into Titles, the second INSERT won't invoke the INSTEAD OF trigger Instead, SQL Server will process the INSERT as if no INSTEAD OF trigger existed for INSERT, and any constraints and AFTER triggers will take effect
Although you might think INSTEAD OF triggers and AFTER triggers could be
interchangeable in some situations, the purpose and the real power of INSTEAD OF triggers is to let you update a certain class of views that aren't usually updatable In fact, you can't create an AFTER trigger on a view, but you can create an INSTEAD OF
trigger For example, you usually can't execute DELETE operations on a view that is based on a join However, you can write an INSTEAD OF DELETE trigger The trigger has access (through the Deleted table) to the rows of the view that would have been deleted had the view been a real table Similarly, in an INSTEAD OF UPDATE or
INSTEAD OF INSERT trigger, you can access the new rows through the Inserted table
Which Trigger to Pick?
The AFTER trigger might appear less efficient because it sometimes needs to undo work that has already been done If you think your table will have numerous violations that your trigger will need to correct, you might want to choose the INSTEAD OF trigger However, if the vast majority of your updates will be acceptable, the INSTEAD OF
Trang 5trigger will have more work to do and thus be less efficient In the case of AFTER
triggers, the contents of the Inserted and Deleted tables are directly available from the transaction log By the time the AFTER trigger executes, the data modification has happened, SQL Server has logged the changes, and the changed records are available internally to SQL Server In fact, when you use AFTER triggers, you can think of the Inserted and Deleted tables as views of the transaction log
When INSTEAD OF triggers fire, SQL Server hasn't yet made any changes and
consequently hasn't logged any changes SQL Server builds worktables to hold the
inserted and deleted records as if the modification had occurred Then, if the modification takes place, SQL Server must make the changes and log them, adding to the work of creating the worktables Besides the extra work, another reason INSTEAD OF triggers are the less desirable type has to do with updates, such as those in the price-changing example If many of the table's columns could be included in the SET clause in the UPDATE statement, the trigger would be cumbersome to write, and each column would have to be checked to see whether it had a different value in the Deleted or Inserted tables
Designing INSTEAD OF Triggers
The primary advantage of INSTEAD OF triggers is that they allow views that would not
be updatable to support updates A view comprising multiple base tables must use an INSTEAD OF trigger to support inserts, updates, and deletes that reference data in the tables Another advantage of INSTEAD OF triggers is that they allow you to code logic that can reject parts of a batch while allowing other parts of a batch succeed
An INSTEAD OF trigger can take actions such as ignoring parts of a batch, not
processing a part of a batch and logging the problem rows, and taking an alternative action if an error condition is encountered (Note: INSTEAD OF DELETE and
INSTEAD OF UPDATE triggers cannot be defined on a table that has a foreign key defined with a DELETE or UPDATE action.) Coding logic as part of an INSTEAD OF trigger prevents all applications accessing the data from having to reimplement the logic
In the following sequence of Transact-SQL statements, an INSTEAD OF trigger updates two base tables from a view In addition, two approaches to handling errors are shown (1) Duplicate inserts to the Person table are ignored, and the information from the insert
is logged in the PersonDuplicates table (2) Inserts of duplicates to the EmployeeTable are turned into an UPDATE statement that retrieves the current information into the EmployeeTable without generating a duplicate key violation
The Transact-SQL statements create two base tables, a view, a table to record errors, and the INSTEAD OF trigger on the view These tables separate personal and business data and are the base tables for the view
Trang 6CREATE TABLE Person
(
SSN char(11) PRIMARY KEY,
Name nvarchar(100),
Address nvarchar(100),
Birthdate datetime
)
GO
CREATE TABLE EmployeeTable
(
EmployeeID int PRIMARY KEY,
SSN char(11) UNIQUE,
Department nvarchar(10),
Salary money,
CONSTRAINT FKEmpPer FOREIGN KEY (SSN)
REFERENCES Person (SSN)
)
This view reports all relevant data from the two tables for a person:
CREATE VIEW Employee AS
SELECT P.SSN as SSN, Name, Address,
Birthdate, EmployeeID, Department, Salary
FROM Person P, EmployeeTable E
WHERE P.SSN = E.SSN
You can record attempts to insert rows with duplicate social security numbers The PersonDuplicates table logs the inserted values, the name of the user who attempted the insert, and the time of the insert
CREATE TABLE PersonDuplicates
(
SSN char(11),
Name nvarchar(100),
Address nvarchar(100),
Birthdate datetime,
InsertSNAME nchar(100),
WhenInserted datetime
)
The INSTEAD OF trigger inserts rows into multiple base tables from a single view Attempts to insert rows with duplicate social security numbers are recorded in the PersonDuplicates table Duplicate rows in the Employee Table are changed to update statements
CREATE TRIGGER IO_Trig_INS_Employee ON Employee
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
Check for duplicate Person If no duplicate, do an insert
IF (NOT EXISTS (SELECT P.SSN
FROM Person P, inserted I
WHERE P.SSN = I.SSN))
Trang 7INSERT INTO Person
SELECT SSN,Name,Address,Birthdate,Comment
FROM inserted
ELSE
Log attempt to insert duplicate Person row in PersonDuplicates table
INSERT INTO PersonDuplicates
SELECT SSN,Name,Address,Birthdate,SUSER_SNAME(),GETDATE()
FROM inserted
Check for duplicate Employee If no duplicate, do an insert
IF (NOT EXISTS (SELECT E.SSN
FROM EmployeeTable E, inserted
WHERE E.SSN = inserted.SSN))
INSERT INTO EmployeeTable
SELECT EmployeeID,SSN, Department, Salary,Comment
FROM inserted
ELSE
If duplicate, change to UPDATE so that there will not
be a duplicate key violation error
UPDATE EmployeeTable
SET EmployeeID = I.EmployeeID,
Department = I.Department,
Salary = I.Salary,
Comment = I.Comment
FROM EmployeeTable E, inserted I
WHERE E.SSN = I.SSN
END
Summary
In this chapter we covered the topic of indexed views and the advantages of using them and the differences between the indexed views and the traditional AFTER views
Triggers
The constraints we've discussed to this point?NOT NULL, CHECK, FOREIGN KEY, PRIMARY KEY, and UNIQUE?are known as declarative constraints Triggers are not declarative because they contain procedural code They're slightly less pure in the relational sense, and early DBMSs didn't support them, but today seven of the Big Eight support both declarative constraints and triggers
Portability
The exception is MySQL, which supports only PRIMARY KEY and UNIQUE as declarative constraints, and doesn't support triggers at all The gains shown throughout this section are for only seven DBMSs
In our tests, we found that triggers are slower than declarative constraints For example, firing this trigger with a compound SQL statement:
Trang 8CREATE TRIGGER Trigger1
AFTER INSERT ON Table1
REFERENCING NEW ROW AS New
FOR EACH ROW
BEGIN
IF
New.column1 NOT BETWEEN 1 AND 10
THEN
SIGNAL SQLSTATE = '23000'
END IF
END
is slower than leaving the checking up to the CHECK constraint in this table definition: CREATE TABLE Table1 (
column1 SMALLINT,
CHECK (column1 BETWEEN 1 AND 10)
.)
GAIN: 4/7
Portability
Because triggers were around long before the SQL Standard got around to requiring them, every DBMS uses slightly different syntax to define triggers A discussion of the differences is beyond the scope of this book
However, before glibly saying "declarative constraints are better than triggers," we should note that the trigger has flexibilities that might be more important to you than performance For example, a trigger statement can log an incident and return a
customized warning, while all a declarative constraint can do in the same instance is return an error with a fixed error message An old saw, which we've never heard before, goes?The more inflexible the server is, the more adaptable the client must be
Nevertheless, the trigger involves so many steps that it can't be fast Consider the steps shown in Listing 10-3
Listing 10-3 Steps involved in executing a trigger
A trigger must:
Make a savepoint
For each row: {
Perform "before each row" trigger code
Copy the row as it exists before update, to a BEFORE IMAGE
Update the row
Copy the row as it exists after update, to an AFTER IMAGE
}
At end of statement (after constraints are checked): {
Trang 9For each row: {
Perform "after each row" trigger code
If (fatal error) restore to savepoint
}
Cleanup
}
Listing 10-3 shows that a trigger does two "for each row" loops In theory the DBMS doesn't have to do the second loop if the trigger is only "before each row." In practice, though, before- and after-triggers are equally fast
At this time, there is support only for INSERT, UPDATE, and DELETE triggers (except with Informix, which also provides SELECT triggers) To make a SELECT trigger, you can employ a trick that works like this?Suppose you want to keep a log of each time that
a row from Table1, which contains the string WASHINGTON in a column named city, is retrieved The solution is to mandate that all selections must go via a view of Table1, called View1, and to define View1 with an external function call The SELECT statement would thus look like this:
SELECT * FROM View1
WHERE city = 'WASHINGTON'
The CREATE VIEW statement would look like this:
CREATE VIEW View1 AS SELECT
Table1.*,
Function1(city) AS Function_return
FROM Table1
And the CREATE FUNCTION statement would look like this:
CREATE FUNCTION Function1
(parameter1 CHAR(10)) RETURNS SMALLINT
LANGUAGE C
PARAMETER STYLE GENERAL
NOT DETERMINISTIC
RETURN NULL ON NULL INPUT
NO SQL
EXTERNAL
Finally, the external function code would look like Listing 10-4
Listing 10-4 External function code for SELECT trigger
short* export cdecl FUNCTION1 (char* c)
{
static short return_value;
HANDLE FileHandle;
HANDLE MutexHandle;
unsigned long ret;
Trang 10if (lstrcmp(c,"Washington")==0) {
MutexHandle=CreateMutex(0,TRUE,"MUTEX");
FileHandle=CreateFile(
"T",
GENERIC_WRITE,
0,
0,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
0);
SetFilePointer(FileHandle,0,0,FILE_END);
WriteFile(FileHandle,"it happened",11,&ret,0);
CloseHandle(FileHandle);
CloseHandle(MutexHandle);
return_value=ret; }
else return_value=0;
return (&return_value); }
The matter is not simple?you must persuade the DBMS that the function is unpredictable and can change the column value Otherwise, the DBMS will optimize the function call and only do it once To persuade the DBMS not to optimize, write the function in a language the DBMS doesn't understand natively, make the column input/output (in fact the column isn't touched), and above all declare the function as NOT DETERMINISTIC
A word of caution about NOT DETERMINISTIC functions In real triggers, it is possible
to call stored procedures with nondeterministic functions, but there is a peril Our favorite one triggers an action that depends on the time of day, because "for some reason" time of day can't be used in a CHECK clause Well, some people would think that reason is important?suppose the database becomes corrupt, and the recovery procedure involves rerunning a log of transactions In such a case, the recovery isn't run at the same time of day as the original transaction?and thus a transaction that succeeded before the crash will fail during the recovery Remember that some DBMSs handle functions in select lists when they open the cursor; others handle them when they fetch from the cursor
The Bottom Line: Triggers
If speed is your only concern, use declarative constraints instead of triggers
Use views with an external function call to write a SELECT trigger
Declarative constraints differ from triggers in that constraint conditions guarantee that data always fulfills a specific requirement, while trigger conditions are enforced only when data changes That is, constraints are passive requirements for correctness, while triggers are responses only to specific actions
Because trigger checking happens after constraint checking, it's tricky to change the value
of a table column within the trigger That could lead to loops (because the change would cause another constraint check) or to subversions (because the change wouldn't go