Sei sulla pagina 1di 8

Artículo "Trigger Happy"

Trigger Happy (DBMS, May 1996)

A look at the many implementations of database triggers

My Server Side columns will be taking an in-depth look at the facilities offered by popular relational
DBMS servers. In this month's column, I look at database triggers. Database triggers are not new.
IBM Corp. published a research report on triggers in 1976 (K.P Eswaran, "Specifications,
Implementations and Interactions of a Trigger Subsystem in an Integrated Database System," IBM
Research Report, RJ1820, 1976). One of the best academic papers on triggers was published in
1986 by K.R. Dittrich, A.M. Kotz, and J.A. Mülle, titled "An Event/Trigger Mechanism to Enforce
Complex Consistency Constraints in Design Databases" (ACM SIGMOD Record, Vol. 15, No. 3).
Ingres, InterBase, and Sybase Inc. featured stable, commercially used triggers in 1989. By 1994,
DBMS had published several articles on triggers (see DBMS, September 1990, page 66, November
1992, page 86, and August 1993, page 55). Most of the major DBMSs currently support some type
of trigger mechanism. What is interesting is how these various implementations differ. But first, let's
get a common understanding of triggers.

What is a Trigger?

A trigger was initially defined as a "predefined database procedure, conditionally or unconditionally


succeeding or preceding other database operations automatically" (K.P Eswaran, "Specifications,
Implementations and Interactions of a Trigger Subsystem in an Integrated Database System," IBM
Research Report, RJ1820, 1976). This does not imply stored procedures per se --it means
procedural code, or a sequence of operations coded in a mixture of SQL and programming
language statements. A trigger is therefore procedural processing logic, stored in the database and
executed automatically by the DBMS server under specific conditions. The word "automatic" is very
important -- applications or users do not activate the triggers, they are executed automatically
when the applications or users perform specific operations on the database. A trigger has the
following components:

* Constraint: The integrity constraint or business rule enforced by the trigger -- in other words,
the "purpose" of the trigger. Practically speaking, this should appear in the header of the trigger,
which should be reflected in its name. For example, the "Positive Opening Balance" constraint
requires that all new accounts must have non-negative balances.

* Event: A specific situation occurring in the database, which indicates when the constraint should
be enforced. The event is said to be "raised" when the situation occurs. The event is often specified
in two ways: as a state change on the database (for example, an insert into the Accounts table)
and as an optional predicate condition used to filter out some of the state changes (for example,
only those inserts into the Accounts table with a negative value for the Balance column). Specific
moments are also very interesting events. Note that "event" in this generic definition should not be
confused with the database event alerters supported by some DBMSs such as CA-OpenIngres.

* Action: A procedure or a sequence of procedural operations that implement the processing logic
required to enforce the constraint. For example, the action must enforce the business rule that
accounts may not have negative opening balances. This can be done by refusing the insert
operation if the opening balance is negative, or by replacing the negative balance with zero and
inserting an entry in a journaling table. The implied "if" condition highlights another point:
conventional database manipulation operations (that is, SQL select, insert, update, and delete
statements) are usually too limited to implement the required actions. They must be extended with
procedural constructs such as iteration (while, for, and repeat) and conditional (if and case)
statements. A trigger is therefore an event that "fires" an action to enforce a constraint.

In Listing 1 (page 90), the forcebalancetrigger trigger fires when a new row is inserted into the
Accounts table. It calls the stored procedure forcebalanceprocedure, which assigns a zero value to
the Balance column if it is negative, as illustrated in Figure 1 (page 91). (Note that in most
implementations of triggers, the trigger itself can contain the code to perform the necessary
operations, or it can call a stored procedure to perform the processing.) The trigger in Listing 1 was
implemented in version 6.0 of SQLBase that Computer Associates International Inc. licensed from
Centura Software Corp. (formerly Gupta Corp.), and released as CA-OpenIngres/Desktop 1.1.

Advantages of Triggers

The most attractive feature of triggers is that they are stored and executed in the database. This
has the following advantages:

* The triggers always fire when the associated events occur. Application developers do not have to
remember to include the functionality in each application, and users cannot bypass the triggers
through interactive tools. (But every skilled DBA knows how to bypass the triggers on his or her
system.) Most DBMSs have some mechanism to bypass the trigger, either by temporarily
deactivating the triggers or by using a "trace point" or some similar mechanism.

* Triggers are administered centrally. They are coded once, tested once, and then centrally
enforced for all the applications accessing the database. The triggers are usually controlled, or at
least audited, by a skilled DBA. The result is that the triggers are implemented efficiently.

* The central activation and processing of triggers fits the client/server architecture well. A single
request from a client can result in a whole sequence of checks and subsequent operations
performed on the database. The data and operations are not "dragged" across the network
between the client and the server.

Because triggers are so powerful, they must be managed well and they must be used correctly.
Inefficient triggers can bring the database server to its knees, due to the amount of work fired off
in the database. Incorrect triggers can corrupt the integrity of the data.

What are Triggers Used For?


Triggers are extremely powerful constructs and can be used for various purposes, for example:
* Integrity control: You can use triggers to implement domain integrity, column integrity,
referential integrity, and unconventional integrity constraints. I will address the declarative vs. the
do-it-yourself (that is, using triggers) approaches to integrity control in my next column.

* Business rules: You can use triggers to centrally enforce business rules. Business rules are
constraints that are enforced over the relationships between tables or between different rows in the
same table. For example, the sum of the amounts of the InvoiceItems rows must add up to the
total on the row in the Invoices table for the corresponding invoice -- that is, if your organization's
standards and/or DBAs let you have denormalized derived attributes in your physical data model.
* Application logic: You can use triggers to enforce business logic centrally, for example, to
insert rows automatically in the Orders and OrderItems tables when the QuantityOnHand value in
the Stocks table drops below a given threshold. Business rules could be formalized and could
actually be defined declaratively, if only the declarative syntax allowed it; but application logic
requires more complex functionality than can be specified declaratively.

* Security: You can use triggers to check value-based security constraints. When an operation is
performed on a sensitive table, the trigger fires to check that the operation is allowed for the user.
For example, you may only insert a row in a table if the department column contains the value of
your own department. In most systems, however, you cannot use triggers to restrict the data that
is visible to users. The only exception I came across is the Progress Database Server (release 7.3
and later), in which you can define triggers to fire on select operations. This makes it possible to
inspect or filter the data a user is about to see. For example, you can restrict a user to only retrieve
the details of orders placed for his department.

* Audit trails: Triggers can insert records into an audit trail table to log all the operations on
sensitive tables. The problem with this approach is that most trigger actions are under transactional
control. When an operation is rolled back, all its triggered operations are also rolled back. The
triggers will therefore only record the effects of successful operations. When an unsuccessful
operation is rolled back, the audit trail entry of that operation will also be rolled back. The audit trail
will therefore not contain attempted threats at violating data integrity constraints or security
restrictions.

* Replication: Many DBMS vendors and consultants (including my corporation) have implemented
replicators using triggers as the recording mechanism. In essence, when the replicated tables
change, the triggers fire and record the changes in buffer tables. A replication server then
propagates the operations from the buffer tables to the various target databases. (It is, however,
not quite as simple as it sounds.) In this situation, the transactional control on the triggers is
extremely useful, as you only want to replicate successfully completed transactions.

* Updatable views: In Borland's InterBase, you can define triggers on views and tables. You can
then use the view triggers to propagate the actions performed on the view to the underlying base
table(s). You can use this extremely powerful feature to update theoretically non-updatable views.
For example, the triggers on a view that does not contain the keys of the underlying tables can
query the key values of the underlying tables and can then perform the necessary operations.

The use of triggers is only limited by the functionality provided by the particular DBMS and, of
course, your imagination and innovative spirit. If you know of other interesting trigger
implementations, please email a short description to me at mr@dba.co.za so that I can list them in
a future column.

Implementations

Most of the major DBMSs implement triggers, each with their own features, syntax, restrictions,
and shortcomings. In this column, I cover the use of triggers in CA-OpenIngres 1.1, InterBase
Server 4.0, Oracle Enterprise Server 7.2, Centura Software SQLBase 6.0 (also licensed as CA-
OpenIngres/Desktop 1.1), and Microsoft SQL Server 6.0. The list of DBMSs supporting triggers also
includes Informix Online (release 5.01 and later), Progress Database Server (release 7.2 and later),
and Sybase SQL Server (release 4.0 and later), and is growing steadily. Unfortunately, there isn't
enough space for a detailed comparison of all the implementations, nor is that the purpose of this
column (but it makes for interesting reading). I will highlight only some of the interesting features
of some of the implementations.
CA-OpenIngres. In CA-OpenIngres 1.1, triggers are called rules (not to be confused with the
rules used to enforce domain constraints in Microsoft SQL Server). A rule fires after an insert,
update, or delete operation is performed on a row in a specified table. The rule calls a stored
procedure to perform the necessary actions. The rule can have a SQL where clause to restrict its
firing, which is a useful tool to eliminate unnecessary execution of stored procedures. There can be
multiple rules per operation per table, and they can call the same or different stored procedures,
which results in a modular and manageable implementation. Although this suits the definition of a
relational data model, it limits the DBA because he or she cannot control the rule firing sequence.

The rules for a particular event fire for each row after the triggering operation has been applied to
the row. This means that you cannot determine the effects of invalid operations until the operations
are complete. A Raise Error statement issued in the stored procedure rolls back the current
operation and all its triggered effects.

An interesting development is that, internally, CA-OpenIngres 1.1 uses operation-based rules and
stored procedures to implement declarative integrity checking. These rules and procedures can
pass sets of values as parameters. This means that an integrity check fires the rule once, which
calls the procedure once to perform the integrity check once (using a set-based query), even if
1000 rows were updated. This is a significant improvement over previous releases that used row-
based rule firing. In the future, this feature may be released as part of a configurable rules system
in order to let DBAs code their own operation-based triggers.

InterBase. Borland's InterBase 4.0 has a very powerful and modular implementation of triggers.
You can define multiple triggers per table, and the triggers can fire before or after the triggering
operations. You can specify the trigger firing sequence for multiple triggers defined for the same
event. The preoperation triggers are useful for do-it-yourself integrity checking to abort the
operation before an integrity constraint is actually violated, in effect, before the DBMS does any
significant work to perform the operation. The body of a trigger can contain the actions to be
performed, or it can call a stored procedure to perform the actions in a more modular fashion. You
can alter InterBase triggers to be active or inactive. Inactive triggers do not fire.

An interesting feature of InterBase triggers is that they can be defined on views. This is how Dr.
Codd proposed that the relational data model should work -- a relation is a relation, and it should
not make any difference whether it is physically implemented as a table or a view.

Oracle. In Oracle7 release 7.2 you can define multiple database triggers per table. You can define
the triggers to fire per operation or per affected row, before or after the operation. The triggers are
coded in PL/SQL, a powerful 4GL-like language that includes a rich set of functions, user-defined
data types (such as temporary tables), and cursors to process single or multiple rows at a time. The
triggers for the same event fire in any order, typically in creation order. If you need to enforce a
firing order, you can combine the code in one trigger. An Oracle database trigger can have a when-
clause, which is used to eliminate unnecessary trigger firing. You can also enable or disable the
Oracle triggers. Disabled triggers do not fire.

Oracle7 release 7.2 doesn't compile the source code of database triggers. The trigger definition is
stored as such in the database. Upon first execution, the trigger code is compiled and is cached in
the shared pool. Stored procedures, on the other hand, are precompiled and stored. This means
that extensive trigger functionality should be coded in stored procedures (which can be called from
the triggers), rather than in the triggers themselves. An Oracle trigger cannot query or perform a
manipulation operation on a "mutating" table, where the trigger may see an inconsistent view of
the table, because there is a transaction pending on the table (the firing transaction, in this case).
SQLBase. In Centura Software Corp.'s (previously Gupta Corp.) SQLBase 6.0, you can define six
triggers per table, one before and one after each of the insert, update, and delete operations. The
body of the trigger can contain the actions to be performed as so-called "inline code," or it can call
a stored procedure to perform the actions.

In SQLBase you can also define timer events, which are raised at specific times or at specific
intervals. These events function like database triggers in that they perform actions inside the
database when they are activated. The only difference is that they are activated by time and not by
operations on database tables or views. (Don't confuse these events with the database event
alerters found in CA-OpenIngres.) In the spring of 1996, Centura plans to release version 6.1 of
SQLBase, which will contain the same trigger functionality but also include a replicator, TCP/IP
support, and improved management functions.

Microsoft SQL Server. In Microsoft SQL Server 6.0, you can define three triggers per table, one
for each of the three operations. The trigger actions go into effect after the triggering operation has
been performed. The body of the trigger can contain the actions to be performed, or it can call
several stored procedures or extended stored procedures. A stored procedure may contain remote
procedure calls, which increases the power of the trigger tremendously. In this way, the trigger can
cause its actions to be performed on remote databases. An extended stored procedure is an
executable re-entrant procedure contained in a dynamic link library (for example, to send mail or a
C procedure to interface to an operational system). The data affected by the triggering operation is
available in two logical tables, called "inserted" and "deleted," respectively. They have the same
structure as the base table that activated the trigger. Your trigger actions often access the inserted
and deleted logical tables to identify the changed data.

It is difficult to process key changes using the inserted and deleted logical tables. You cannot join
the inserted and deleted tables to one another when the key values have changed. You also should
not code multiple row select statements in SQL Server triggers, because the results of the select
statements are returned directly to the application --these returned results must be handled in the
application. This makes it almost impossible to process the affected rows individually. Although
triggers can be nested to 16 levels, when a trigger's action causes an update on the same row in
the same table, the trigger is not fired again. When you specify a new trigger for an operation for a
table that already has an associated trigger, the existing trigger is replaced without a warning
message.

Future Directions

All the implementations I have investigated support triggers on insert, update, and delete
operations; some fire per operation, others fire per changed row. None of the relational DBMSs,
except the Progress Database Server, support triggers on select operations. Why would you want
to fire a trigger on a select operation? Well, to name just two reasons: to implement your own
security control and to count the popularity of different data warehouse tables. In the same vein,
you might use a select trigger to record the types of queries users perform, including those
originating from Web browsers accessing corporate databases over the Internet or an Intranet. The
other thought-provoking observation is that none of these systems support triggers firing per
transaction. Transaction triggers can enforce data consistency very efficiently. For example, you
only have to enforce that the OrderLines rows add up to the Order row (obviously for the same
order) at the completion of the entire transaction, not for each operation.

Another interesting development is the standardization of trigger and procedure syntax, which ANSI
has already proposed. The current implementations differ so much in functionality and syntax that
it is almost impossible to see how they could be integrated in a standard syntax. One can only hope
that they do not settle for the lowest common denominator, as that will imply a great loss of
powerful functionality in the database server.

Listing 1

create table ACCOUNTS

(AccountNo varchar (10) not null,

ClientName varchar (30) not null,

AccountType varchar (15) not null,

Balance Decimal(10,2) not null with default,

primary key (AccountNo));

/* FORCEBALANCEPROCEDURE stored procedure */

/* -------------------------------------- */

/* Constraint: Force the Balance of new rows in the ACCOUNTS */

/* table to have non-negative values.*/

/* Event: Insert into ACCOUNTS table. */

/* Action: Update negative Balance values to zero. */

store FORCEBALANCEPROCEDURE

procedure FORCEBALANCEPROCEDURE static

parameters

string AccountNo

number Balance
local variables

sql handle AcctHandle

actions

on procedure startup

begin

call sqlconnect(AcctHandle)

call sqlprepare(AcctHandle, 'update ACCOUNTS \

set Balance = 0 \

where AccountNo = :AccountNo')

end

on procedure execute

begin

if (Balance < 0)

call sqlexecute(AcctHandle)

call sqlgetmodifiedrows(AcctHandle, RowCount)

if RowCount <= 0

return 20001

end

on procedure close

begin

call sqldisconnect(AcctHandle)
end

/* FORCEBALANCETRIGGER trigger */

/* --------------------------- */

/* Constraint: Force the Balance of new rows in the */

/* ACCOUNTS table to have non-negative values. */

/* Event: Insert into ACCOUNTS table. */

/* Action: FORCEBALANCEPROCEDURE stored procedure. */

create trigger FORCEBALANCETRIGGER

after insert on ACCOUNTS

(execute FORCEBALANCEPROCEDURE(ACCOUNTS.AccountNo,

ACCOUNTS.Balance))

for each row

Potrebbero piacerti anche