What are triggers?
A trigger defines an action the database should take when some database-related event (such as inserts, updates, deletes) occurs.
• Triggers are similar to procedures, in that they are named PL/SQL blocks.
Differences between Procedures and Triggers
A procedure is executed explicitly from another block via a procedure call with passing arguments, while a trigger is executed (or fired) implicitly whenever the triggering event (DML: INSERT, UPDATE, or DELETE) happens, and a trigger doesn't accept arguments.
When triggers are used?
• Maintaining complex integrity constraints (referential integrity)
or business rules
• Auditing information in a table by recording the changes.
• Automatically signaling other programs that action needs to take place
when changes are made to a table
• Collecting/maintaining statistical data.
CREATE [OR REPLACE] TRIGGER trigger_name
{BEFORE
| AFTER} {INSERT|UPDATE|DELETE} [OF column]
ON
table_reference
[FOR
EACH ROW [WHEN trigger_condition]]
[DECLARE] -- optional, for declaring local variables
trigger_body;
• Note that the DECLARE keyword is back in Trigger!
• The Trigger_body is executed when an event (Insert, Update, Delete operation)
occurs.
Trigger names
Triggers exist in a separate namespace from procedure, package, tables (that share the same namespace), which means that a trigger can have the same name as a table or procedure.
Types of triggers
There are two types of triggers in Oracle including row-level triggers and statement-level triggers
Row-level triggers for data-related activities
• Row-level triggers execute once for each row
in a transaction.
• Row-level triggers are the most common type of triggers; they are often
used in data auditing applications.
• Row-level trigger is identified by the FOR EACH ROW
clause in the CREATE TRIGGER command.
Statement-level triggers for transaction-related activities
• Statement-level triggers execute once for each transaction.
For example, if a single transaction inserted 500 rows into the Customer
table, then a statement-level trigger on that table would only be executed once.
• Statement-level triggers therefore are not often used for data-related
activities; they are normally used to enforce additional security
measures on the types of transactions that may be performed on a table.
• Statement-level triggers are the default type of triggers created and
are identified by omitting the FOR EACH ROW
clause in the CREATE TRIGGER command.
Before and After Triggers
• Since triggers occur because of events, they may be set to occur immediately
before or after those events. The events that execute triggers
are database transactions, triggers can be executed immediately BEFORE or AFTER
the statements INSERTs, UPDATEs, DELETEs.
• AFTER row-level triggers are frequently used in auditing applications,
since they do not fire until the row has been modified.
Clearly, there is a great deal of flexibility in the design of a
trigger.
Valid trigger types (possible combination of triggers)
• Statement (INSERT, DELETE, UPDATE), Timing
(BEFORE, AFTER),
Level (Row-level, Statement-level)
The values for the statement, timing, and level determine
the types of the triggers. There are total of 12 possible types of triggers:
3*2*2 = 12
Combining Trigger Types
• Triggers for multiple INSERT, UPDATE, DELETE commands on a table can
be combined into a single trigger (using OR), provided
they are all at the same level (row-level or statement-level), e.g., INSERT
OR UPDATE OR DELETE.
• However, you can not combine BEFORE or AFTER, e.g., BEFORE OR AFTER
is illegal.
An Example of Trigger -- for the table major_stats(major, total_credits, total_students);
CREATE OR REPLACE TRIGGER updateMajorStats
AFTER INSERT OR DELETE
OR UPDATE ON students
-- Oracle will check the status of this table
DECLARE
-- unlike a procedure, use DECLARE keyword
CURSOR c_statistics IS
SELECT
major, COUNT(*) total_students, SUM(current_credits) total_credits
FROM
students
GROUP
BY major;
BEGIN
FOR v_statsRecord IN c_statistics LOOP
UPDATE major_stats
SET
total_credits = v_statsRecord.total_credits,
total_students
= v_statsRecord.total_students
WHERE
major = v_statsRecord.major;
IF SQL%NOTFOUND THEN
INSERT
INTO major_stats(major, total_credits, total_students)
VALUES(v_statsRecord.major,
v_statsRecord.total_credits,
v_statsRecord.total_students);
END IF;
END LOOP;
END updateMajorStats;
Using pseudo variable :old and :new in Row-level Triggers only
• A row-level trigger fires once per row processed by the triggering
statement. Inside the trigger, you can access the row that is currently
being processed through two pseudocodes -- :old and
:new, e.g., :new.field or :old.field.
However, they are not true records.
• :old refers to the data as it existed prior to the
transaction. Updates and Deletes usually reference :old values.
The :new values are the data values that the transaction creates
(such as the columns in an inserted record).
• :old is undefined for INSERT statements, :new
is undefined for DELETE statements.
• If you need to set a column value in an inserted row via your trigger,
then you will need to use a BEFORE INSERT trigger in order to access
the :new values. Using an AFTER INSERT trigger would not allow
you to set the inserted value, since the row will already have been
inserted into the table (and thus be :old).
• The colon in front of :new and :old
is required.
• Operations that would normally be valid on
records are not valid for :new and
:old. For example, they cannot be assigned as
entire records, e.g., v_tmprec := :old; is not legal. Only
the individual fields within them may be assigned, e.g., v_tmprec.col
:= :old.col;.
• Likewise, :old and :new cannot be
passed to procedures or functions that take arguments of triggering_table%ROWTYPE,
since the actual parameter is assigned to the formal parameter in
the same way as a standard assignment.
For example, the GenerateStudentID trigger shown next uses :new. Its purpose is to fill in the ID field of Students with a vlaue generated from the student_sequence sequence.
CREATE OR REPLACE TRIGGER GenerateStudentID
BEFORE
INSERT OR UPDATE ON students
FOR
EACH ROW
BEGIN
SELECT
student_sequence.nextval INTO :new.ID
FROM
dual;
END GenerateStudentID;
The trigger GenerateStudentID actually modifies the value of :new.ID. This is one of the useful features of :new -- when the statement is actually executed, whatever values are in :new will be used.
INSERT INTO students(first_name, last_name) VALUES('Lolita', 'Lazarus');
We can issue an INSERT statement without generating an error. Even though we haven't specified a value for the primary column ID (which is required), the trigger will supply it.
=> If we specify a value for ID, it will be ignored, since the trigger changes it.
The WHEN clause -- WHEN condition
• The WHEN clause is valid for row-level triggers only. If present, the trigger body will be executed only for those rows that meet the condition specified by the WHEN clause.
For example, suppose we want to monitor any adjustments to an Amount that are greater than 10 percent.
CREATE TRIGGER customer_bef_upd_row
BEFORE UPDATE ON
customer
FOR EACH ROW
WHEN (new.amount/old.amount
> 1.1) --note
that there is no colon “:” for “new” and “old”
--
missing the colon is not an error.
BEGIN
INSERT
INTO customer_audit --
different table from the triggering table
VALUES
(:old.Action_date, :old.Action, :old.Item, :old.qty, :old.qtyType, :old.rate,
:old.amount)
--
the colon “:” is back in the trigger body.
END;
• The above row-level BEFORE UPDATE trigger will be executed only if
the new value of the Amount column is more than 10 percent
greater than its old value.
• The When clause adds further criteria to the triggering condition. The
triggering event must not only be an UPDATE of the customer
table, but also must reflect an increase of over 10 percent in the value of
the Amount column.
• The statements in the BEGIN and END is the trigger body. The commands
shown in the BEGIN and END are to be executed for every UPDATE
of the customer table that passes the WHEN condition.
Using Trigger Predicates: INSERTING, UPDATING, and DELETING
We can use three boolean functions to determine what the operation is (since you can have several triggering operations, sometimes you need to determine which operation is performing now).
CREATE TRIGGER customer_bef_upd_ins_row
BEFORE
INSERT OR UPDATE OF Amount ON CUSTOMER
FOR
EACH ROW
BEGIN
IF
INSERTING THEN
INSERT
INTO customer_audit
VALUES(:new.Action_date,
:new.Action, :new.Item, :new.qty, :new.qtyType,
:new.rate,
:new.Amount);
ELSE
--
if not inserting, then we are updating Amount
INSERT
INTO customer_audit
VALUES(:old.Action_date,
:old.Action, :old.Item, :old.qty, :old.qtyType,
:old.rate,
:old.Amount);
END
IF;
END;
• If you look at the trigger body in the above PL/SQL program, the trigger
checks to see if the record is being inserted into the customer
table. If it is, then the first part of the trigger body is executed. The INSERTING
portion of the trigger body inserts the new values of the record
into the customer_audit table.
• Other transaction types can be checked. In this example, since the trigger
executed, the transaction must either an INSERT or an UPDATE
of the Amount column.
Dictionary table for triggers
USER_TRIGGERS
Triggers and Dependencies
A trigger has a separate name space from a procedure/function or package, which means there is no naming conflict with a procedure. Since the trigger is stored in source format, it does not depend on anything.
Restrictions on Triggers
• A trigger may not issue any transaction control statement (e.g., COMMIT,
ROLLBACK, …)
• The trigger body cannot declare any LONG variables.
• Row-level triggers may not access mutating or constraining tables. Mutating
table is a table that the current trigger is watching for
a modification. Constraining table is a table that is related to a mutating
table through foreign key.
Trigger related commands
• DROP TRIGGER trigger-name
• ALTER TRIGGER trigger-name [{DISABLE | ENABLE }]
• ALTER TABLE table_name [{DISABLE | ENABLE } [ALL TRIGGERS]]