Triggers Triggers are identical to stored procedures with the following exceptions. Triggers are called automatically when the data in the table the trigger is attached to changes. Triggers take no input parameters. Triggers do not return values Triggers are created by the CREATE TRIGGER statement. Using CREATE TRIGGER The following CREATE TRIGGER statement shows all of the elements of the CREATE TRIGGER syntax. The keywords CREATE TRIGGER are followed by the trigger name, the keyword FOR then the name of the table the trigger is attached to. Next comes either ACTIVE or INACTIVE to indicate whether the trigger will function or not. If the trigger is inactive it will not execute. You will see how to activate and deactivate a trigger later in this paper. The next element of the CREATE TRIGGER statement indicates when the trigger will fire. This will be one of the following six conditions. BEFORE UPDATE AFTER UPDATE BEFORE INSERT AFTER INSERT BEFORE DELETE AFTER DELETE Next comes the optional POSITION keyword followed by an integer. Interbase allows you to attach any number of triggers to the same event. For example, you could have four triggers for the employee table all of which fire after update. This is a great feature because it allows you to modularize your code. However, the order in which the triggers fire may be important. The POSITION keyword lets you control the firing order based on the integer number supplied. In this example the trigger shown below would fire first because its position is zero. If there were three other triggers you might assign them positions of 10, 20 and 30. It is a good idea to leave a gap in the numbering sequence so you can easily insert another trigger at any point in the firing order in the future. CREATE TRIGGER SAVE_SALARY_CHANGE FOR EMPLOYEE ACTIVE AFTER UPDATE POSITION 0 AS BEGIN IF (old.salary <> new.salary) THEN INSERT INTO salary_history (emp_no, change_date, updater_id, old_salary, percent_change) VALUES ( old.emp_no, 'NOW', user, old.salary, (new.salary - old.salary) * 100 / old.salary); END CREATE TRIGGER SAVE_SALARY_CHANGE FOR EMPLOYEE ACTIVE AFTER UPDATE POSITION 0 AS BEGIN IF (old.salary <> new.salary) THEN INSERT INTO salary_history (emp_no, change_date, updater_id, old_salary, percent_change) VALUES ( old.emp_no, 'NOW', user, old.salary, (new.salary - old.salary) * 100 / old.salary); END ^ After the keyword AS comes any local variable declarations using the same DECLARE VARIABLE syntax that was used for a stored procedure. Finally, comes the procedure body enclosed in a BEGIN/END block. One thing to keep in mind as you begin using triggers is that a single change to a record in a database can cause many triggers to fire. A change to table A can fire a trigger that updates table B. The update to table B can fire a trigger that inserts a record in table C. Inserting a new record in table C can fire a trigger that updates table D and so on. The second important point about triggers is that a trigger is part of the transaction that caused it to fire. This means that if you start a transaction and update a row which causes a trigger to fire and that trigger changes a table which causes another trigger to fire which updates another table and you then rollback the transaction your change and all of the changes made by all of the triggers fired by your change will be rolled back. Before or After? A trigger should fire before the row is updated if you want to change the value of one or more of the columns in the row being updated or if you may want to block the change to the row by raising an exception. For example, you might use a before delete trigger to prevent the user from deleting a customer record if the customer has placed an order within the last two years. After triggers are used when you want to ensure that the row action as been successfully completed before you take any other action. The trigger above is a good example. This trigger inserts a row into the salary_history table any time an employee's salary is changed. The history row contains both the old salary and the percent change. Since the update to the employee record could fail for many reasons, such as a value in a field that violates a constraint, you do not want to create the history record until after the update has successfully completed. Using Old and New In the sample trigger above you see column names preceded by the "old" and "new" modifiers. In the body of the trigger Interbase makes both the old and new value of each column available, for example old.salary and new.salary. Using the old and new values you can easily create history records, calculate the amount or percentage of change in a numeric value, find records in another table that match either the old or new value or do anything else you can think of. Raising Exceptions In a BEFORE trigger you can prevent the row update that fired the trigger from taking place by raising an exception. Before you can raise an exception you must create the exception using the CREATE EXCEPTION statement. For example, CREATE EXCEPTION CUSTOMER_STILL_CURRENT "This customer has ordered within the last two years." where the keywords CREATE EXCEPTION are followed by the exception's name and the text of the error message for the exception. To raise this exception in a trigger or stored procedure use the EXCEPTION keyword as shown below. EXCEPTION CUSTOMER_STILL_CURRENT; When you raise an exception execution of the trigger or stored procedure terminates. Any statements in the trigger or stored procedure that follow the EXCEPTION statement are not executed. In the case of a BEFORE trigger the update that fired the trigger is aborted. Finally, the exception's error message is returned to the application. You can drop an exception using the DROP EXCEPTION statement and alter the message associated with an exception using the ALTER EXCEPTION STATEMENT. For example: ALTER EXCEPTION CUSTOMER_STILL_CURRENT "This customer is still active."; DROP EXCEPTION CUSTOMER_STILL_CURRENT; Using Generators Interbase does not have an autoincrementing field type. Instead it has a more flexible tool called a generator. A generator returns an incrementing value every time you call it. To create a generator use CREATE GENERATOR as follows: CREATE GENERATOR CUSTOMER_ID; Note that there is no DROP GENERATOR statement. Once you create a generator it is part of the database forever. This is not really a problem since a generator is just a single four byte entry in one of the system tables. To get the next value from a generator use the GEN_ID function. For example: GEN_ID(CUSTOMER_ID, 1); The first parameter is the name of the generator and the second is the increment. In the example above the value returned will be one greater than the last value. The increment can be set to any value including zero, which is very handy for obtaining the current value of the generator without changing it. You can also change the current value of a generator any time you wish using the SET GENERATOR statement as follows: SET GENERATOR CUSTOMER_ID TO 1000; Note that a generator will not be rolled back if you call GEN_ID from within a transaction and then rollback the transaction. Generators are frequently used in triggers to provide a unique surrogate primary key value as in the following example. CREATE TRIGGER SET_EMP_NO FOR EMPLOYEE ACTIVE BEFORE INSERT POSITION 0 AS BEGIN new.emp_no = gen_id(emp_no_gen, 1); END ^ You can also call GEN_ID in a stored procedure that returns the value from the generator in an output parameter to the client application. The client can then assign the value to the primary key and display it to the user when the client creates a new record but before it has been posted. In this case you might also want to include a trigger, like the one above, in case a record is inserted by some client that does not provide the primary key value. All you need to do is alter the code to: IF new.emp_no IS NULL THEN new.emp_no = gen_id(emp_no_gen, 1); Now the trigger will supply a value for the key field only if it is null. Changing and Deleting Triggers You can use the ALTER TRIGGER statement to alter either the header information or the body of the trigger. The most common use of altering the header information is to activate or deactivate a trigger. Another use is to change the position of the trigger. The following statement will inactivate the trigger. ALTER TRIGGER SET_EMP_NO INACTIVE; To alter just the body of the trigger supply its name with no other header information and the new body as shown below. You can also change the header and body at the same time. ALTER TRIGGER SET_EMP_NO AS BEGIN IF new.emp_no IS NULL THEN new.emp_no = gen_id(emp_no_gen, 1); END ^ To drop a trigger use DROP TRIGGER. For example: DROP TRIGGER SET_EMP_NO; Summary Stored procedures and triggers are at the heart of developing client/server applications. Using stored procedures and triggers you can: Reduce network traffic. Create a common set of business rules in the database that apply to all client applications. Provide common routines that are available to all client applications thereby reducing application development and maintenance time. Centralize processing on the server and reduce the hardware requirements of the client. Improve application performance. For more information on Interbase stored procedures and triggers see the Data Definition Guide and the Language Guide as well as the stored procedures and triggers in the sample EMPLOYEE.GDB database.JLK