4. 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
databases, and it can ensure that data rows inserted into a table in one
database are valid based on rows in another database.
Listing 5 shows how to re-create and populate the customers and orders tables in the sample BigPubs2008 database.
Listing 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
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 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.
5. Cascading Deletes
A cascading delete is relatively simple to create. Listing 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.
Listing 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)
ROLLBACK TRAN
RETURN
END
|
The following DELETE statement deletes the row for Customer 1, so all three rows for that customer in the orders table should be deleted by the trigger:
DELETE customers WHERE customer_id = 1
Server: Msg 547, Level 16, State 1
The DELETE statement conflicted with COLUMN REFERENCE
constraint 'FK_orders_customers'.
The conflict occurred in database 'BigPubs2008',
table 'orders', column 'customer_id'.
The statement has been terminated.
This result might not be what you expected. The FOREIGN KEY constraint here restricts the DELETE statement, so the trigger never fires. The trigger in this example is an AFTER
trigger. Therefore, the trigger does not fire, and the cascading action
never takes place. You have several options to get around this
situation:
- Remove the FOREIGN KEY constraint from orders to customers.
- Disable the FOREIGN KEY constraint from orders to customers.
- Keep the FOREIGN KEY constraint and perform all cascading actions in stored procedures.
- Keep the FOREIGN KEY constraint and perform all cascading actions in the application.
- Use an INSTEAD OF trigger in place of the AFTER trigger.
- Use the new cascading referential integrity constraints.
Listing 7 shows how you can disable the FOREIGN KEY constraint so that a cascading delete can occur.
Listing 7. Disabling the FOREIGN KEY Constraint to the customers Table So That a Cascading Delete Can Occur
ALTER TABLE orders
NOCHECK CONSTRAINT FK_orders_customers
GO
GO
DELETE customers WHERE customer_id = 1
SELECT * FROM customers
SELECT * FROM orders
customer_id customer_name customer_comments
----------- ------------------------- ----------------------
2 Software Suppliers AB Elisabeth is contact.
3 Firmware Suppliers AB Mike is contact.
order_id customer_id order_date
----------- ----------- ---------------------------
|
In Listing 7, the cascading deletes occur via the trigger because the FOREIGN KEY
constraint is disabled. Compared to a trigger for cascading deletes, a
trigger for cascading updates is more complex and not as common. This
issue is discussed in more detail in the next section.
If you disable the FOREIGN KEY constraint, you have a potential integrity problem. If rows are inserted or updated in the orders table, there are no constraints to ensure that the customer number exists in the customer table. You can take care of this situation by using an INSERT and UPDATE trigger on the orders table (see Listing 8). The trigger in Listing 8 tests for the existence of a customer before the order is inserted or updated.
Listing 8. Handling a Restriction by Using a Trigger on the orders Table
if exists (select * from sysobjects where id = object_id('dbo.ord_ins_upd_cust')
and sysstat & 0xf = 8)
drop trigger dbo.ord_ins_upd_cust
GO
CREATE TRIGGER ord_ins_upd_cust ON orders
FOR INSERT, UPDATE
AS
IF EXISTS (SELECT * FROM inserted
WHERE customer_id NOT IN
(SELECT customer_id FROM customers))
BEGIN
RAISERROR('No customer with such customer number', 16, 1)
ROLLBACK TRAN
RETURN
END