I am a big fan of DB2 Triggers. I've used them for years. Triggers are a great way to verify data, capture any file modification (add, delete, change), or start other processes based on a change in a database without retooling existing applications. The typical steps for an RPG solution with triggers are
How would you like to take three steps down to one? Well, you can with SQL triggers. This article introduces you to DB2 file triggers using SQL, which is available with V5R1 and later versions of the operating system.
If you're new to triggers, you may want to read the article "Uncovering V5R1 Triggers" first for background information; doing so will make this article easier to understand. Also, to catch up on SPL error handling, see "Inside V5R2 SPL Error Handling: Part 1: Fundamentals" and "Inside V5R2 SPL Error Handling: Part 2: Coding a Bulletproof Stored Procedure".
The best way to show you how to create an SQL trigger is with an example where you can follow along without any worry of trampling on your production files. I'll use the readily available table (i.e., database file) QIWS/ QCUSTCDT. If you have iSeries Access on your system, you should have this table. Figure 1 shows the structure.
I want a trigger that is fired only when a change is made to the credit limit column (i.e., field) CDTLMT or the credit due column CDTDUE. This is a column-sensitive trigger, as I want to do something only when a column within the table is updated. I want the SQL trigger to look at two conditions:
I'll write out the exception entries to another table, CREDITCHG. Figure 2 shows the table's columns. (You can create CREDITCHG with DDS or SQL.)
Figure 3 contains the SQL statement to add the trigger to the table QIWS/QCUSTCDT. The body of the trigger (following the BEGIN statement) conditionally inserts a row (or rows) into the CREDITCHG table. Let's go through the statement carefully, and I'll explain options I used to create this SQL trigger.
The line at A shows the name of the trigger, VERIFYCHG, and where the trigger object will be stored, MYLIB.
The line at B indicates when to fire the trigger: after an UPDATE operation that references either the CDTDUE or the CDTLMT column. When no columns are specified, the trigger is fired every time the row is updated.
The lines at C specify how to reference the before and after values of the columns: Using the NEW ROW AS and the OLD ROW AS clauses, the trigger can access the before-update (old) and after-update (new) values for all columns.
The line at D indicates the granularity of the trigger: The FOR EACH ROW specifies that the trigger is called once for each updated row. For a multirow SQL UPDATE statement, MODE specifies when to take the trigger actions. MODE DB2ROW specifies that the trigger will be called immediately after each row is updated, rather than after the last row has been updated.
The lines at E indicate the conditions that must be satisfied for the trigger action to occur: In this example, either the credit due must have been changed to more than $500 or a non-zero credit limit value must have been changed. Using old and new values in a WHEN clause allows you to execute the trigger actions for a limited set of cases. Note how the column values are referenced. For example, NEWROW.CDTDUE is the new value of the column after the update.
At F, the BEGIN statement designates the beginning of the trigger's main block of executable code.
As with other programming languages, you can define variables. At G, TriggerRsn is a character-type variable with a length of 6, and its default value is Null. The variable will be used to store text identifying the reason for the exception row. The other two variables are numeric variables that hold the old and new values if an exception condition occurs.
Always make sure you have some sort of error handling in your SQL procedures. At H, I've set an exit handler for the entire procedure so that if something bad happens in the procedure, the value 01U91 will be returned as the SQLSTATE setting. Triggers can't return a result set or result variable, so setting the SQLSTATE is the best alert for a trigger.
I'm using the same trigger for two conditions, so at I in Figure 3, I test for each of the two cases. If the credit due value is greater than 500, I set the exception text in TriggerRsn and store the values I'll insert in my exception table later.
Then, at J, I handle the changes to CDTLMT of 10 percent or more.
I can now check (at K) to see whether either condition occurred by testing the value of TriggerRsn. If it isn't Null, I use the saved values to INSERT a row in the CREDITCHG table.
At L, The END statement indicates the end of the procedure.
All done! The SQL source code in Figure 3 is all we need to add the trigger to the table and execute our trigger logic.
The SQL procedure source can be created four different ways:
RUNSQLSTM SRCFILE(MYLIB/QSQLSRC) SRCMBR(VERIFYCHG) NAMING(*SQL)
If the statement is correct, the SQL procedure is created. You'll find three spooled files, and if you are curious, you can look at the C source code that the process creates to build the C program. In our example, you'll find an object MYLIB/VERIFYCHG, type CLE. If you have to debug the procedure, you'll find yourself viewing C code.
To test to see whether the trigger code works, you can use SQL statements. Figure 4a shows two SQL statements, and Figure 4b shows the results in the mylib.creditchg table.
To learn more about SQL procedures and triggers, see the IBM Redbook Stored Procedures, Triggers and User Defined Functions on DB2 Universal Database for iSeries (SG24-6503) at ibm.com/redbooks.
Fire those triggers and enjoy!
Jef Sutherland is an iSeries NEWS technical editor and the vice president of IS for Kampgrounds of America, Inc., in Billings, Montana. He has worked with RPG since 1987 and with various Windows development environments for more GPFs and reboots than he cares to count. Jef is the co-author, with Bryan Meyers, of the book VisualAge for RPG by Example.