ptg 954 CHAPTER 30 Creating and Managing Triggers trigger and inclusion of the word AFTER in the name of the old trigger have helped accen- tuate the behavior of the AFTER trigger: the AFTER trigger executes after a data modification has taken place. NOTE Throughout the rest of this chapter, if the trigger type is not specified, you can assume that it is an AFTER trigger. The fact that an AFTER trigger fires after a data modification might seem to be a simple concept, but it is critical to understanding how it works. The AFTER trigger fires after the data modification statement completes but before the statement’s work is committed to the databases. The statement’s work is captured in the transaction log but not committed to the database until the trigger has executed and performed its actions. The trigger has the capability to roll back its actions as well as the actions of the modifica- tion statement that invoked it. This is possible because an implicit transaction exists that includes both the modification statement and trigger it fires. If the trigger does not issue a rollback, an implicit COMMIT of all the work is issued when the trigger completes. The basic syntax for creating an AFTER trigger is as follows: CREATE TRIGGER trigger_name ON table_name AFTER { INSERT | UPDATE | DELETE } AS SQL statements The AFTER trigger is the default type of DML trigger, so the AFTER keyword is optional. Listing 30.1 shows the code you use to create a trigger in the BigPubs2008 database. (You can find instructions for creating the BigPubs2008 database in the introduction chapter at the beginning of this book.) This new trigger prints a message, stating the number of rows updated by an UPDATE statement. You then execute a couple of UPDATE statements to see whether the trigger works. LISTING 30.1 An Example of a Simple AFTER Trigger CREATE TRIGGER tr_au_upd ON authors AFTER UPDATE AS PRINT ‘TRIGGER OUTPUT: ‘ +CONVERT(VARCHAR(5), @@ROWCOUNT) + ‘ rows were updated.’ GO UPDATE authors SET au_fname = au_fname Download from www.wowebook.com ptg 955 Using DML Triggers WHERE state = ‘UT’ GO TRIGGER OUTPUT: 1 rows were updated. UPDATE authors SET au_fname = au_fname WHERE state = ‘CA’ GO TRIGGER OUTPUT: 37 rows were updated. Even though you do not actually change the contents of the au_fname column (because you set it to itself), the trigger fires anyway. Listing 30.1 does not show the typical use of a trigger, but it gives you some insight into how and when a trigger fires. The fact that the trigger fires, regardless of what is updated, causes many developers to test the @@rowcount value at the beginning of the trigger code. If @@rowcount is equal to zero, the trigger can return without executing the remainder of the trigger code. This is a good tactic for opti- mizing the performance of triggers. NOTE Triggers are meant to guarantee the integrity of data. Although you can retur n result sets and messages in triggers, doing so is not recommended. The programmers who write applications that perform modifications on a table are probably not prepared to get unexpected result sets or messages when they submit data modification statements. The exception is returning an error with the RAISERROR command. If a trigger performs ROLLBACK TRAN, it should also execute RAISERROR to communicate the failure to the application. Executing AFTER Triggers You know that the AFTER trigger fires when a data modification (such as an insertion, an update, or a deletion) takes place. What about the trigger’s execution in relationship to other events, including the execution of constraints? The following events take place before an AFTER trigger executes: . Constraint processing—This includes CHECK constraints, UNIQUE constraints, and PRIMARY KEY constraints. . Declarative referential actions—These actions are defined by FOREIGN KEY constraints that ensure the proper relationships between tables. This includes cascad- ing FOREIGN KEY constraints. . Triggering action—This data modification caused the trigger to fire. The action occurs before the trigger fires, but the results are not committed to the database until the trigger completes. You need to consider this execution carefully when you design triggers. For example, if you have a constraint and trigger defined on the same column, any violations to the 30 Download from www.wowebook.com ptg 956 CHAPTER 30 Creating and Managing Triggers constraint abort the statement, and the trigger execution does not occur. For example, if you have a foreign key constraint on a table that ensures referencial integrity and a trigger that that does some validation on that same foreign key column then the trigger valida- tion will only execute if the foreign key validation is successful. Specifying Trigger Firing Order You can create more than one trigger on a table for each data modification action. In other words, you can have multiple triggers responding to an INSERT, an UPDATE, or a DELETE command. This can be useful in certain situations, but it can generate confusion because you might not know the order in which the triggers fire for the particular action. Some of the confusion has been alleviated by the fact that SQL Server 2008 allows you to specify the first and last trigger that fire for a particular action. If you have four triggers responding to updates on a given table, you can set the order for two of the triggers (first and last), but the order of the remaining two triggers remains unknown. The sp_settriggerorder procedure is the tool you use to set the trigger order. This proce- dure takes the trigger name, order value (FIRST, LAST, or NONE), and action (INSERT, UPDATE, or DELETE) as parameters. For example, you could use the following to set the firing order on the trigger used in this chapter’s simple example: sp_settriggerorder tr_au_upd, FIRST, ‘UPDATE’ The execution of this command sets the tr_au_upd trigger as the first trigger to fire when an update happens to the table on which this trigger has been placed. If an ALTER state- ment is executed against the trigger after the trigger order has been defined, the firing order is lost. The sp_settriggerorder procedure must be run again to reestablish the firing order. NOTE It is recommended that you avoid defining multiple triggers for the same event on the same table when possible. Often, it is possible to include all the logic in one trigger defined for an action. This can simplify your database and avoid the uncertainty of the firing order. Special Considerations with AFTER Triggers Following are a few other considerations for AFTER triggers: . AFTER triggers can be used on tables that also have cascading referential integrity constraints. The cascading feature, which was new to SQL Server 2000, allows you to define cascading actions when a user updates or deletes a primary key to which a foreign key points. This new feature is discussed in more detail in Chapter 24, “Creating and Managing Tables.” . WRITETEXT and TRUNCATE TABLE do not fire triggers. BCP, by default, does not fire triggers either, but the FIRE_TRIGGERS bulk copy hint can be specified to cause both AFTER and INSTEAD OF triggers to execute. Download from www.wowebook.com ptg 957 Using DML Triggers . Triggers are objects, so they must have unique names within the database. If you try to add a trigger with a name that already exists, you get an error message. You can, however, use ALTER on an existing trigger. The following restrictions apply to AFTER triggers: . AFTER triggers can be placed only on tables, not on views. . A single AFTER trigger cannot be placed on more than one table. . The text, ntext, and image columns cannot be referenced in the AFTER trigger logic. Using inserted and deleted Tables In most trigger situations, you need to know what changes were made as part of the data modification. You can find this information in the inserted and deleted tables. For the AFTER trigger, these temporary memory-resident tables contain the rows modified by the statement. With the INSTEAD OF trigger, the inserted and deleted tables are actually temporary tables created on-the-fly. The inserted and deleted tables have identical column structures and names as the tables that were modified. Consider running the following statement against the BigPubs2008 database: UPDATE titles SET price = $15.05 WHERE type LIKE ‘%cook%’ When this statement is executed, a copy of the rows to be modified is recorded, along with a copy of the rows after the modification. These copies are available to the trigger in the deleted and inserted tables. If you want to be able to see the contents of the deleted and inserted tables for testing purposes, you can create a copy of the table and then create a trigger on that copy (see Listing 30.2). You can perform data modification statements and view the contents of these tables without the modification actually taking place. LISTING 30.2 Viewing the Contents of the inserted and deleted Tables Create a copy of the titles table in the BigPubs2008 database SELECT * INTO titles_copy FROM titles GO add an AFTER trigger to this table for testing purposes CREATE TRIGGER tc_tr ON titles_copy FOR INSERT, UPDATE, DELETE AS PRINT ‘Inserted:’ SELECT title_id, type, price FROM inserted 30 Download from www.wowebook.com ptg 958 CHAPTER 30 Creating and Managing Triggers PRINT ‘Deleted:’ SELECT title_id, type, price FROM deleted ROLLBACK TRANSACTION The inserted and deleted tables are available within the trigger after INSERT, UPDATE, and DELETE. Listing 30.3 shows the contents of inserted and deleted, as reported by the trigger when executing the preceding UPDATE statement. LISTING 30.3 Viewing the Contents of the inserted and deleted Tables When Updating the titles_copy Table UPDATE titles_copy SET price = $15.05 WHERE type LIKE ‘%cook%’ Inserted: title_id type price TC7777 trad_cook 15.05 TC4203 trad_cook 15.05 TC3218 trad_cook 15.05 MC3021 mod_cook 15.05 MC2222 mod_cook 15.05 Deleted: title_id type price TC7777 trad_cook 14.3279 TC4203 trad_cook 14.595 TC3218 trad_cook 0.0017 MC3021 mod_cook 15.894 MC2222 mod_cook 14.9532 NOTE In SQL Server 2008, an error message is displayed after a rollback is initiated in a trig- ger. The error message indicates that the transaction ended in the trigger and that the batch has been aborted. Prior to SQL Server 2005, an error message was not dis- played when a rollback was encountered in the trigger. The nature of the inserted and deleted tables enables you to determine the action that fired the trigger. For example, when an INSERT occurs, the deleted table is empty because there were no previous values prior to the insertion. Table 30.1 shows the DML triggering events and the corresponding contents in the deleted and inserted tables. Download from www.wowebook.com ptg 959 Using DML Triggers NOTE Triggers do not fire on a row-by-row basis. One common mistake in coding triggers is to assume that only one row is modified. However, triggers are set-based. If a single statement affects multiple rows in the table, the trigger needs to handle the processing of all the rows that were affected, not just one row at a time. One common approach to dealing with the multiple rows in a trigger is to place the rows in a cursor and then process each row that was affected, one at a time. This approach works, but it can have an adverse effect on the performance of the trigger. To keep your trigger execution fast, you should try to use rowset-based logic instead of cursors in triggers when possible. Rowset-based logic will typically join to the inserted or deleted table that are available to a trigger. You can join these tables to other tables that are being manipulated by the trigger. For example, a trigger on a Job table can update a related employee table with rowset-based logic such as : UPDATE employee SET employee.job_lvl = i.min_lvl FROM inserted i WHERE employee.emp_id = i.emp_id This kind of logic will allow the trigger update to work correctly if one job record is changed or many job rows are changed at once. This is much more efficient than load- ing all of the rows from the inserted table into a cursor which updates the employee records one at a time within the cursor loop. Checking for Column Updates The UPDATE() function is available inside INSERT and UPDATE triggers. UPDATE() allows a trigger to determine whether a column was affected by the INSERT or UPDATE statement that fired the trigger. By testing whether a column was actually updated, you can avoid performing unnecessary work. For example, suppose a rule mandates that you cannot change the city for an author (a silly rule, but it demonstrates a few key concepts). Listing 30.4 creates a trigger for both INSERT and UPDATE that enforces this rule on the authors table in the BigPubs2008 database. 30 TABLE 30.1 Determining the Action That Fired a Trigger Statement Contents of inserted Contents of deleted INSERT Rows added Empty UPDATE New rows Old rows DELETE Empty Rows deleted Download from www.wowebook.com ptg 960 CHAPTER 30 Creating and Managing Triggers LISTING 30.4 Using the UPDATE() Function in a Trigger CREATE TRIGGER tr_au_ins_upd ON authors FOR INSERT, UPDATE AS IF UPDATE(city) BEGIN RAISERROR (‘You cannot change the city.’, 15, 1) ROLLBACK TRAN END GO UPDATE authors SET city = city WHERE au_id = ‘172-32-1176’ Server: Msg 50000, Level 15, State 1, Procedure tr_au_ins_upd, Line 5 You cannot change the city. Listing 30.4 shows how you generally write triggers that verify the integrity of data. If the modification violates an integrity rule, an error message is returned to the client applica- tion, and the modification is rolled back. The UPDATE() function evaluates to TRUE if you update the column in the UPDATE state- ment. As shown in the preceding example, you do not have to change the value in the column for the UPDATE() function to evaluate to TRUE, but the column must be referenced in the UPDATE statement. For example, with the author update, the city column was set it to itself (the value does not change), but the UPDATE() function still evaluates to TRUE. NOTE If you created the tr_au_upd trigger on the authors table as part of the AFTER trigger example earlier in this chapter, you might have also seen the TRIGGER OUTPUT: 1 rows were updated message. This trigger was set to be the first trigger to fire, and it executes in addition to the new ins_upd trigger added in the example from this section. Now you can add a couple of INSERT statements on the authors table: INSERT authors (au_id, au_lname, au_fname, city, contract) VALUES(‘111-11-1111’, ‘White’, ‘Johnson’,’Menlo Park’, 1) Results from the previous insert Server: Msg 50000, Level 15, State 1 You cannot change the city. Download from www.wowebook.com ptg 961 Using DML Triggers The UPDATE() function evaluates to TRUE and displays the error message. This outcome is expected because the trigger was created for INSERT as well, and the IF UPDATE condition is evaluated for both insertions and updates. Now you can see what happens if you change the INSERT statement so that it does not include the city column in the INSERT: INSERT authors (au_id, au_lname, au_fname, contract) VALUES(‘111-11-2222’, ‘White’, ‘Johnson’, 1) Server: Msg 50000, Level 15, State 1 You cannot change the city. The error message is still displayed, even though the insertion was performed without the city column. This process might seem counterintuitive, but the IF UPDATE condition always returns a TRUE value for INSERT actions. The reason is that the columns have either explicit default values or implicit (NULL) values inserted, even if they are not specified. The IF UPDATE conditions see this as a change and evaluate to TRUE. If you change the tr_au_ins_upd trigger to be for UPDATE only (not INSERT and UPDATE), the insertions can take place without error. Enforcing Referential Integrity by Using DML Triggers Several options, including foreign key constraints and stored procedures, are available to enforce referential integrity, but using a trigger is still a viable alternative. A trigger provides a great deal of flexibility and allows you to customize your referential integrity solution to fit your needs. Some of the other alternatives, such as foreign keys, do not provide the same degree of customization. TIP In a database environment in which multiple databases are used with related data, a trigger can be invaluable for enforcing referential integrity. The trigger can span data- bases, and it can ensure that data rows inserted into a table in one database are valid based on rows in another database. Listing 30.5 shows how to re-create and populate the customers and orders tables in the sample BigPubs2008 database. LISTING 30.5 Creating and Populating the customers and orders Tables if exists (select * from sysobjects where id = object_id(‘orders’) and sysstat & 0xf = 3) drop table orders GO if exists (select * from sysobjects 30 Download from www.wowebook.com ptg 962 CHAPTER 30 Creating and Managing Triggers where id = object_id(‘customers’) and sysstat & 0xf = 3) drop table customers GO CREATE TABLE customers (customer_id INT PRIMARY KEY NOT NULL, customer_name NVARCHAR(25) NOT NULL, customer_comments NVARCHAR(22) NULL) CREATE TABLE orders (order_id INT PRIMARY KEY NOT NULL, customer_id INT, order_date DATETIME, CONSTRAINT FK_orders_customers FOREIGN KEY (customer_id) REFERENCES customers (customer_id)) INSERT customers (customer_id, customer_name, customer_comments) VALUES(1, ‘Hardware Suppliers AB’,’Stephanie is contact.’) INSERT customers (customer_id, customer_name, customer_comments) VALUES(2, ‘Software Suppliers AB’,’Elisabeth is contact.’) INSERT customers (customer_id, customer_name, customer_comments) VALUES(3, ‘Firmware Suppliers AB’,’Mike is contact.’) INSERT orders (order_id, customer_id, order_date) VALUES(100, 1, GETDATE()) INSERT orders (order_id, customer_id, order_date) VALUES(101, 1, GETDATE()) INSERT orders (order_id, customer_id, order_date) VALUES(102, 1, GETDATE()) SELECT * FROM customers SELECT * FROM orders customer_id customer_name customer_comments 1 Hardware Suppliers AB Stephanie is contact. 2 Software Suppliers AB Elisabeth is contact. 3 Firmware Suppliers AB Mike is contact. order_id customer_id order_date 100 1 2009-06-17 05:16:49.233 101 1 2009-06-17 05:16:49.233 102 1 2009-06-17 05:16:49.233 When foreign key constraints are used to enforce referencial integrity, they prohibit several different types of changes to the data in the related tables. These restrictions Download from www.wowebook.com ptg 963 Using DML Triggers ensure the that the integrity of the relationships is maintained. For example, the FOREIGN KEY constraint FK_orders_customers on the orders table prohibits the following: . Inserting rows into the orders table for customer numbers that don’t exist in the customers table . Updating the orders table by changing the customer number to values that don’t exist in the customers table . Deleting rows in the customers table for which orders exist . Updating the customers table by changing the customer number for which orders exist You might want a cascading action instead of prohibiting the deletion or update of rows on the customers table. This would include automatically cascading the DELETE or UPDATE statement executed on the customers table to the related orders table. You can do this by using triggers. Cascading Deletes A cascading delete is relatively simple to create. Listing 30.6 shows a cascading delete trigger for the customers table. TIP SQL Server 2000 added a feature that allows you to define cascading actions on a FOREIGN KEY constraint. When defining the constraints on a table, you can use the ON UPDATE CASCADE or ON DELETE CASCADE clause, which causes changes to the primary key of a table to cascade to the related foreign key tables. Refer to Chapter 26, “Implementing Data Integrity,” for further information on this option. LISTING 30.6 A Cascading Delete for the customers Table CREATE TRIGGER cust_del_orders ON customers FOR DELETE AS IF @@ROWCOUNT = 0 RETURN DELETE orders FROM orders o , deleted d WHERE o.customer_id = d.customer_id IF @@ERROR <> 0 BEGIN RAISERROR (‘ERROR encountered in cascading trigger.’, 16, 1) 30 Download from www.wowebook.com . fire for the particular action. Some of the confusion has been alleviated by the fact that SQL Server 2008 allows you to specify the first and last trigger that fire for a particular action trad_cook 14.595 TC3218 trad_cook 0.0017 MC3021 mod_cook 15.894 MC2222 mod_cook 14.9532 NOTE In SQL Server 2008, an error message is displayed after a rollback is initiated in a trig- ger. The error. also have cascading referential integrity constraints. The cascading feature, which was new to SQL Server 2000, allows you to define cascading actions when a user updates or deletes a primary