The first script creates a sample valid inventory item for test purposes: USE OBXKites; DECLARE @ProdID UniqueIdentifier, @LocationID UniqueIdentifier; SELECT @ProdID = ProductID FROM db
Trang 1TheOBXKitesdatabase includes a simplified inventory system To demonstrate transaction-aggregation
handling, the following triggers implement the required rules The first script creates a sample valid
inventory item for test purposes:
USE OBXKites;
DECLARE
@ProdID UniqueIdentifier,
@LocationID UniqueIdentifier;
SELECT @ProdID = ProductID
FROM dbo.Product
WHERE Code = 1001;
SELECT @LocationID= LocationID
FROM dbo.Location
WHERE LocationCode = ‘CH’;
INSERT dbo.Inventory (ProductID, InventoryCode, LocationID) VALUES (@ProdID,’A1’, @LocationID);
SELECT P.Code, I.InventoryCode, I.QuantityOnHand
FROM dbo.Inventory AS I INNER JOIN dbo.Product AS P
ON I.ProductID = P.ProductID;
Result:
Code InventoryCode QuantityOnHand - -
The inventory-transaction trigger
The inventory-transaction trigger performs the aggregate function of maintaining the current
quantity-on-hand value in theInventorytable With each row inserted into theInventoryTransaction
table, the trigger updates theInventorytable TheJOINbetween theInsertedimage table and the
Inventorytable enables the trigger to handle multiple-row inserts:
CREATE TRIGGER InvTrans_Aggregate
ON dbo.InventoryTransaction AFTER Insert
AS
UPDATE dbo.Inventory
SET QuantityOnHand += i.Value FROM dbo.Inventory AS Inv INNER JOIN Inserted AS i
ON Inv.InventoryID = i.InventoryID;
Return;
Trang 2The next batch tests theInvTrans_Aggregatetrigger by inserting a transaction and observing the
InventoryTransactionandInventorytables:
INSERT InventoryTransaction (InventoryID, Value)
SELECT InventoryID, 5
FROM dbo.Inventory
WHERE InventoryCode = ‘A1’;
INSERT InventoryTransaction (InventoryID, Value)
SELECT InventoryID, -3
FROM dbo.Inventory
WHERE InventoryCode = ‘A1’;
INSERT InventoryTransaction (InventoryID, Value)
SELECT InventoryID, 7
FROM dbo.Inventory
WHERE InventoryCode = ‘A1’;
The following query views the data within theInventoryTransactiontable:
SELECT i.InventoryCode, it.Value
FROM dbo.InventoryTransaction AS it
INNER JOIN dbo.Inventory AS i
ON i.InventoryID
= it.InventoryID;
Result:
InventoryCode Value
-
TheInvTrans_Aggregatetrigger should have maintained a correct quantity-on-hand value through
the inserts to the InventoryTransactiontable Indeed, the next query proves the trigger functioned
correctly:
SELECT p.Code, i.InventoryCode, i.QuantityOnHand
FROM dbo.Inventory AS i
INNER JOIN dbo.Product AS p
ON i.ProductID = p.ProductID;
Result:
Code InventoryCode QuantityOnHand
- -
Trang 3The inventory trigger
The quantity values in theInventorytable should never be directly manipulated Every quantity
adjustment must go through theInventoryTransactiontable However, some users will want
to make manual adjustments to theInventorytable The gentlest solution to the problem is to use
server-side code to perform the correct operations regardless of the user’s method:
1 An inventoryinstead oftrigger must redirect direct updates intended for theInventory
table, converting them into inserts in theInventoryTransactiontable, while permitting theInvTrans_Aggregatetrigger to update theInventorytable
2 The inserts into theInventoryTransactiontable then update theInventorytable, leaving the correct audit trail of inventory transactions
As a best practice, the trigger must accept multiple-row updates The goal is to undo the original
DMLUPDATEcommand while keeping enough of the data to write the change as anINSERTto the
InventoryTransactiontable:
CREATE TRIGGER Inventory_Aggregate
ON Inventory INSTEAD OF UPDATE AS
Redirect direct updates
If Update(QuantityOnHand)
BEGIN;
UPDATE dbo.Inventory SET QuantityOnHand = d.QuantityOnHand
FROM Deleted AS d INNER JOIN dbo.Inventory AS i
ON i.InventoryID = d.InventoryID;
INSERT dbo.InventoryTransaction (Value, InventoryID)
SELECT
i.QuantityOnHand - Inv.QuantityOnHand,
Inv.InventoryID FROM dbo.Inventory AS Inv INNER JOIN Inserted AS i
ON Inv.InventoryID = i.InventoryID;
END;
To demonstrate the trigger, the followingUPDATEattempts to change the quantity on hand from
9to10 The newInventory_Aggregatetrigger traps theUPDATEand resets the quantity on
hand back to9, but it also writes a transaction of +1to theInventoryTransactiontable (If
theInventoryTransactiontable had transaction type and comment columns, then the
trans-action would be recorded as a manual adjustment by user X.) TheInventoryTransaction
table’sInvTrans_Aggregatetrigger sees theINSERTand properly adjusts the Inventory
.QuantityOnHandto10:
Trigger Test
Trang 4UPDATE dbo.Inventory
SET QuantityOnHand = 10
WHERE InventoryCode = ‘A1’;
Having performed the manual adjustment, the following query examines theInventoryTransaction
table:
SELECT i.InventoryCode, it.Value
FROM dbo.InventoryTransaction AS it
INNER JOIN dbo.Inventory AS i
ON i.InventoryID
= it.InventoryID;
Sure enough, the manual adjustment of 1 has been written to theInventoryTransactiontable:
InventoryCode Value
-
As the adjustment was being inserted into theInventoryTransactiontable, theInvTrans_
Aggregatetrigger posted the transaction to theInventorytable The following query double-checks
theQuantityOnHandfor inventory item‘A1’:
SELECT p.Code, i.InventoryCode, i.QuantityOnHand
FROM dbo.Inventory AS i
INNER JOIN dbo.Product AS p
ON i.ProductID = p.ProductID;
Result:
Code InventoryCode QuantityOnHand
- -
Summary
Triggers are a key feature of client/server databases It is the trigger that enables the developer to create
complex custom business rules that are strongly enforced at the Database Engine level SQL Server has
two types of triggers:INSTEAD OFtriggers andAFTERtriggers
Key takeaways about triggers:
■ INSTEAD OFtriggers cancel the firing DML statement and do something else instead of the
original DML statement
Trang 5■ Triggers extend the lock duration, so try to place the code in the abstraction layer before it goes into the trigger
■ Triggers fire once per DML statement, not once per row, so be certain the trigger is set-based and can handle multiple rows well
■ Use the inserted and deleted virtual tables to access the data being modified by the DML statement
■ Logic in triggers can quickly become a rat’s nest of code — ad hoc SQL firing a trigger, which calls a stored procedure, which updates two tables, which fires two triggers, one of which updates another table, and so on This type of system is very expensive to maintain or refactor
The previous five chapters presented T-SQL programming and described how to package the code
within stored procedures, user-defined functions, and triggers The next chapter, ‘‘Creating DDL
Triggers,’’ continues the discussion about triggers
Trang 6DDL Triggers
IN THIS CHAPTER
Creating DDL database triggers Preventing server or database changes
DDL trigger scope Reading event data with XML Security triggers
Sometimes the difference between an endless search to identify the problem
and readily solving the problem is as simple as knowing what changed
DDL triggers are perfect for auditing server-level and database changes
Why devote a whole chapter to DDL triggers when the material only takes a few
pages? Because I’m convinced every database, development, test, or production,
should have a schema audit DDL trigger
A trigger is code that executes as the result of some action DML triggers fire as
the result of DML code — anINSERT,UPDATE,DELETE, orMERGEstatement
DDL triggers fire as the result of some server-level or database schema–level
event — typically data definition language (DDL) code — aCREATE,ALTER, or
DROPstatement DML triggers respond to data changes, and DDL triggers respond
to schema changes
Just like DML triggers, DDL triggers can execute T-SQL code and can rollback
the event DML triggers can see into the data transaction using the inserted and
deleted tables Because DDL triggers can respond to so many types of events and
commands, the command that fired the trigger and other appropriate
informa-tion about the event is passed to the trigger in XML using theEventData()
function
Trang 7What’s New with DDL Triggers
Introduced in SQL Server 2005, DDL triggers were extended to include logon triggers with SQL Server 2005
SP2 For SQL Server 2008, DDL triggers are a bit more fleshed out and slightly easier to code:
■ More trappable events — especially system stored procedures that work like DDL
code
■ EventData() XML schema is more readily available
Policy-Based Management, new in SQL Server 2008, leverages DDL triggers to enforce policies, so you can
be assured that DDL triggers reflect a technology that’s here to stay
Many of the reasons for using a DDL trigger can now be accomplished with more consistency across
multiple servers using Policy-Based Management (PBM) For more about PBM see Chapter 40,
‘‘Policy-Based Management.’’
Chapter 54, ‘‘Schema Audit Triggers,’’ is a sister chapter to this one that shows how to implement DDL
triggers to record every schema change to a database
Managing DDL Triggers
DDL triggers are easy to manage because they are managed using normal DDL The most significant
factor when developing a DDL trigger is the scope of the trigger — deciding which server- or
database-level events will fire a trigger
Creating and altering DDL triggers
DDL triggers are created or altered using syntax similar to working with DML triggers The location
of the trigger, specified by theONclause, is eitherALL SERVERorDATABASE— literally, the term is
DATABASE, not the name of the database The following code creates a database-level DDL trigger:
CREATE TRIGGER SchemaAudit
ON DATABASE
FOR DDL_Database_Level AS
code
Server-level events are a superset of database-level events They include all database level events The
next example shows a server-level DDL trigger:
CREATE TRIGGER SchemaAudit
ON ALL SERVER
Trang 8FOR DDL_Server_Level
WITH Options
AS
code
Using Management Studio, database triggers are listed under the database’s Programmability node in
Object Explorer Server triggers are listed under Server Objects in Object Explorer Database triggers can
be scripted using Object Explorer, but not modified as easily as other programmability objects such as
stored procedures The context menu for DDL triggers does not offer the➪ modify, or ➪ script to alter
options that a stored procedure’s context menu includes
To list the database DDL triggers using code, query thesys.triggersandsys.eventscatalog
views Server triggers are found atsys.server_triggersandsys.server_trigger_events
Trigger scope
There are dozens of events that can potentially fire a DDL trigger — one for every DDL type of action
that can be executed on the server or database These events are categorized into a hierarchy using event
groups Creating a DDL trigger for an event group will cause the DDL trigger to fire for every event in
that group
DDL triggers can be fired by individual events, such ascreate_table,create_login, or
alter_view This can be the perfect scope for trapping the specific event There are 136 server/
database-level events, and 33 server-only level events The full list of DDL events is listed in Books
Online — search for ‘‘DDL Events.’’
With so many events, it’s sometimes (often) useful to develop a DDL trigger that spans multiple events
Fortunately, Microsoft has combined multiple similar events into a well-designed logical hierarchy of
event groups The ‘‘DDL Event Groups’’ page in Books Online clearly shows the whole hierarchy
The top, or root, of the hierarchy is theddl_eventsgroup, which includes every possible event The
next level has two groups:ddl_server_level_eventsandddl_database_level_events Each
of these groups includes several subgroups and events Chances are good that when you need to write a
DDL trigger, there’s a group that matches exactly with the types of events you want to handle
The following code creates three DDL triggers to demonstrate DDL trigger scope The first DDL trigger
handles all server-level events:
CREATE TRIGGER DDL_Server_Level_Sample
ON ALL SERVER
FOR DDL_SERVER_LEVEL_EVENTS
AS
Set NoCount ON
Print ‘DDL_Server_Level_Sample DDL Trigger’
The second DDL trigger fires for all database-level events:
USE tempdb
CREATE TRIGGER DDL_Database_Sample
Trang 9ON DATABASE FOR DDL_DATABASE_LEVEL_EVENTS AS
Set NoCount ON Print ‘DDL_Database_Sample DDL Trigger’
The third DDL trigger traps onlycreate tablecommands:
CREATE TRIGGER DDL_Create_Table_Sample
ON DATABASE FOR Create_Table AS
Set NoCount ON Print ‘DDL_Create_Table_Sample DDL Trigger’
With these three DDL triggers installed, the next few DDL commands demonstrate DDL trigger scope
Creating a new database is a server-level event:
Create database Testdb
Result:
DDL_Server_Level_Sample DDL Trigger
Creating a new table will fire thecreate tableDDL trigger as well as the general database DDL
events trigger:
create table Test (col1 INT)
Result:
DDL_Database_Sample DDL Trigger DDL_Create_Table_Sample DDL Trigger
Dropping the table fires the general database DDL event trigger, but not the specificcreate table
event trigger:
drop table Test
Result:
DDL_Database_Sample DDL Trigger
DDL triggers and security
The DDL trigger creation options,ENCRYPTIONandEXECUTE AS, both ensure the security of
system-level auditing triggers The following DDL trigger will be encrypted when stored:
Trang 10CREATE TRIGGER DDL_DDL_Level_Sample
ON ALL SERVER
WITH ENCRYPTION
FOR DDL_EVENTS
AS
code
As with stored procedures, triggers can be executed under a different security context Instead of the
user who issued the DDL command that caused the DDL trigger to fire, the trigger can execute as one
the following security contexts:
■ Caller: Executes as the person executing the DDL command that fires the DDL trigger
■ Self: Executes as the person who created the DDL trigger
■ ‘login_name’: Executes as a specific login
Enabling and disabling DDL triggers
DDL triggers can be turned on and off This is good because DBAs need an easy way to
disable DDL triggers that roll back any schema changes The following code disables and then
enables theDDL_Create_Table_Sampletrigger:
DISABLE TRIGGER DDL_Create_Table_Sample
ON DATABASE;
ENABLE TRIGGER DDL_Create_Table_Sample
ON DATABASE;
Removing DDL triggers
Typically, removing an object in SQL Server requires nothing more than aDROPcommand Because
DDL triggers can exist on either the database or server level, slightly more code is required Also,
because of their dual lives (residing in either the database or server), DDL triggers aren’t listed in
sys.objects, nor can their presence be detected usingobject_id() DDL triggers are listed in
sys.server_triggersandsys.triggersDMVs :
IF EXISTS (SELECT *
FROM sys.server_triggers WHERE Name = ‘DDL_Server_Level_Sample’) DROP TRIGGER DDL_Server_Level_Sample ON ALL SERVER
IF EXISTS (SELECT *
FROM sys.triggers WHERE Name = ‘DDL_Database_Sample’) DROP TRIGGER DDL_Database_Sample ON DATABASE