Chapter 14 - Creating and Managing Triggers![]()
![]() Triggers are methods that SQL Server provides to the application programmer and database analyst to ensure data integrity. Triggers are very useful for those databases that are going to be accessed from a multitude of different applications because they enable business rules to be enforced by the database instead of relying on the application software.
Understanding SQL Server TriggersA trigger is a special type of stored procedure that is executed by the SQL Server automatically when a particular table modification is applied by SQL Server (or hits) a given table. The most common use of a trigger is to enforce business rules in the database. Triggers are used when the standard constraints or table-based Declarative Referential Integrities (DRI) are not adequate. If a DRI constraint is enforced by SQL Server, then the trigger will not be executed because it was not needed.Triggers have a very low impact on performance to the server and are often used to enhance applications that have to do a lot of cascading operations on other tables and rows.
In SQL Server 6, Microsoft added ANSI compliant DRI statements that can be used in the CREATE TABLE statement. The sorts of rules that can be enforced by them are relatively complex; however, it makes the understanding of the table creation quite difficult. Besides the inability to perform complex business rule analysis based on values that are supplied when a trigger is executed, DRI has one important limitation: the current implementation does not permit referencing values in other databases. Although this may seem a relatively insignificant problem, it has a substantial impact on those people trying to write distributed applications that may need to check data constraints/values on other databases and servers.
Creating TriggersCreating a trigger is much like declaring a stored procedure, and it has a similar syntax, as follows:
CREATE TRIGGER [owner.]trigger_name ON [owner.]table_name FOR {INSERT, UPDATE, DELETE} [WITH ENCRYPTION] AS sql_statementsThe options for the Transact-SQL command CREATE TRIGGER are as follows:
Examining Limitations of TriggersSQL Server has some limitations on the types of SQL statements that can be executed while performing the actions of a trigger. The majority of these limitations are because the SQL cannot be rolled back (or inside a transaction), which may need to occur if the UPDATE, INSERT or DELETE that caused the trigger to execute in the first place is also rolled back.The following is a list of Transact-SQL statements that are not permitted to be in the body text of a trigger. SQL Server will reject the compilation and storing of a trigger with these statements:
Additionally, the following are limitations that should be clearly understood:
Using TriggersIn this section you will see several types of triggers being created for use. These examples aren't very sophisticated but should give you ideas on how you might implement triggers in your own environment.Triggers are fired or executed whenever a particular event occurs. In the following sections you will see the different events that can cause a trigger to be executed and some idea of what you may want to do on those events.
Using INSERT and UPDATE TriggersINSERT and UPDATE triggers are particularly useful because they can enforce referential integrity constraints and make sure that your data is valid before it enters the table. Typically INSERT and UPDATE triggers are used to verify that the data on the columns being monitored by the trigger meets the criteria required or to update timestamp columns. Triggers are used when the criteria for verification is more complex than what can be represented in a declarative referential integrity constraint.In Listing 14.1, the trigger is executed whenever a record is inserted into the SALES table or when it is modified. If the order date is not during the first 15 days of the month, the record is rejected. Listing 14.1 14_1.SQL—Sales Trigger Disallowing Specified Records.
Create Trigger Tri_Ins_Sales On SALES For INSERT, UPDATE As /* declare local variables needed */ Declare @nDayOfMonth tinyint /* Find the information about the record inserted */ Select @nDayOfMonth = DatePart( Day, I.ORD_DATE ) From SALES S, INSERTED I Where S.STOR_ID = I.STOR_ID And S.ORD_NUM = I.ORD_NUM And S.TITLE_ID = I.TITLE_ID /* Now test rejection criteria and return an error if necessary */ If @nDayOfMonth > 15 Begin /* Note: always Rollback first, you can never be sure what kind of error processing a client may do that may force locks to be held for unnecessary amounts of time */ ROLLBACK TRAN RAISERROR ( 'Orders must be placed before the 15th of the month', 16, 10 ) End Go ![]() Notice the way the INSERTED table is referred to in the previous join. This logical table is created specially by SQL Server to allow you to reference information in the record being modified. Using an alias I (as shown) makes it easy to reference the table in the join criteria specified in the Where clause. ![]()
Using DELETE TriggersDELETE triggers are typically used for two reasons. The first reason is to prevent deleting records that will have data integrity problems if they indeed are deleted (for example, they are used as foreign keys to other tables). The second reason for a DELETE trigger (which is really an extension of the first reason) is to perform a cascading delete operation that deletes children records of a master record such as deleting all the order items from a master sales record.In Listing 14.2, the trigger is executed whenever a user attempts to delete a record from the STORES table. If there are sales at that store, then the request is denied. Listing 14.2 14_2.SQL—Stores Trigger Disallowing Removal of More Than One Store
Create Trigger Tri_Del_Stores On STORES For DELETE As /* First check the number of rows modified and disallow anybody from deleting more than one store at a time */ If @@RowCount > 1 Begin ROLLBACK TRAN RAISERROR ( 'You can only delete one store at a time.', 16, 10 ) End /* declare a temp var to store the store that is being delete */ Declare @sStorID char(4) /* now get the value of the author being nuked */ Select @sStorID = D.STOR_ID From STORES S, DELETED D Where S.STOR_ID = D.STOR_ID If exists (Select * From SALES Where STOR_ID = @sStorID ) Begin ROLLBACK TRAN RAISERROR ( 'This store cannot be deleted because there are still sales valid in the SALES table.', 16, 10 ) End Go ![]() Use RAISERROR as an easy way to send the calling process or user detailed and specific information about the error to the calling process or user. RAISERROR allows you to specify error text, severity levels and state information all of which combined make for more descriptive errors for the user (it also makes it easy to write generic error-handlers in your client applications). ![]()
Using Triggers That Send E-MailOne of the better features of SQL Server is its ability to invoke behavior directly from the operating system. The sort of behavior must be predefined through SQL Server's Extended procedures, but they allow you to create incredibly powerful trigger operations. SQL Server is relatively unique in its ability to support operating system-specific features. This is achieved because it only runs on Windows NT, which has a very standardized programming interface across all of its supported hardware platforms (Intel, MIPS, Alpha and PowerPC).Triggers can call any of the extended procedures (xp_*) available to the server and any external procedures that you add to the server with the sp_addextendedproc command. In Listing 14.3, the trigger demonstrates sending e-mail when a record is deleted from the underlying AUTHORS table. Listing 14.3 14_3.SQL—Trigger Sending E-Mail to ChiefPublisher Indicating that Author Deleted From System.
Create Trigger Tri_Del_Authors_Mail On AUTHORS For DELETE As /* declare some variables to store the author's name */ Declare @sLName varchar(40), @sFName varchar(20), @sAuthor varchar(60) /* now get the value of the author being removed */ Select @sLName = D.AU_LNAME, @sFName = D.AU_FNAME From AUTHORS A, DELETED D Where A.AU_ID = D.AU_ID /* Send mail message */ Select @sAuthor = @sLName + ', ' + @sFName exec master.dbo.xp_sendmail @recipient = 'ChiefPublisher', @message = 'deleted ' + @sAuthor Go Using Nested TriggersTriggers can be nested up to 16 layers deep. However, if it is not desirable to have nested trigger operations, SQL Server can be configured to disallow them. Use the nested triggers option of sp_configure to toggle this option.
Triggers become nested when, during execution of one trigger, it modifies another table on which there is another trigger—which is therefore executed.
SQL Server cannot detect nesting that causes an infinite loop during the creation of a trigger until the situation occurs at execution time. An infinite loop could be caused by having a trigger, TRIGGER_A on TABLE_A, that executes on an update of TABLE_A, causing an update on TABLE_B. TABLE_B has a similar trigger, TRIGGER_B, that is executed on an update and causes an update of TABLE_A. Thus, if a user updates either table, then the two triggers would keep executing each other indefinitely. If SQL Server detects such an occurrence, it shuts down or cancels the trigger.
For example, suppose we have two triggers: one on the Sales table and one on the STORES table. The two triggers are defined in Listing 14.4. Listing 14.4 14_4.SQL—Two Triggers Nested if Delete Occurs on Sales Table
/* First trigger deletes stores if the sales are deleted */ Create Trigger Tri_Del_Sales On SALES For DELETE As /* Announce the trigger being executed */ Print "Delete trigger on the sales table is executing..." /* declare a temp var to store the store that is being deleted */ Declare @sStorID char(4), @sMsg varchar(40) /* now get the value of the store being deleted */ Select @sStorID = STOR_ID From DELETED /* DELETED is a fake table created by SQLServer to hold the values of records deleted */ Group By STOR_ID /* Now delete the store record */ Select @sMsg = "Deleting store " + @sStorID Print @sMsg Delete STORES Where STOR_ID = @sStorID Go /* Second trigger deletes discounts if a store is deleted */ Create Trigger Tri_Del_Stores On STORES For DELETE As /* Announce the trigger being executed */ Print "Delete trigger on the Stores table is executing..." /* declare a temp var to store the store that is being deleted */ Declare @sStorID char(4), @sMsg varchar(40) /* now get the value of the store being deleted */ Select @sStorID = sTOR_ID From DELETED /* DELETED is a fake table created by SQLServer to hold the values of records deleted */ Group By STOR_ID If @@rowcount = 0 Begin Print "No Rows affected on the stores table" Return End /* Now delete the store record */ Select @sMsg = "Deleting discounts for store " + @sStorID Print @sMsg Delete DISCOUNTS Where STOR_ID = @sStorID GoIf a Delete is executed on the Sales table (as shown in Listing 14.5), the trigger is executed on the Sales table, which in turns causes a trigger to execute on the Stores table. Listing 14.5—Results of Executing Delete on Sales Table
/*----------------------------- Delete from sales where stor_id = '8042' -----------------------------*/ Delete trigger on the sales table is executing... Deleting store 8042 Delete trigger on the Stores table is executing... Deleting discounts for store 8042 ![]() Triggers and DRI don't typically work together very well. For example, in Listing 14.5, you must first drop the Foreign Key constraint on the Discounts table for it to actually complete the delete. It is recommended that wherever possible you implement either triggers or DRI for integrity constraints. ![]()
Displaying Trigger InformationIf you need to view the behavior that is being enforced on a table due to a trigger, you must display the information that describes the triggers (if any) that a table owns. There are a number of ways of obtaining information about a trigger that is on any given table. In this section, the two most common, using SQL Enterprise Manager (SQL EM) and the system procedures sp_help and sp_depends, will be demonstrated.
Using SQL Enterprise ManagerTo view information about a trigger using the SQL Enterprise Manager, perform the following steps:
Using sp_help, sp_depends, and sp_helptextThe system procedures, sp_help, sp_depends, and sp_helptext, will provide valuable information in determining if a trigger exists, what it references, and what its actual text or source code looks like—provided the ENCRYPTION option was not used during trigger creation.
Using sp_helpsp_help is a generic system procedure that reports information about any object in the database. The following syntax can be used:
sp_help [object_name]If the object_name is omitted, SQL Server will report information on all user objects found in the sysobjects system catalog table. Sp_help is useful to determine who created a trigger and when it was created. Here is an example of the output from sp_help when used on the trigger created below, Tri_Del_Authors: Listing 14.6 Using sp_help to View Information About Tri_Del_Authors
/*----------------------------- sp_help Tri_Del_Authors -----------------------------*/ Name Owner Type When_created ------------------- ------------ ------------------ --------------- Tri_Del_Authors dbo trigger Nov 26 1995 4:37PM Data_located_on_segment ----------------------- not applicableListing 14.7 shows a more advanced trigger that will be used in the following sections. Listing 14.7 14_5.SQL—Advanced Trigger Demonstrating Information Returned from sp_helptext
/* create a basic trigger to stop anyone deleting an author that still has records titleauthor table */ Create Trigger Tri_Del_Authors On AUTHORS For DELETE As /* First check the number of rows modified and disallow anybody from removing more than one author at a time */ If @@RowCount > 1 Begin ROLLBACK TRAN RAISERROR ( 'You can only delete one author at a time.', 16, 10 ) End /* declare a temp var to store the author that is being deleted */ Declare @nAuID id /* now get the value of the author being deleted */ Select @nAuID = D.AU_ID From AUTHORS A, DELETED D /* DELETED is a fake table created by SQL Server to hold the values of records deleted */ Where A.AU_ID = D.AU_ID If exists (Select * From TITLEAUTHOR Where AU_ID = @nAuID ) Begin ROLLBACK TRAN RAISERROR ( 'This author cannot be deleted because he/ she still has valid titles.', 16, 10 ) End Go Using sp_dependssp_depends is a useful system-stored procedure that will return a database object's dependencies, such as tables, views, and stored procedures. The syntax is as follows:
sp_depends object_nameAfter adding the trigger shown in Listing 14.7, Listing 14.8 shows the output from sp_depends when run on the Authors table. Listing 14.8—Using sp_depends to View Dependency Information on the Authors Table
/*----------------------------- sp_depends authors -----------------------------*/ In the current database the specified object is referenced by the following: name type ---------------------------------------- ---------------- dbo.reptq2 stored procedure dbo.titleview view dbo.Tri_Del_Authors trigger Using sp_helptextUser defined objects, such as rules, defaults, views, stored procedures, and triggers, store their text in the system catalog table, syscomments. This table is not the most easy to read, so the sp_helptext procedure is provided to enable easier access.The syntax for sp_helptext is as follows:
sp_helptext object_name ![]() I've tried to find out the text for a stored procedure using the sp_helptext system procedure, but the only data that is returned is garbled and unreadable. ![]() The stored procedure was stored in ENCRYPTED format. Contact the creator of the procedure to get the text for the procedure. There is no way to decrypt the data. If you have lost original text and it is critical to return the information, contact Microsoft Technical Support in your region for more information.
Dropping TriggersThere are a number of reasons why you might want to remove triggers from a table or tables. For example, you might be moving into a production environment and you want to remove any triggers that were put in place to enforce good quality assurance but that were costing performance. Or you simply might want to drop a trigger so that you can replace it with a newer version.To drop a trigger, use the following syntax:
DROP TRIGGER [owner.]trigger_name[,[owner.]trigger_name...]Dropping a trigger is not necessary if a new trigger is to be created that will replace the existing one. Note also that by dropping a table, all its child-related objects, such as triggers, will also be dropped. The following example drops the trigger created above:
Drop Trigger Tri_Del_Authors From Here...In this chapter, you learned about the values of triggers and how they can be applied to enforce referential integrity in your application. In addition, you learned that triggers can be nested and that they can be used to provide more complex business rule validation than CONSTRAINTs that can be defined during table creation.Look at the following chapter for more information that may be useful in helping you write effective triggers:
|