ptg 984 CHAPTER 30 Creating and Managing Triggers This type of trigger is useful for controlling development and production database envi- ronments. It goes beyond the normal security measures and helps manage unwanted change. For development environments, this type of trigger enables the database adminis- trator to lock down an environment and focus all changes through that person. The previous examples include events scoped at the database level. Let’s look at an example that applies to server-level events. The script in Listing 30.15 creates a trigger scoped at the server level. It prevents changes to the server logins. When this trigger is installed, it displays a message and rolls back any login changes that are attempted. LISTING 30.15 A Server-Scoped DDL Trigger for Logins CREATE TRIGGER tr_LoginAudit ON ALL SERVER FOR CREATE_LOGIN, ALTER_LOGIN, DROP_LOGIN AS PRINT ‘You must disable the tr_LoginAudit trigger before making login changes’ ROLLBACK The DDL trigger examples we have looked at thus far have targeted specific events listed in Table 30.2. These individual events can also be referenced via an event group. Event groups are hierarchical in nature and can be referenced in DDL triggers instead of the indi- vidual events. For example, the table-level trigger from Listing 30.14 can be changed as shown in Listing 30.16 to accomplish the same result. In Listing 30.16, the DDL_TABLE_EVENTS group reference replaces the individual event references to CREATE_TABLE, ALTER_TABLE, and DROP_TABLE. LISTING 30.16 An Example of a DDL Trigger Referencing an Event Group USE [BigPubs2008] IF EXISTS (SELECT * FROM sys.triggers WHERE name = N’tr_TableAudit’ AND parent_class=0) DROP TRIGGER [tr_TableAudit] ON DATABASE go CREATE TRIGGER tr_TableAudit ON DATABASE FOR DDL_TABLE_EVENTS AS PRINT ‘You must disable the TableAudit trigger in order to change any table in this database’ ROLLBACK GO Download from www.wowebook.com ptg 985 Using DDL Triggers 30 SQL Server Books Online has an excellent diagram listing all the event groups that can be used to fire DDL triggers. Refer to the “DDL Event Groups” topic in Books Online, which shows the event groups and related DDL events they contain. Event groups simplify administration and allow for auditing at a high level. The DDL trigger examples we have looked at thus far have executed simple print state- ments. To further extend the functionality of DDL triggers, you can code them to capture event information related to the DDL trigger execution. You do this by using the EVENTDATA function. The EVENTDATA function returns an XML string that includes the time of the event, server process ID (SPID), and type of event that fired the trigger. For some events, additional information, such as the object name or T-SQL statement, is included in the XML string as well. The EVENTDATA function is essentially the replacement for the inserted and deleted tables available with DML triggers but not available with DDL triggers. It gives you information you can use to implement an auditing solution that captures changes to a data definition. This function is particularly useful in situations in which you do not want to prevent changes to your definition, but you want a record of the changes that occur. Listing 30.17 shows an auditing solution with a DDL trigger that utilizes the EVENTDATA function to capture any changes to indexes in the BigPubs2008 database. Several event data elements are selected from the EVENTDATA XML string and displayed whenever a change is made to an index. LISTING 30.17 An Example of a DDL Trigger That References an Event Group CREATE TRIGGER tr_ddl_IndexAudit ON DATABASE FOR CREATE_INDEX, ALTER_INDEX, DROP_INDEX AS DECLARE @EventData XML Capture event data from the EVENTDATA function SET @EventData = EVENTDATA() Select the auditing info from the XML stream SELECT @EventData.query (‘’data(/EVENT_INSTANCE/PostTime)’’) AS [Event Time], @EventData.query (‘’data(/EVENT_INSTANCE/EventType)’’) AS [Event Type], @EventData.query (‘’data(/EVENT_INSTANCE/ServerName)’’) AS [Server Name], @EventData.query (‘’data(/EVENT_INSTANCE/TSQLCommand/CommandText)’’) AS [Command Text] GO To test the DDL trigger in Listing 30.17, you can run the following statement to create an index on the titles table in the BigPubs2008 database: Download from www.wowebook.com ptg 986 CHAPTER 30 Creating and Managing Triggers CREATE NONCLUSTERED INDEX [nc_titles_type] ON [dbo].[titles] ( [type] ASC ) The INDEX CREATE statement completes successfully, and the event-specific information appears in the Results pane. You can further extend the auditing capabilities of this type of DDL trigger by writing the results to an audit table. This would give you a quick way of tracking changes to database objects. This type of approach dramatically improves change control and reporting on database changes. NOTE DDL triggers can also execute managed code based on the CLR. This topic is dis- cussed in the section “Using CLR Triggers,” later in this chapter. Managing DDL Triggers The administration of DDL triggers is similar to the administration of DML triggers, but DDL triggers are located in a different part of the Object Explorer tree. The reason is that DDL triggers are scoped at the server or database level, not at the table level. Figure 30.3 shows the Object Explorer tree and the nodes related to DDL triggers at both the server and database levels. The tr_TableAudit trigger you created earlier in this chapter is shown under the Database Triggers node. Figure 30.3 shows the options available when you right-click a database trigger in the Object Explorer tree. FIGURE 30.3 Using SSMS to manage DDL triggers. Download from www.wowebook.com ptg 987 Using DDL Triggers 30 The DDL triggers scoped at the server level are found in the Triggers node under the Server Objects node of the Object Explorer tree. (The Server Objects node is near the bottom of Figure 30.3.) You can obtain information about DDL triggers by using catalog views. These views provide a convenient and flexible means for querying database objects, including DDL triggers. Table 30.3 lists the catalog views that relate to triggers. The table includes the scope of the trigger that the view reports on and a brief description of what it returns. Listing 30.18 shows sample SELECT statements that utilize the catalog views. These state- ments use the sys.triggers and sys.server_triggers views. The SELECT against the sys.triggers table uses a WHERE clause condition that checks the parent_class column to retrieve only DDL triggers. The SELECT from sys.server_triggers does not need a WHERE clause because it inherently returns only DDL triggers. The results of each statement are shown below each SELECT in the listing. LISTING 30.18 Viewing DDL Triggers with Catalog Views DATABASE SCOPED DDL TRIGGERS select left(name,20) ‘Name’, create_date, modify_date, is_disabled from sys.triggers where parent_class = 0 TABLE 30.3 Catalog Views for DDL Triggers Catalog View Description Statements with Database-Level Scope sys.triggers All triggers, including DDL database-scoped triggers sys.trigger_events All trigger events, including those that fire DDL database- scoped triggers sys.sql_modules All SQL-defined modules, including trigger definitions sys.assembly_modules All CLR-defined modules, including database-scoped triggers Statements with Server-Level Scope sys.server_triggers Server-scoped DDL triggers sys.server_trigger_events Events that fire server-scoped triggers sys.sql_modules DDL trigger definitions for server-scoped triggers sys.server_assembly_modules CLR trigger definitions for server-scoped triggers Download from www.wowebook.com ptg 988 CHAPTER 30 Creating and Managing Triggers Name create_date modify_date is_disabled tr_TableAudit 2009-06-18 12:48:43.140 2009-06-18 12:48:43.140 0 tr_ddl_IndexAudit 2009-06-22 06:35:10.233 2009-06-22 06:35:10.233 0 SERVER SCOPED DDL TRIGGERS select left(name,20) ‘Name’, create_date, modify_date, is_disabled from sys.server_triggers Name create_date modify_date is_disabled tr_LoginAudit 2009-06-18 12:13:46.077 2005-06-18 12:13:46.077 0 Using CLR Triggers CLR triggers are triggers based on the CLR. CLR integration, which was added with SQL Server 2008, allows for database objects (such as triggers) to be coded in one of the supported .NET languages, including Visual Basic .NET and C#. The decision to code triggers and other database objects by using the CLR depends on the type of operations in the trigger. Typically, objects that have heavy computations or require references to objects outside SQL are coded in the CLR. Triggers strictly geared toward database access should continue to be coded in T-SQL. You can code both DDL and DML triggers by using a supported CLR language. Generally speaking, it is much easier to code a CLR trigger in the Visual Studio .NET Integrated Development Environment (IDE), but you can create them outside the IDE as well. Visual Studio .NET provides a development environment that offers IntelliSense, debugging facil- ities, and other user-friendly capabilities that come with a robust IDE. The .NET Framework and development environment are discussed in more detail in Chapter 4, “SQL Server and the .NET Framework.” The following basic steps are required to create a CLR trigger: 1. Create the CLR class. You code the CLR class module with references to the name- spaces required to compile CLR database objects. 2. Compile the CLR class into an assembly or a DDL file, using the appropriate language compiler. 3. Load the CLR assembly into SQL Server so that it can be referenced. 4. Create the CLR trigger that references the loaded assembly. The following listings provide examples of each of these steps. Download from www.wowebook.com ptg 989 Using CLR Triggers 30 NOTE The CLR must be enabled on your server before you can add CLR components. The CLR option is disabled by default. To enable the CLR, you use the sp_configure ‘clr enabled’, 1 T-SQL command followed by the RECONFIGURE command. You can also enable CLR integration by using SQL Server 2008 Surface Area Configuration and then choosing the Surface Area Configuration for Features and selecting the Enable CLR Integration option. Listing 30.19 contains C# code that can be used for the first step: creating the CLR class. This simple example selects rows from the inserted table. LISTING 30.19 A CLR Trigger Class Created with C# using System; using System.Data; using System.Data.Sql; using Microsoft.SqlServer.Server; using System.Data.SqlClient; using System.Data.SqlTypes; using System.Xml; using System.Text.RegularExpressions; public class clrtriggertest { public static void showinserted() { SqlTriggerContext triggContext = SqlContext.TriggerContext; SqlConnection conn = new SqlConnection (“context connection = true”); conn.Open(); SqlCommand sqlComm = conn.CreateCommand(); SqlPipe sqlP = SqlContext.Pipe; SqlDataReader dr; sqlComm.CommandText = “SELECT pub_id, pub_name from inserted”; dr = sqlComm.ExecuteReader(); while (dr.Read()) sqlP.Send((string)dr[0] + “, “ + (string)dr[1]); } } The CLR class in Listing 30.19 needs to be compiled so that SQL Server can use it. The compiler for C# is located in the .NET Framework path, which is Download from www.wowebook.com ptg 990 CHAPTER 30 Creating and Managing Triggers C:\WINDOWS\Microsoft.NET\Framework\version by default. The last part of the path, version, is the number of the latest version installed on your machine. For simplicity’s sake, you can add the full .NET Framework path to your path variable in the Advanced tab of your System Properties dialog. If you add the .NET Framework path to your path vari- able, you can run the executable for the compiler without navigating to that location. You can save the code from Listing 30.19 in a text file named clrtriggertesting.cs. Then you can open a command prompt window and navigate to the folder where you saved the clrtriggertesting.cs file. The command shown in Listing 30.20 compiles the clrtriggertesting.cs file into clrtriggertesting.dll. This command can be run from any directory if you have added the .NET Framework path (for example, C:\WINDOWS\Microsoft.NET\Framework\v3.5) to your path variable. Without the addi- tional path entry, you need to navigate to the .NET Framework path prior to executing the command. LISTING 30.20 A CLR Trigger Class Compilation csc /target:library clrtriggertesting.cs After compiling clrtriggertesting.dll, you need to load the assembly into SQL Server. Listing 30.21 shows the T-SQL command you can execute to create the assembly for clrtriggertesting.dll. LISTING 30.21 Using CREATE ASSEMBLY in SQL Server CREATE ASSEMBLY triggertesting from ‘c:\clrtrigger\clrtriggertesting.dll’ WITH PERMISSION_SET = SAFE The final step is to create the trigger that references the assembly. Listing 30.22 shows the T-SQL commands to add a trigger on the publishers table in the BigPubs2008 database. LISTING 30.22 Creating a CLR Trigger CREATE TRIGGER tri_publishers_clr ON publishers FOR INSERT AS EXTERNAL NAME triggertesting.clrtriggertest.showinserted Listing 30.23 contains an INSERT statement to the publishers table that fires the newly created CLR trigger. Download from www.wowebook.com ptg 991 Using Nested Triggers 30 LISTING 30.23 Using an INSERT Statement to Fire a CLR Trigger INSERT publishers (pub_id, pub_name) values (‘9922’,’Sams Publishing’) The trigger simply echoes the contents of the inserted table. The output from the trigger based on the insertion in Listing 30.23 is as follows: 9922, Sams Publishing The tri_publishers trigger demonstrates the basic steps for creating a CLR trigger. The true power of CLR triggers lies in performing more complex calculations, string manipula- tions and things of this nature that the can be done much more efficiently with CLR programming languages than they can in T-SQL. NOTE For more detailed information and examples of CLR triggers, see Chapter 45. Using Nested Triggers Triggers can be nested up to 32 levels. If a trigger changes a table on which another trigger exists, the second trigger is fired and can then fire a third trigger, and so on. If the nesting level is exceeded, the trigger is canceled, and the transaction is rolled back. The following error message is returned if the nesting level is exceeded: Server: Msg 217, Level 16, State 1, Procedure ttt2, Line 2 Maximum stored procedure nesting level exceeded (limit 32). You can disable nested triggers by setting the nested triggers option of sp_configure to 0 (off): EXEC sp_configure ‘nested triggers’, 0 GO RECONFIGURE WITH OVERRIDE GO After the nested triggers option is turned off, the only triggers to fire are those that are part of the original data modification: the top-level triggers. If updates to other tables are made via the top-level triggers, those updates are completed, but the triggers on those tables do not fire. For example, say you have an UPDATE trigger on the jobs table in the BigPubs2008 database and an UPDATE trigger on the employee table as well. The trigger on the jobs table updates the employee table. If an update is made to the jobs table, the jobs trigger fires and completes the updates on the employee table. However, the trigger on the employee table does not fire. Download from www.wowebook.com ptg 992 CHAPTER 30 Creating and Managing Triggers The default configuration is to allow nested triggers, but there are reasons for turning off the nested triggers option. For example, you might want triggers to fire on direct data modifications but not on modifications that are made by another trigger. Say you have a trigger on every table that updates the audit time. You might want the audit time for a table to be updated by a trigger when that table is being updated directly, but you might not want the audit date updated on any of the other tables that are part of the nested trigger executions. This can be accomplished by turning off the nested triggers option. Using Recursive Triggers Recursive triggers were introduced in SQL Server 7.0. If a trigger modifies the same table where the trigger was created, the trigger does not fire again unless the recursive trig- gers option is turned on. recursive triggers is a database option turned off by default. The first command in the following example checks the setting of recursive triggers for the BigPubs2008 database, and the second sets recursive triggers to TRUE: EXEC sp_dboption BigPubs2008, ‘recursive triggers’ EXEC sp_dboption BigPubs2008, ‘recursive triggers’, TRUE If you turn off nested triggers, recursive triggers are automatically disabled, regardless of how the database option is set. The maximum nesting level for recursive triggers is the same as for nested triggers: 32 levels. You should use recursive triggers with care. It is easy to create an endless loop, as shown in Listing 30.24, which creates a recursive trigger on a new test table in the BigPubs2008 database. LISTING 30.24 The Error Message Returned for an Endless Loop with Recursive Triggers The first statement is used to disable the previously created DDL trigger which would prevent any changes. DISABLE TRIGGER ALL ON DATABASE EXEC sp_configure ‘nested triggers’, 1 RECONFIGURE WITH OVERRIDE EXEC sp_dboption BigPubs2008, ‘recursive triggers’, TRUE CREATE TABLE rk_tr_test (id int IDENTITY) GO CREATE TRIGGER rk_tr ON rk_tr_test FOR INSERT AS INSERT rk_tr_test DEFAULT VALUES GO INSERT rk_tr_test DEFAULT VALUES Server: Msg 217, Level 16, State 1, Procedure rk_tr, Line 2 Maximum stored procedure nesting level exceeded (limit 32). Download from www.wowebook.com ptg 993 Summary 30 The recursion described thus far is known as direct recursion. Another type of recursion exists as well: indirect recursion. With indirect recursion, a table that has a trigger fires an update to another table, and that table, in turn, causes an update to happen to the origi- nal table on which the trigger fired. This action causes the trigger on the original table to fire again. With indirect recursion, setting the recursive triggers database setting to FALSE does not prevent the recursion from happening. The only way to prevent this type of recursion is to set the nested triggers setting to FALSE, which, in turn, prevents all recursion. Summary Triggers are among the most powerful tools for ensuring the quality of data in a database. The range of commands that can be executed from within triggers and their capability to automatically fire give them a distinct role in defining sound database solutions. Chapter 31, “Transaction Management and the Transaction Log,” looks at the methods for defining and managing transactions within SQL Server 2008. Download from www.wowebook.com . C# using System; using System.Data; using System.Data .Sql; using Microsoft. SqlServer .Server; using System.Data.SqlClient; using System.Data.SqlTypes; using System.Xml; using System.Text.RegularExpressions; public. { SqlTriggerContext triggContext = SqlContext.TriggerContext; SqlConnection conn = new SqlConnection (“context connection = true”); conn.Open(); SqlCommand sqlComm = conn.CreateCommand(); SqlPipe. Scope sys .server_ triggers Server- scoped DDL triggers sys .server_ trigger_events Events that fire server- scoped triggers sys .sql_ modules DDL trigger definitions for server- scoped triggers sys .server_ assembly_modules