Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 71 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
71
Dung lượng
375,26 KB
Nội dung
Microsoft SQL Server 2000 Programming by Example 340 CREATE TRIGGER tr1_Customers ON Customers AFTER UPDATE AS Your code here PRINT 'This is the tr1 trigger' GO CREATE TRIGGER tr2_Customers ON Customers AFTER UPDATE AS Your code here PRINT 'This is the tr2 trigger' GO CREATE TRIGGER tr3_Customers ON Customers AFTER UPDATE AS Your code here PRINT 'This is the tr3 trigger' GO Test the order of execution By using a MOCK operation UPDATE Customers SET ContactName = ContactName GO Specify the tr3 trigger as first trigger to execute EXEC sp_settriggerorder 'tr3_Customers', 'FIRST', 'UPDATE' Specify the tr2 trigger as last trigger to execute EXEC sp_settriggerorder 'tr2_Customers', 'LAST', 'UPDATE' Specify the tr1 trigger as any order to execute EXEC sp_settriggerorder 'tr1_Customers', 'NONE', 'UPDATE' GO Test the order of execution By using a MOCK operation PRINT CHAR(10) + 'After reordering'+ CHAR(10) UPDATE Customers SET ContactName = ContactName Go Chapter 9. Implementing Complex Processing Logic: Programming Triggers 341 This is the tr1 trigger This is the tr2 trigger This is the tr3 trigger After reordering This is the tr3 trigger This is the tr1 trigger This is the tr2 trigger Caution Remember that INSTEAD OF triggers are always executed before the data is modified. Therefore, they execute before any of the AFTER triggers. Checking for Updates on Specific Columns To check inside a trigger if a column has been updated, you can use the IF UPDATE() clause. This clause evaluates to TRUE if the column has been updated. To test for changes in multiple columns in a single statement, use the COLUMNS_UPDATED() function. This function returns a bitmap with the update status of every column in the base table. In other words, COLUMNS_UPDATED returns a sequence of bits, one bit for every column, and the bit is 1 if the column has been updated or otherwise it is 0. Listing 9.15 shows an example of these two functions. Listing 9.15 Inside a Trigger You Can Check Which Columns Have Been Updated CREATE TRIGGER tr_OrderDetails ON [Order Details] AFTER UPDATE AS Testing for changes to the PRIMARY KEY IF UPDATE(OrderID) BEGIN PRINT 'Changes to the PRIMARY KEY are not allowed' ROLLBACK TRAN Microsoft SQL Server 2000 Programming by Example 342 END Testing for changes on the 2nd, 3rd and 5th columns IF ((COLUMNS_UPDATED() & (2 + 4 + 8)) > 0) BEGIN IF ((COLUMNS_UPDATED() & 2) = 2) PRINT 'ProductID updated' IF ((COLUMNS_UPDATED() & 4) = 4) PRINT 'UnitPrice updated' IF ((COLUMNS_UPDATED() & 8) = 8) PRINT 'Quantity updated' END GO PRINT CHAR(10) + 'Updating ProductID and UnitPrice' UPDATE [Order Details] SET ProductID = ProductID, UnitPrice = UnitPrice PRINT CHAR(10) + 'Updating Quantity only' UPDATE [Order Details] SET Quantity = Quantity PRINT CHAR(10) + 'Updating OrderID' UPDATE [Order Details] SET OrderID = OrderID Updating ProductID and UnitPrice ProductID updated UnitPrice updated Updating Quantity only Quantity updated Updating OrderID Changes to the PRIMARY KEY are not allowed Multiple-Row Considerations Keep in mind that a trigger can be fired by an action that modifies a single row or multiple rows in a single statement. If you define your trigger to work for single rows only, you should reject changes that affect multiple rows. In this case, you can check whether the system function @@ROWCOUNT returns a value greater than 1. Chapter 9. Implementing Complex Processing Logic: Programming Triggers 343 You can define your trigger to deal only with multiple-row operations. In this case, you could use aggregate functions or use cursors. None of these strategies is efficient for single-row operations. The ideal situation would be to create a trigger with conditional logic to deal with either single-row or multiple- row operations depending on the value returned by @@ROWCOUNT. Listing 9.16 shows a new version of the example of Listing 9.6, optimized for both kinds of transactions. Listing 9.16 You Can Use @@ROWCOUNT to Detect Multiple-Row Operations Create trigger for insert CREATE TRIGGER isrOrderDetails ON [Order Details] AFTER INSERT AS IF @@ROWCOUNT = 1 BEGIN Single-row operation UPDATE TC SET TotalSales = TotalSales + I.UnitPrice * Quantity * (1 - Discount) FROM TotalCategoriesSales TC JOIN Products P ON P.CategoryID = TC.CategoryID JOIN Inserted I ON I.ProductID = P.productID END ELSE BEGIN Multi-row operation UPDATE TC SET TotalSales = TotalSales + (SELECT SUM(I.UnitPrice * Quantity * (1 - Discount)) FROM Inserted I WHERE I.ProductID = P.productID) FROM TotalCategoriesSales TC JOIN Products P ON P.CategoryID = TC.CategoryID END GO Create trigger for delete Microsoft SQL Server 2000 Programming by Example 344 CREATE TRIGGER delOrderDetails ON [Order Details] AFTER DELETE AS IF @@ROWCOUNT = 1 BEGIN Single-row operation UPDATE TC SET TotalSales = TotalSales - D.UnitPrice * Quantity * (1 - Discount) FROM TotalCategoriesSales TC JOIN Products P ON P.CategoryID = TC.CategoryID JOIN Deleted D ON D.ProductID = P.productID END ELSE BEGIN Multi-row operation UPDATE TC SET TotalSales = TotalSales - (SELECT SUM(D.UnitPrice * Quantity * (1 - Discount)) FROM Deleted D WHERE D.ProductID = P.productID) FROM TotalCategoriesSales TC JOIN Products P ON P.CategoryID = TC.CategoryID END GO Create trigger for Update CREATE TRIGGER udtOrderDetails ON [Order Details] AFTER UPDATE AS IF @@ROWCOUNT = 1 BEGIN Single-row operation UPDATE TC SET TotalSales = TotalSales + I.UnitPrice * I.Quantity * (1 - I.Discount) FROM TotalCategoriesSales TC JOIN Products P ON P.CategoryID = TC.CategoryID JOIN Inserted I ON I.ProductID = P.productID UPDATE TC SET TotalSales = TotalSales - D.UnitPrice * D.Quantity * (1 - D.Discount) Chapter 9. Implementing Complex Processing Logic: Programming Triggers 345 FROM TotalCategoriesSales TC JOIN Products P ON P.CategoryID = TC.CategoryID JOIN Deleted D ON D.ProductID = P.productID END ELSE BEGIN Multi-row operation UPDATE TC SET TotalSales = TotalSales + (SELECT SUM(I.UnitPrice * Quantity * (1 - Discount)) FROM Inserted I WHERE I.ProductID = P.productID) FROM TotalCategoriesSales TC JOIN Products P ON P.CategoryID = TC.CategoryID UPDATE TC SET TotalSales = TotalSales - (SELECT SUM(D.UnitPrice * Quantity * (1 - Discount)) FROM Deleted D WHERE D.ProductID = P.productID) FROM TotalCategoriesSales TC JOIN Products P ON P.CategoryID = TC.CategoryID END GO Tip As shown previously in Listing 9.16, you can easily define a trigger for AFTER UPDATE as a sequence of the actions defined in the AFTER INSERT and AFTER DELETE triggers. Altering Trigger Definitions To modify the definition of a trigger, you can use the ALTER TRIGGER statement. In this case, the trigger will take the new definition directly. Listing 9.17 shows how to execute the ALTER TRIGGER statement to modify the tr_Employees trigger. The syntax is identical to the CREATE TRIGGER statement. Moreover, because triggers are independent objects, no objects are depending on them. They can be dropped and re-created any time, if necessary. Caution You can change the name of a trigger using the sp_rename stored procedure, but this does not change the name of the trigger stored in the definition of the trigger in syscomments. To rename a trigger, it is recommended to drop the trigger and re-create it with a different name. Microsoft SQL Server 2000 Programming by Example 346 Listing 9.17 You Can Use the ALTER TRIGGER Statement to Modify a Trigger. USE Northwind GO Create a trigger to restrict modifications to the employees table to the dbo CREATE TRIGGER tr_Employees ON Employees AFTER UPDATE, INSERT, DELETE AS IF CURRENT_USER <> 'dbo' BEGIN RAISERROR ('Only Database Owners can modify Employees, transaction rolled back', 10, 1) ROLLBACK TRAN END GO Modify the trigger to restrict modifications to the employees table to the members of the db_owner role ALTER TRIGGER tr_Employees ON Employees AFTER UPDATE, INSERT, DELETE AS IF IS_MEMBER('db_owner') <> 1 BEGIN RAISERROR ('Only Database Owners can modify Employees, transaction rolled back', 10 ,1) ROLLBACK TRAN END GO Disabling Triggers To prevent triggers from running when data arrives through replication, you can add the NOT FOR REPLICATION option to the CREATE TRIGGER or ALTER TRIGGER statements. In this case, the trigger will fire on direct modifications to the base table, but not from subscription actions. Temporarily, you can disable a trigger to speed up some processes. To do so, you can use the ALTER TABLE statement with the DISABLE TRIGGER option, as in Listing 9.18. Listing 9.18 You Can Disable a Trigger Chapter 9. Implementing Complex Processing Logic: Programming Triggers 347 USE Northwind GO To disable a single trigger ALTER TABLE Employees DISABLE TRIGGER tr_Employees , isr_Employees, udt_Employees To disable several triggers from the same table ALTER TABLE Employees DISABLE TRIGGER tr_Employees, isr_Employees, udt_Employees To disable all the triggers from a table ALTER TABLE Employees DISABLE TRIGGER ALL To reenable the trigger, use the ALTER TABLE statement with the ENABLE TRIGGER option. Listing 9.19 shows how to reenable the triggers that were disabled in Listing 9.18. Listing 9.19 You Can Reenable a Trigger USE Northwind GO To enable a single trigger ALTER TABLE Employees ENABLE TRIGGER tr_Employees , isr_Employees, udt_Employees To enable several triggers from the same table ALTER TABLE Employees ENABLE TRIGGER tr_Employees, isr_Employees, udt_Employees To enable all the triggers from a table ALTER TABLE Employees ENABLE TRIGGER ALL Nesting Triggers Microsoft SQL Server 2000 Programming by Example 348 A trigger can be defined to modify a table, which in turn can have a trigger defined to modify another table, and so on. In this case, triggers force the execution of other triggers, and the execution stops when the last action does not fire any more triggers. Because triggers are a specialized form of stored procedures, you can nest trigger execution up to 32 levels. Triggers, stored procedures, scalar user-defined functions, and multistatement table-valued functions share this limit. If the execution of a sequence of nested triggers requires more than 32 levels, the execution is aborted, the transaction is rolled back, and the execution of the batch is cancelled. Nested triggers are enabled by default. You can change this option at server level by setting the "nested triggers" option to 0, using the system stored procedure sp_configure. You can read the system function @@NESTLEVEL to know how many levels of nesting you have during the execution of a trigger, stored procedure, or user-defined function. Note In a nested trigger situation, all the triggers are running inside the same transaction. Therefore, any errors inside any of the triggers will roll back the entire transaction. Listing 9.20 shows an example where you define triggers to maintain sales totals at different levels. 1. You insert, update, or delete data in the Order Details table. This data modification forces the execution of the AFTER UPDATE trigger. 2. The AFTER UPDATE trigger in the Order Details table updates the SaleTotal column in the Orders table. 3. Because the SaleTotal column in the Orders table has been updated, the existing AFTER UPDATE trigger in the Orders table runs automatically and updates the SaleTotal column in the Employees table and the Customers table. Listing 9.20 You Can Create Triggers That Can Be Nested in Sequence USE Northwind GO Add the column SaleTotal to the Orders table ALTER TABLE Orders ADD SaleTotal money NULL Add the column SaleTotal to the Employees table ALTER TABLE Employees ADD SaleTotal money NULL Add the column SaleTotal to the Customers table Chapter 9. Implementing Complex Processing Logic: Programming Triggers 349 ALTER TABLE Customers ADD SaleTotal money NULL GO Initialize the data UPDATE Orders SET SaleTotal = (SELECT SUM([Order Details].UnitPrice * Quantity * (1 - Discount)) FROM [Order Details] WHERE [Order Details].OrderID = Orders.OrderID) UPDATE Employees SET SaleTotal = (SELECT SUM(Orders.SaleTotal) FROM Orders WHERE Orders.EmployeeID = Employees.EmployeeID) UPDATE Customers SET SaleTotal = (SELECT SUM(Orders.SaleTotal) FROM Orders WHERE Orders.CustomerID = Customers.CustomerID) GO Create nested triggers CREATE TRIGGER isrTotalOrderDetails ON [Order details] AFTER INSERT, DELETE, UPDATE AS IF @@rowcount = 1 Single-row operation UPDATE Orders SET SaleTotal = SaleTotal + ISNULL( (SELECT UnitPrice * Quantity * (1 - Discount) FROM Inserted WHERE Inserted.OrderID = Orders.OrderID), 0) - ISNULL( (SELECT UnitPrice * Quantity * (1 - Discount) FROM Deleted WHERE Deleted.OrderID = Orders.OrderID), 0) ELSE Multi-row operation UPDATE Orders SET SaleTotal = SaleTotal + ISNULL( (SELECT SUM(UnitPrice * Quantity * (1 - Discount)) FROM Inserted WHERE Inserted.OrderID = Orders.OrderID), 0) - ISNULL( (SELECT SUM(UnitPrice * Quantity * (1 - Discount)) FROM Deleted [...]... list of the available collations fn_ServerSharedDrives returns a list of the drives shared by a clustered server fn_VirtualServerNodes returns the list of server nodes, defining a virtual server in a clustering server environment fn_VirtualFileStats returns statistical I/O information about any file in a database, including transaction log files Listing 10.2 shows some examples of how to use these table-valued,... 355 11, 12, 13, 14, 15, 16, 17, 18, */ Section B, 4, 30000000.0000, 41000000.0000, 1 Welding, 5, 20000 0.0000, 250000.0000, 0 Civil, 5, 145000.0000, 20000 0.0000, 0 Buildings, 5, 560 00.0000, 100000.0000, 0 Civil works, 10, 20000 000.0000, 30000000.0000, 0 Civil works, 11, 18000000.0000, 25000000.0000, 0 Pipeline, 10, 11000000.0000, 17000000.0000, 0 Pipeline, 11, 120000 00.0000, 160 00000.0000, 0 BULK INSERT... 'fn_replprepadbinary8(1234 568 90123)' + CHAR(10) select fn_replprepadbinary8(1234 568 90123) GO PRINT CHAR(10) + 'fn_replquotename("hello")' + CHAR(10) select fn_replquotename('hello') 363 fn_chariswhitespace(CHAR(9)) -1 fn_mssharedversion(1) -80 fn_replgetbinary8lodword(0x0304030401020102) 169 085 46 fn_replmakestringliteral(@a) -N'peter is right' fn_replprepadbinary8(1234 568 90123) ... executed by the Replication Merge Agent fn_GetPersistedServerNameCaseVariation(@servername) returns the server name of the server specified in @servername with exactly the same case it uses in the sysservers system table, regardless of the case used to call this function fn_ReplGetBinary8LoDWord(@binary8_value) takes the lower four bytes from the @binary8_value binary variable and converts them into an integer... of these built-in user-defined functions, you have them in the installation scripts Using Query Analyzer, open the following files located in the INSTALL directory: procsyst .sql, replcom .sql, replsys .sql, repltran .sql, and sqldmo .sql Types of User-Defined Functions According to Their Return Value You can define a user-defined function with a single statement or with multiple statements, as you will see... connected user and from which machine the user is connected The third example returns the date of the latest executed process in SQL Server, which we can consider today's date in general The fourth example is a bit more complex; it generates a random number, between 0.0 and 1.0, based on the time of the latest statement executed in SQL Server This function is similar to the system function Rand; however,... but SQL Server itself implements them 361 You cannot change the definition of these built-in user-defined functions In some cases, you cannot see their definition using the sp_help or sp_helptext system stored procedures, and you cannot script them However, their definition is stored in the syscomments system table as any other user-defined function Caution Microsoft does not guarantee that undocumented... Project, 0, 8 560 1000.0000, 117500000.0000, 1 2, Engineering, 1, 800000.0000, 950000.0000, 1 3, Materials, 1, 23400000.0000, 28000000.0000, 1 4, Construction, 1, 61 000000.0000, 88000000.0000, 1 5, Supervision, 1, 401000.0000, 550000.0000, 1 6, Line, 2, 300000.0000, 400000.0000, 0 7, Stations, 2, 500000.0000, 550000.0000, 0 8, Pipes, 3, 14500000.0000, 160 00000.0000, 0 9, Machinery, 3, 8900000.0000, 120000 00.0000,... applications that you might require This chapter will help you discover how user-defined functions can help you solve these common programming problems Built-In User-Defined Functions SQL Server 2000 implements some system functions as built-in user-defined functions Many of them are not documented; Query Analyzer, Enterprise Manager, Profiler, Replication, and other client applications and system processes... 10 Enhancing Business Logic: User-Defined Functions (UDF) Procedural languages are based mainly in the capability to create functions, encapsulate complex programming functionality, and return a value as a result of the operation Using SQL Server 2000, you can define user-defined functions(UDF), which combine the functionality of stored procedures and views but provide extended flexibility This chapter . 47000000.0000, 1 Microsoft SQL Server 2000 Programming by Example 3 56 11, Section B, 4, 30000000.0000, 41000000.0000, 1 12, Welding, 5, 20000 0.0000, 250000.0000, 0 13, Civil, 5, 145000.0000, 20000 0.0000,. recommended to drop the trigger and re-create it with a different name. Microsoft SQL Server 2000 Programming by Example 3 46 Listing 9.17 You Can Use the ALTER TRIGGER Statement to Modify a Trigger P.CategoryID = TC.CategoryID END GO Create trigger for delete Microsoft SQL Server 2000 Programming by Example 344 CREATE TRIGGER delOrderDetails ON [Order Details] AFTER DELETE