Saturday, 28 January 2017

DATABASE TRIGGERS

What is a Database Trigger?
Database trigger is a PL/SQL block that is executed on an event in the database.  The event is related to a particular data manipulation of a table such as inserting, deleting or updating a row of a table.

Triggers may be used for any of the following:

*  To implement complex business rule, which cannot be implemented using integrity constraints.
*  To audit the process.  For example, to keep track of changes made to a table.
*  To automatically perform an action when another concerned action takes place.  For example, updating a table whenever there is an insertion or a row into another  table.

Triggers are similar to stored procedures, but stored procedures are called explicitly and triggers are called implicitly by Oracle when the concerned event occurs.

Note: Triggers are automatically executed by Oracle and their execution is transparent to users.
Types of Triggers
Depending upon, when a trigger is fired, it may be classified as :

*  Statement-level trigger
*  Row-level trigger
*  Before triggers
*  After triggers

Statement-level Triggers
A statement trigger is fired only for once for a DML statement irrespective of the number of rows affected by the statement.

For example, if you execute the following UPDATE command STUDENTS table, statement trigger for UPDATE is executed only for once.

update students   set  bcode=’b3’
where bcode = ‘b2’;

However, statements triggers cannot be used to access the data that is being inserted, updated or deleted.  In other words, they do not have access to keywords NEW and OLD, which are used to access data.

Statement-level triggers are typically used to enforce rules that are not related to data. For example, it is possible to implement a rule that says “no body can modify BATCHES table after 9 P.M”.

Statement-level trigger is the default type of trigger.

Row-level Trigger
A row trigger is fired once for each row that is affected by DML command.  For example, if an UPDATE command updates 100 rows then row-level trigger is fired 100 times whereas a statement-level trigger is fired only for once.

Row-level trigger are used to check for the validity of the data. They are typically used to implement rules that cannot be implemented by integrity constraints.

Row-level triggers are implemented by using the option FOR EACH ROW in CREATE TRIGGER statement.

Before Triggers
While defining a trigger, you can specify whether the trigger is to be fired before the command  (INSERT, DELETE, and UPDATE) is executed or after the command is executed.

Before triggers are commonly used to check the validity of the data before the action is performed. For instance, you can use before trigger to prevent deletion of row if deletion should not be allowed in the given case.

AFTER Triggers
After triggers are fired after the triggering action is completed. For example, If after trigger is associated with INSERT command then it is fired after the row is inserted into the table.

Possible Combinations
The following are the various possible combinations of database triggers.

*  Before Statement
*  Before Row
*  After Statement
*  After Row

Note: Each of the above triggers can be associated with INSERT, DELETE, and UPDATE commands resulting in a total of 12 triggers.

In the next section, we will see how to create database triggers.

Creating a Database Trigger
CREATE TRIGGER command is used to create a database trigger. The following details are to be given at the time of creating a trigger.

*  Name of the trigger
*  Table to be associated with
*  When trigger is to be fired - before or after
*  Command that invokes the trigger - UPDATE, DELETE, or INSERT
*  Whether row-level trigger or not
*  Condition to filter rows.
*  PL/SQL block that is to be executed when trigger is fired.

The following is the syntax of CREATE TRIGGER command.

CREATE  [OR REPLACE] TRIGGER trigername
{BEFORE | AFTER}
{DELETE | INSERT | UPDATE [OF columns]}
        [OR {DELETE | INSERT |UPDATE [OF columns]}]...
ON table
[FOR EACH ROW [WHEN condition]]
[REFERENCING  [OLD AS old] [NEW AS new]]
PL/SQL block

If FOR EACH ROW option is used then it becomes a row-level trigger otherwise it is a statement-level trigger.

WHEN is used to fire the trigger only when the given condition is satisfied.  This clause can be used only with row triggers.

For example, the following trigger is fired only when AMOUNT is more than 1000.

create or replace trigger ..
before insert on payments
for each row
when  :new.amount > 1000

OF option allows you to specify updation of which columns will fire trigger. The list of columns can be given by separating column by comma.

REFERENCING is used to use new names instead of default correlation names OLD and NEW. See the section  “Correlation Names “

The following is a simple database trigger that is used to check whether date of joining of the student is less than or equal to system date. Otherwise it raises an error.

create or replace trigger students_bi_row
before insert
on students
for each row
begin
    if :new.dj > sysdate then
       raise_application_error
           (-20002,'Date of joining cannot be after system date.');
    end if;
end;

STUDENTS_BI_ROW is the name of the trigger. It represents table name – STUDENTS, event of the trigger – BI (before insert) and type – ROW. Though trigger name can be anything, it is better you follow a convention while naming it.

FOR EACH ROW specifies that this trigger is a row-level trigger.

Condition :NEW.DJ > SYSDATE checks whether the value of DJ of the row being inserted is greater than system date. If the condition is true then it raises an error using RAISE_APPLICATION_ERROR procedure. 


Note: Use data dictionary view USER_TRIGGERS to list the existing triggers.

The following command can be used to display the name and body of each trigger in the current account.

select trigger_name, trigger_body from user_trigger;

Column TRIGGER_BODY is of LONG type. How many characters of this column will be displayed is determined by variable LONG in SQL*Plus.  Set variable LONG to a larger value to see the complete body of the trigger. The default value of LONG is 80.

The following is another trigger used to implement a rule that cannot be implemented by integrity constraints. The trigger checks whether payment made by the student is more than what the student is supposed to pay.

create or replace trigger payments_bi_row
before insert
on payments
for each row
declare
  v_dueamt number(5);
begin
   v_dueamt := getdueamt(:new.rollno);
   if  :new.amount > v_dueamt then
          raise_application_error(-20112,'Amount being paid is more than what is to be paid');
   end if;
end;

The above trigger makes use of GETDUEAMT function that we created in the previous chapter.  It first gets the due amount of the given roll number. If the amount being paid – values of AMOUNT column – is more than the due amount then trigger fails.

Statement-level trigger example
The following is an example of statement-level trigger. As we have seen in the previous section, a statement trigger is fired only for once for the entire statement irrespective of the number of rows affected by the statement.

create or replace trigger payments_biud
before insert or update or delete
on payments
begin
  if to_char(sysdate,'DY') = 'SUN' then
    raise_application_error(-20111,'No changes can be made on sunday.');
  end if;
end;

The above trigger is fired whenever an UPDATE or DELETE or INSERT command is executed on PAYMENTS table. Trigger checks whether day of week of the system date is Sunday.  If condition is true then it raises an application error. Since the trigger is not concerned with data, it uses a statement-level trigger.


Valid Statements in trigger body
The following are the only valid statements in trigger body. Trigger can make use of existing stored procedures, functions and packages (as shown in the previous example).

q  DML commands
q  SELECT INTO command

Correlation Names
The default correlation names are NEW for new values and OLD for old values of the row. So, in order to access the values of new row use NEW and to access the values of old (existing) row, use OLD.

It is also possible to change these correlation names using REFERENCING option of CREATE TRIGGER command. This is done mainly to avoid names conflicts between table names and correlation names.

See the table 1, to understand the availability of correlation name with triggering commands.

Command
NEW
OLD
DELETE
No
Yes
INSERT
Yes
No
UPDATE
Yes
Yes

Table 1: Availability of correlation names

NEW is not available with DELETE command because there are no new values. In the same way, OLD is not available with INSERT because a new row is being inserted and it doesn’t contain old values. Triggers based on UPDATE command can access both NEW and OLD correlation names. OLD refers to values that refer to values before the change and NEW refers to values that are after the change.

The following trigger prevents any change to AMOUNT column of PAYMENTS table. This is done by comparing old value with new value and if they differ that means the value is changed and trigger fails.

create or replace trigger payments_bu_row
before update
on payments
for each row
begin
   if  :new.amount <> :old.amount then
          raise_application_error(-20113,'Amount cannot be changed. Please delete and reinsert the row, if required');
   end if;
end;

Instead-of Trigger
These trigger are defined on relation-views and object-views.  These triggers are used to modify views that cannot be directly modified by DML commands. Unlike normal trigger, which are fired during the execution of DML commands, these triggers are fired instead of execution of DML commands. That means instead of executing DML command on the view, Oracle invokes the corresponding INSTEAD-OF trigger.

The following is a view based on STUDENTS and PAYMENTS tables.


create view  newstudent
as
select  s.rollno, name, bcode,gender, amount
from students s, payments p
where  s.rollno = p.rollno;
  
If you try to insert data into NEWSTUDENT table then Oracle displays the following error.

SQL> insert into newstudent values (15,'Joe','b2','m',2000);
insert into newstudent values (15,'Joe','b2','m',2000)
*
ERROR at line 1:
ORA-01779: cannot modify a column which maps to a non key-preserved table

But, we want the data supplied to NEWSTUDENT view to be inserted into STUDENTS and PAYMENTS table. This can be done with an instead of trigger as follows:

create or replace trigger newstudent_it_bi_row
instead of insert
on newstudent
for each row
begin
     -- insert into STUDENTS table first

     insert into students(rollno,bcode,name,gender,dj)
        values(:new.rollno, :new.bcode, :new.name, :new.gender,sysdate);

     -- insert a row into PAYMENTS table

     insert into payments
           values(:new.rollno, sysdate, :new.amount);
end;

The above INSTEAD-OF trigger is used to insert one row into STUDENTS table with roll number, name, batch code and gender of the student. It then inserts a row into PAYMENTS table with roll number, date of payment  - sysdate, and amount.

Since we created an INSTEAD-OF trigger for INSERT command, Oracle invokes this trigger when an INSERT command is executed on NEWSTUDENTS view.

SQL> insert into newstudent values (15,'Joe','b2','m',2000);

So the above command inserts two rows – one into STUDENTS table and another into PAYMENTS table. The following two queries will ratify that.

select rollno, bcode,name, gender, dj
from students where rollno = 15;

   ROLLNO BCODE NAME                           G DJ
--------- ----- ------------------------------ - ---------
       15 b2    Joe                            m 30-Oct-01


select * from payments where rollno = 15;

   ROLLNO DP           AMOUNT
--------- --------- ---------
       15 30-OCT-01      2000
 
Knowing which command fired the trigger
When a trigger may be fired by more than one DML command, it may be required to know which DML command actually fired the trigger.

Use the following conditional predicates in the body of the trigger to check which command fired the trigger.

*  INSERTING
*  DELETING
*  UPDATING

The following example is used to log information about the change made to table COURSES. The trigger makes use of a COURSES_LOG table, which is created with the following structure.

create table courses_log
(  cmd  number(1),
   pk   varchar2(20),
   dc   date,
   un   varchar2(20)
);

Now a row level trigger is used to insert a row into COURSES_LOG table whenever there is a change to COURSES table.
create or replace trigger  courses_biud_row
before insert or delete or update
on courses
for each row
begin
    if  inserting then
            insert into courses_log values (1, :new.ccode,sysdate, user);
    elsif  deleting then
            insert into courses_log values(2,:old.ccode,sysdate,user);
    else
            insert into courses_log values(3,:old.ccode,sysdate,user);
    end if;
end;

After the trigger is created, if you execute an UPDATE command as follows:

update courses set fee = fee * 1.1 where ccode = 'ora';

it will fire COURSES_BIUD_ROW trigger, which will insert a row into COURSES_LOG table as follows:


SQL> select * from courses_log;

      CMD PK                   DC        UN
--------- -------------------- --------- --------------------
        3 ora                  30-OCT-01 BOOK

Enabling and disabling triggers
A database trigger may be either in enabled state or disabled state.

Disabling trigger my improve performance when a large amount of table data is to be modified, because the trigger code is not executed.

For example,

UPDATE students set name = upper(name);

will run faster if  all the triggers fired by UPDATE on STUDENTS table are disabled.

Use ALTER TRIGGER command to enable or disable triggers as follows:

alter trigger  payments_bu_row disable;

A disabled trigger is not fired until it is enabled.  Use the following command to enable a disabled trigger.

alter trigger payments_bu_row enable;

The following command disables all triggers of STUDENTS table.

alter table  students disable all triggers;

Dropping a Trigger
When a trigger is no longer needed it can be dropped using DROP TRIGGER command  as follows.

drop trigger payments_bu_row;
 
Summary
Database triggers are used to implement complex business rules that cannot be implemented using integrity constraints. Triggers can also be used for logging and to take actions that should be automatically performed.

Trigger may be fired before the DML operation or after DML operation. Trigger may be fired for each row affected by the DML operation or for the entire statement.

INSTEAD-OF trigger are called instead of executing the given DML command. They are defined on relational-views and object-views (will be discussed later in this book).

A trigger can be disabled to increase the performance when a lot of data manipulations are to be done on the table.

No comments:

Post a Comment

SQL Interview Questions and Answers

There is given sql interview questions and answers that has been asked in many companies. For PL/SQL interview questions, visit our next...