Top Banner
1 May 19, 2008 • 04:00 p.m. – 05:00 p.m. Platform: DB2 for z/OS David Churn DST Systems, Inc. Session: F04 Adventures using Triggers, Stored Procedures, and User Defined Functions Implementing triggers and stored procedures on your database can seem like a trip through the jungle. This session is designed to guide you past some of the pitfalls and show you some common design patterns that DST has found useful in developing triggers and stored procedures. We will also talk about calling user defined functions from triggers. Tips for working with your DBA and SQL examples will be included.
49

Adventures Using Triggers, Stored Procedures, and User ...

Mar 10, 2023

Download

Documents

Khang Minh
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Adventures Using Triggers, Stored Procedures, and User ...

1

May 19, 2008 • 04:00 p.m. – 05:00 p.m.Platform: DB2 for z/OS

David ChurnDST Systems, Inc.

Session: F04

Adventures using Triggers, Stored Procedures, and User Defined Functions

Implementing triggers and stored procedures on your database can seem like a trip through the jungle. This session is designed to guide you past some of the pitfalls and show you some common design patterns that DST has found useful in developing triggersand stored procedures. We will also talk about calling user defined functions from triggers. Tips for working with your DBA and SQL examples will be included.

Page 2: Adventures Using Triggers, Stored Procedures, and User ...

2

2

Getting our bearings

• DST database environment• Simplifying development• Verifying the data• Updating the data• Imitating referential constraints• Synchronizing the data

Trigger processing is affected by the environment to which it is added. To better understand why DST has made some of the choices in thepresentation, it is important to know a little more about the DST environment.

DST has put some guidelines in place to simply how they develop triggers, stored procedures, and user defined functions. The most useful ones will be presented here. Following that are examples of triggers developed under these guidelines.

Page 3: Adventures Using Triggers, Stored Procedures, and User ...

3

3

Disclaimer

• The information is presented “as is”without any warranty either expressed or implied. The use of this information is the client’s responsibility.

Page 4: Adventures Using Triggers, Stored Procedures, and User ...

4

4

Disclaimer

• The presentation assumes that some trigger basics are known to the reader.• BEFORE vs. AFTER triggers• Row level vs. Statement level• Error after 16 levels of activation• Invoking a User Defined Function

In order to fit all the material into this presentation some basics were not included in the actual presentation. The short version of these topics follows.

BEFORE triggers can modify the SQL change that fired it (INSERT, UPDATE, or DELETE) but cannot issue SQL changes. The originating change has happened to the table when it is fired.

AFTER triggers can issue SQL changes. The originating SQL change has been applied to the table. Any error will have to rollback the originating change too.

Row level triggers fire for each row that is changed. Stored Procedures and User Defined Functions called from this type of trigger only have to handle one row but run multiple times when a SQL statement changes multiple rows.

Statement level triggers fire for each statement (INSERT, UPDATE, or DELETE) that changes the table. Stored Procedures and User Defined Functions call from this type of trigger are coded to process a table of rows but are called only once.

There are scenarios where a trigger updates a table with a trigger on it. DB2 tracks how many levels nested triggering is happening at run time. When the seventeenth trigger is called it gets an error. (SQLCODE=-724 SQLSTATE=54038 Maximum depth of cascaded triggers exceeded.)

The basics on invoking a user defined function is covered in slide 20.

Page 5: Adventures Using Triggers, Stored Procedures, and User ...

5

5

.

http://www.dstsystems.com

About DST Systems

•Leading provider of computer software solutions and services, NYSE listed – “DST”•Revenue $2.24 billion•110 million+ shareowner accounts

•24,000 MIPS•150 TB DASD•145,000 workstations•462,000 DB2 objects•Non-mainframe: 600 servers (DB2, Oracle, Sybase) with 2.3 million objects

If you have ever invested in a mutual fund, have had a prescription filled, or are a cable or satellite television subscriber, you may have already had dealings with our company.

DST Systems, Inc. is a publicly traded company (NYSE: DST) with headquarters in Kansas City, MO. Founded in 1969, it employs about 12,000 associates domestically and internationally.

The three operating segments - Financial Services, Output Solutions and Customer Management - are further enhanced by DST’s advanced technology and e-commerce solutions.

Page 6: Adventures Using Triggers, Stored Procedures, and User ...

6

6

DST Database Environment

• Each client has its own production system

• Each client has its own tables.

• Around 40 production systems.

There are several product groups, each one with multiple “platforms”, TEST through STAGE through PROD. The PROD systems are replications of the same set of tables. Our largestproduct group, Mutual Funds, has around 40 identical PROD platforms with over 3,000 tables in each platform.

Most tables are small, with a few large ones (millions of rows), but none in the super-large range. Applications are being enhanced continually and the table count continues to grow. With so manytables, changes are a consistent, pressing topic among our DBA staff.

Page 7: Adventures Using Triggers, Stored Procedures, and User ...

7

7

DST trigger history• 5 years experience• Almost 100 individual triggers

• Usually call a Stored Procedure or User Defined Function

• Written process specifying roles• Applications Development• Database Administrators• Database Consultants

• Stored Procedures and User Defined Functions usually written in COBOL

DevelopersProject Leaders

AnalystsProgrammers

hundreds

DBAtens

DB Consulting

ones

Our development staff is heavily slanted toward application development. Most enhancements, around 95%, are driven from theapplications area to provide new functionality. Since there are more developers than data base associates, any complex logic gets implemented in Stored Procedures and User Defined Functions. Our scanning tools make it easier to find this logic in programs than in the catalog.

DBAs assist with the enhancements and handle implementing changes to each production system’s database. They review and install the triggers.

Database Consulting assists with enhancement SQL development andwrites the triggers.

Page 8: Adventures Using Triggers, Stored Procedures, and User ...

8

8

Why DST uses triggers

• Better data integrity• Improves redundant data handling

• Queries on redundant data run faster• Fewer synchronization jobs required

• Reduce the number of coding changes• Centralize business logic

• Reduce the functions allowed on a table

SP/UDF

table

DST uses triggers for the classic reasons. We have triggers that handle data validation and updating other tables (i.e.. to support denormalization). Since we have many production environments some of the updates cross production boundaries.

DST has also implemented a couple triggers that block certain types of SQL changes. Specifically, there is one trigger that blocks updates to a history table and another trigger that blocks deletes to a bank information table. More on these triggers is presented later.

Page 9: Adventures Using Triggers, Stored Procedures, and User ...

9

9

Guidelines for the environmentGuidelines for the environment

Simplifying Development

Page 10: Adventures Using Triggers, Stored Procedures, and User ...

10

10

Simplifying Development

• Parsing trigger text• Managing timing issues• Flexibility for future changes• Invoking a User Defined Function • Handling errors• DB2 version 8 incompatibilities

• INSERT within SELECT• Common Table Expressions

The five practices listed are guidelines that DST uses to managethe complexity of adding triggers to our production tables.

The incompatibilities listed are a couple issues that we have encounter that have changed the designs of our processes including the triggers.

DST prefers row level triggers over statement level triggers. It keeps the code less complex. In a scenario where triggers are added, we are not as concerned about performance.

Page 11: Adventures Using Triggers, Stored Procedures, and User ...

11

11

Parsing Triggers

• Migration tool may remove extra spaces• Obtain a program to format the triggers

• 3rd party tools often handle this• REXX trigger dump example available

Migration tools have the desired emphasis on efficiently storinginformation in the catalog. Efficient storage does not lend itself to human reading and understand. DST wants to use the vendor tool without customization so our solution is to format the text after retrieving it and before trying to read it.

The following slides show the contrasting examples.

Page 12: Adventures Using Triggers, Stored Procedures, and User ...

12

12

Parsing Triggers - UnformattedCREATE TRIGGER schema.DATYPTR1 AFTER INSERT ON schema.DATYP REFERENCING NEW AS N FOR EACH ROW MODE DB2SQL BEGIN ATOMIC CALL schema.SPDAVE ('DATYP' CONCAT 'I' CONCAT DIGITS(N.IDENTITY_ID) CONCAT CHAR(N.UPDATE_TSP) CONCAT N.OPERATOR_ID CONCAT CHAR(N.DATE_DT) CONCAT CHAR(N.TIME_TM) CONCAT DIGITS(N.SMALLINT_NBR) CONCAT CASE WHEN N.SMALLINT_NBR < 0 THEN '-' ELSE '+' END CONCAT DIGITS(N.INTEGER_NBR) CONCAT CASE WHEN N.INTEGER_NBR < 0 THEN '-‘ELSE '+' END CONCAT CHAR(N.REAL_NBR) CONCAT CHAR(N.DOUBLE_NBR) CONCAT DIGITS(N.SM_DECIMAL_NBR) CONCAT CASE WHEN N.SM_DECIMAL_NBR < 0 THEN '-' ELSE '+' END CONCAT DIGITS(N.LG_DECIMAL_NBR) CONCAT CASE WHEN N.LG_DECIMAL_NBR < 0 THEN '-' ELSE '+' END CONCAT DIGITS(N.DECIMAL_PCT) CONCAT CASE WHEN N.DECIMAL_PCT < 0 THEN '-‘ END CONC

The text displayed is an efficiently stored trigger coming straight out of the catalog. Unfortunately, it is not efficiently displayed for a human’s understanding. See the next slide for a more human readable format.

Page 13: Adventures Using Triggers, Stored Procedures, and User ...

13

13

Parsing Triggers - FormattedCREATE TRIGGER schema.DATYPTR1

AFTER INSERT ON schema.DATYPREFERENCING NEW AS NFOR EACH ROW MODE DB2SQL

BEGIN ATOMIC

CALL schema.SPDAVE ('DATYP'CONCAT 'I' CONCAT DIGITS(N.IDENTITY_ID)CONCAT CHAR(N.UDPATE_TSP)CONCAT N.OPERATOR_IDCONCAT CHAR(N.DATE_DT)CONCAT CHAR(N.TIME_TM)

...

The text displayed was generated by a REXX routine, TRIGSQL, which may be obtained by request from the presenter. Portions of this routine were developed from code introduced in Rich Fazio’s presentation, REXX and DB2 – The Stuff NOT in the Manuals given at the International DB2 Users Group – North America, 2003.

One other tip is to include the REFERENCING clause in a trigger. Key strokes can be saved by abbreviating the IBM transition table definitions with something shorter.

Page 14: Adventures Using Triggers, Stored Procedures, and User ...

14

14

Managing Timing Issues

• Triggers execute in the order created• One trigger per action combination

• 6 total triggers on one table• Avoids dependencies between

triggers• Handle multiple logical conditions

through a Stored Procedure or User Defined Function

Any DST table can have a maximum of six total triggers; Before INSERT, Before UPDATE, Before DELETE, After INSERT, After UPDATE, After DELETE.

For one action, DB2 will run the triggers in the order that they were created. To avoid any dependencies between the triggers, run only one trigger per action. If you call a stored procedure or invoke a user defined function, then you can put any dependencies in the program. That will allow your developers to manage the dependencies instead of the DBAs.

If you find this too constraining, please reference the 2004 International DB2 User Group presentation, Did we just create 1,024 triggers? by Mark Harris and Steve Heichelheim for recommendations on other ways to manage the timing issue.

Page 15: Adventures Using Triggers, Stored Procedures, and User ...

15

15

Flexibility for Future Changes

• Common character parameter block• Allows triggers and programs to install

separately• Program definitions do not change• New; install program then trigger• Modify; install new trigger then new

program

v1 Fillerv2 v3

4,000 bytes

DST made the parameter blocks 4,000 bytes long because it seems big enough to store our desired parameters. When there is a need for a larger parameter block the size will be increased.

All DST Stored Procedures called by triggers have one similar definition SQL that only requires a name change.

CREATE PROCEDURE schema.SPXXXX

( IN PARM_IN VARCHAR(4000) )

...your supporting parameters here...

;

Page 16: Adventures Using Triggers, Stored Procedures, and User ...

16

16

Character Parameter Block• Convert all columns to character• Allows CONCAT• Numbers

• DIGITS for numbers without the decimal point

• Handle +/- sign• VARCHAR columns

• Decide on maximum length• COALESCE any null columns

In general terms, these are the guidelines to assemble a parameter block that contains all the columns that need to be passed to a stored procedure or user defined function. We use the CONCAT statement to pull them all together but it only works with character columns.

All numeric columns need to be converted to character columns. The CHAR function does not produce a result that can be used with COBOL definitions. The DIGITS function gives a result that fills the high order digits with zero and without a decimal point.

For variable character columns, it is easier to convert the column to a fixed length so you can predict where the following columns will be placed in the parameter block.

NULL columns have to use the COALESCE function. A null value concatenated to a value column becomes a null value.

Page 17: Adventures Using Triggers, Stored Procedures, and User ...

17

17

Character Parameter Block• Example (nullable)DECIMAL_AMT DECIMAL(15,2)

• On the triggerCONCAT DIGITS(COALESCE(DECIMAL_AMT,0))CONCAT CASE WHEN DECIMAL_AMT < 0

THEN ‘-’ ELSE ‘+’ ENDCONCAT CASE WHEN DECIMAL_AMT IS NULL

THEN ‘Y’ ELSE ‘N’

This is an example of the most involved type of column, a nullable decimal column. The trigger needs to convert the value of DECIMAL_AMT into characters without a decimal point. It also needs to supply a value of zero when the column is null because concatenating a null value makes the entire result null. Since DIGITS does not handle a sign the trigger needs to evaluate and place a sign on the field. Note that a null column will get a positive sign as the default. Finally the trigger needs to check for the null value and pass an indicator about the column.

The rules for these transformations do not change. The trigger generation process could be automated to save time.

Page 18: Adventures Using Triggers, Stored Procedures, and User ...

18

18

Character Parameter Block• Example (nullable)DECIMAL_AMT DECIMAL(15,2)

• On the copybook05 DECIMAL-AMT PIC 9(13)V99

SIGN TRAILING SEPARATE.05 DECIMAL-AMT-NULL-CD PIC X.

88 DECIMAL-AMT-NULL-YES VALUE ‘Y’.88 DECIMAL-AMT-NULL-NO VALUE ‘N’.

On the copybook, the column is represented in display format. With the addition of the SIGN TRAILING SEPARATE clause on the end, COBOL will treat the column as a numeric field and handle the sign correctly. The null indicator has to be checked to correctly process this field. When the column is null DECIMAL-AMT = 0.

The rules for these transformations do not change. This COBOL copybook generation process could be automated to save time. Ideally, it would be combined with the trigger generation automation to have one process.

Page 19: Adventures Using Triggers, Stored Procedures, and User ...

19

19

Character Parameter Block

CHAR(varchar_tx,n)

05 VARCHAR-TX.49 VARCHAR-TX-LEN PIC S9(10).49 VARCHAR-TX-TEXT PIC X(n).

CASE WHEN LENGTH(varchar_tx) <= n THEN DIGITS(LENGTH(varchar_tx))ELSE DIGITS(n) END

VARCHAR(n)

05 COL-TSP PIC X(26).CHAR(col_tsp)TIMESTAMP

05 COL-TM PIC X(8).CHAR(col_tm)TIME

CASE WHEN smallint_nbr < 0 THEN '-' ELSE '+' END

05 SMALLINT-NBR PIC S9(5)SIGN TRAILING SEPARATE.

DIGITS(smallint_nbr)SMALLINT

same as VARCHARLONGVAR

CASE WHEN integer_nbr < 0 THEN '-' ELSE '+' END

05 INTEGER-NBR PIC S9(10)SIGN TRAILING SEPARATE.

DIGITS(integer_nbr)INTEGER

CASE WHEN decimal_nbr < 0 THEN '-' ELSE '+' END

05 DECIMAL-NBR PIC S9(p - s)V9(s) SIGN TRAILING SEPARATE.

DIGITS(decimal_nbr)DECIMAL(p,s)

05 COL-DT PIC X(10).CHAR(col_dt)DATE

05 COL-TX PIC X(n).col_txCHAR(n)

COBOL copybooktrigger definitionDB2 column type

These are the most common transformations that are needed and the resulting COBOL definition.

Page 20: Adventures Using Triggers, Stored Procedures, and User ...

20

20

Invoking a User Defined Function

• VALUES clause• SET statement• SELECT statement• SELECT with INNER JOIN

• Allows trigger to pass back SQLSTATE from user defined function

• Allow one user defined function call for multiple conditions

• Can return other values

There are several methods to invoke a user defined function. DST has standardized on one way to invoke their user defined functions using SELECT with an INNER JOIN. The SET statement is also useful and proved to be an exception to our standard.

The first two methods to invoke the user defined function work well as long as they do not want to report an error. At DST, the second way is used for small routines that perform some supplemental function. See the hashing example in slide 38.

VALUES example (from Redbook)VALUES(VALSAL(O.EMP_NO,O.SALARY,N.SALARY));

SET exampleSET N.SALARY = (VALSAL(O.EMP_NO,O.SALARY));

SELECT example with error handling (mostly from Redbook)SELECT CASE

WHEN VALSAL(N.EMP_NO,O.SALARY,N.SALARY) = '1'THEN COALESCE(RAISE_ERROR(‘JKLMN','SOME MESSAGE'),SPACE(1)) END

WHEN VALSAL(N.EMP_NO,O.SALARY,N.SALARY) = ‘2’THEN COALESCE(RAISE_ERROR(‘KLMNP’,’ANOTHER MESSAGE’),SPACE(1)) END

WHEN VALSAL(N.EMP_NO,O.SALARY,N.SALARY) = ‘3’THEN COALESCE(RAISE_ERROR(‘LMNPQ’,’A DIFFERENT MESSAGE’),SPACE(1)) END

FROM SYSIBM.SYSDUMMY1;

This last method invokes a user defined function and can return a custom error. The performance disadvantage is that the user defined function can be invoked multiple times, up to 3 in this example. Also, the custom SQLSTATE is coded in the trigger. The another set of codes have to be defined in the trigger and the user defined function. Changes require DBA involvement.

Page 21: Adventures Using Triggers, Stored Procedures, and User ...

21

21

Invoking a User Defined FunctionBEGIN ATOMIC

SELECT CASE WHEN B.UF_STATE <> '00000'THEN COALESCE ( RAISE_ERROR (B.UF_STATE,'SEE SQLSTATE_TBL FOR MESSAGE TEXT. SQLSTATE='

CONCAT B.UF_STATE), SPACE(1)) ENDFROM SYSIBM.SYSDUMMY1 AS A INNER JOIN (

SELECT CAST ( schema.VALSAL (SUBSTR (DIGITS(O.EMPNO)

CONCAT DIGITS(O.SALARY)CONCAT CASE WHEN O.SALARY < 0 THEN '-' ELSE '+' ENDCONCAT DIGITS(N.SALARY)CONCAT CASE WHEN N.SALARY < 0 THEN '-' ELSE '+' ENDCONCAT SPACE (4000) , 1, 4000) )

AS CHAR (5) ) AS UF_STATE FROM SYSIBM.SYSDUMMY1) AS B

ON 1 = 1 ; END #

Change module nameand the parameter list

This method invokes the user defined function once. The trigger can recognize a SQLSTATE passed back from the user defined function. All the coding is embedded inside the user defined function. If you want to add another condition just change the user defined function.

Page 22: Adventures Using Triggers, Stored Procedures, and User ...

22

22

Handling Errors• SQLCODE = -723 (trigger failed)

• Need the formatted message from DSNTIAR to get details

• SQLCODE = -430• Program Abend

• SQLCODE = -438 or +438• RAISE_ERROR• SIGNAL SQLSTATE

• Triggers and User Defined Functions can return a custom SQLSTATE

• Where are the other messages?• COBOL DISPLAY shows in WLM SYSOUT

Determining what the problem is when a trigger fails can be a challenge. The trigger failure would give a SQLCODE -723. The DSNTIAR information shows that the problem was actually with the Stored Procedure abending (-430).

DSNT408I SQLCODE = -723, ERROR: AN ERROR OCCURRED IN A TRIGGERED SQLSTATEMENT IN TRIGGER TRWTRIG.TR3TR001, SECTION NUMBER 2.INFORMATION RETURNED: SQLCODE -430, SQLSTATE 38503, AND

MESSAGETOKENS PROCEDURE,TRWSTPR.SPT002 ,TR

DSNT418I SQLSTATE = 09000 SQLSTATE RETURN CODEDSNT415I SQLERRP = DSNX9CAC SQL PROCEDURE DETECTING ERRORDSNT416I SQLERRD = 0 0 0 -1 0 0 SQL DIAGNOSTIC INFORMATIONDSNT416I SQLERRD = X'00000000' X'00000000' X'00000000' X'FFFFFFFF'X'00000000' X'00000000' SQL DIAGNOSTIC INFORMATION

Our COBOL programs use a common SYSOUT in WLM to DISPLAY their messages. If multiple stored procedures and/or user defined functions can run in that WLM, it is important to recognize which messages go together. Putting the program name and an execution time on each message is vital to know the message that go together.

Page 23: Adventures Using Triggers, Stored Procedures, and User ...

23

23

Custom SQLSTATE guidelines• First character must be I through Z

• IBM requirement• Watch for offensive combinations

• ID10T, L0SER, MORON• Avoid confusion with I, 1, O, and 0• Generic SQLSTATE for unexpected errors

• ZZZZZ, DB2 returned an unexpected error

• Create a table to explain the SQLSTATE

There are specific rules for the custom SQLSTATE that varies by whether it is sent from RAISE_ERROR or SIGNAL SQLSTATE. See the DB2 SQLreference under the function RAISE_ERROR or the command SIGNAL SQLSTATE for details. The guidelines given in this slide are common to both.

The SQLSTATE table should have at least three columns.SQLSTATE_ID CHAR(5): The actual SQLSTATE in the SIGNAL_ERROR command.SQLSTATE_TXT CHAR(80): A message that you can use on a screen.SQLSTATE_DSC_TXT CHAR(2000): What the operator or help desk person should do if this error is returned.

Other generic SQLSTATE include;ILOOP = This row will cause an infinite loop of routing rows.PKUPD = This rows primary key cannot be updated.

Page 24: Adventures Using Triggers, Stored Procedures, and User ...

24

24

DB2 version 8 IncompatibilitiesSELECT x_id, y_ts, z_dtFROM FINAL TABLE (INSERT INTO (y_ts, z_dt, a_qty)VALUES(current timestamp, …)

• Returns a “not found” error when AFTER trigger modifies the triggering table

• GET DIAGNOSTICS shows SQLCODE -989• IBM will enhance documentation

Based upon the text in the Messages and Codes manual, we concluded that IBM will not support this combination in the future. Since DST already has triggers that modify the triggering table, we have to manage programs that are going to use INSERT with SELECT.

The following text is from DB2 V8 Messages and Codes, pages 132-133-989 AFTER TRIGGER trigger-name ATTEMPTED TO MODIFY A ROW IN TABLE table-name THAT WAS INSERTED BY AN INSERT STATEMENT WITHIN A SELECT STATEMENT Explanation: An INSERT statement was specified in the FROM clause of a SELECT statement of a subselect or a SELECT INTO statement, but the underlying target base table of the INSERT has an AFTER trigger defined which modifies the table being inserted. This is disallowed.System Action: The statement cannot be processed. Programmer Response: Avoid using the INSERT statement within SELECT, or change the trigger so that it does not modify the table that is the target of the INSERT statement. SQLSTATE: 560C3

Page 25: Adventures Using Triggers, Stored Procedures, and User ...

25

25

DB2 version 8 Incompatibilities

> ROWS FETCHED.........0000000000> ERRORS ENCOUNTERED...0000000002 >ROW 00000000001 SQLSTATE=02000 SQLCODE=0000000100

ROW NOT FOUND FOR FETCH, UPDATE, OR DELETE, OR THE RESULT OF A QUERY IS AN EMPTY TABLE

>ROW 00000000001 SQLSTATE=09000 SQLCODE=0000000723-AN ERROR OCCURRED IN A TRIGGERED SQL STATEMENT IN TRIGGER ARZTRIG.DATYP001. INFORMATION RETURNED: SQLCODE -989, SQLSTATE 560C3, MESSAGE TOKENS DATYP001,DATYP, SECTION NUMBER 2

Insert within SELECT error messages

In COBOL terms, the code to display the given messages is given below.EXEC SQL

GET DIAGNOSTICS:SQL-ROW-QTY = ROW_COUNT

, :SQL-ERROR-QTY = NUMBEREND-EXEC.DISPLAY '> ROWS FETCHED.........' SQL-ROW-QTY.DISPLAY '> ERRORS ENCOUNTERED...' SQL-ERROR-QTY.PERFORM

VARYING SQL-LOOP-QTY FROM +1 BY +1UNTIL SQL-LOOP-QTY > SQL-ERROR-QTY

EXEC SQLGET DIAGNOSTICS CONDITION :SQL-LOOP-QTY

:SQL-CODE = DB2_RETURNED_SQLCODE, :SQL-STATE = RETURNED_SQLSTATE, :SQL-ROW-ID = DB2_ROW_NUMBER, :SQL-ERROR-TX = MESSAGE_TEXT

END-EXECDISPLAY 'ROW ' SQL-ROW-ID ') SQLSTATE=' SQL-STATE

' SQLCODE=' SQL-CODEDISPLAY SQL-ERROR-TX

END-PERFORM.

Page 26: Adventures Using Triggers, Stored Procedures, and User ...

26

26

DB2 version 8 IncompatibilitiesWITH SUB_QRY AS (sub-query)SELECT ‘y’FROM A_TABLE, SUB_QRY…

• Cannot use a common table expression in the WHEN clause• Self-referential queries not allowed

• Can use a nested table expression

Nested table expressions work in a triggers WHEN condition. Common table expressions do not. Based on the error message, it looks like the parser is looking for the keyword, SELECT.

CTE error example (self-referential)CREATE TRIGGER schema.DATYPTR1AFTER INSERT ON schema.DATYPREFERENCING NEW AS N FOR EACH ROW MODE DB2SQLWHEN ( EXISTS ( WITH PREV_GRP (LEVEL_QTY, PA_FUND_ID

, PA_SYSTEM_ID, CH_FUND_ID, CH_SYSTEM_ID)AS ( SELECT 1 AS LEVEL_QTY, PA_FUND_ID

, PA_SYSTEM_ID, CH_FUND_ID, CH_SYSTEM_IDFROM schema.DATYPWHERE CH_FUND_ID = N.PA_FUND_ID

AND CH_SYSTEM_ID = N.PA_SYSTEM_IDUNION ALLSELECT P_G.LEVEL_QTY + 1, C_G.PA_FUND_ID

, C_G.PA_SYSTEM_ID, C_G.CH_FUND_ID, C_G.CH_SYSTEM_IDFROM PREV_GRP AS P_GINNER JOIN schema.DATYP AS C_G

ON P_G.PA_FUND_ID = C_G.CH_FUND_IDAND P_G.PA_SYSTEM_ID = C_G.CH_SYSTEM_IDAND P_G.LEVEL_QTY < 10 )

SELECT 1 FROM PREV_GRPINNER JOIN schema.DATYP AS OUTER

ON PREV_GRP.PA_FUND_ID = OUTER.CH_FUND_IDAND PREV_GRP.PA_SYSTEM_ID = OUTER.CH_SYSTEM_ID

WHERE OUTER.PA_FUND_ID = N.PA_FUND_IDAND OUTER.PA_SYSTEM_ID = N.PA_SYSTEM_ID ) )

BEGIN ATOMICSIGNAL SQLSTATE 'ILOOP' ( 'INFINITE UPDATE LOOP DETECTED');

END #---------+---------+---------+---------+---------+---------+---------+-------DSNT408I SQLCODE = -199, ERROR: ILLEGAL USE OF KEYWORD WITH. TOKEN ( SELECT

WAS EXPECTEDDSNT418I SQLSTATE = 42601 SQLSTATE RETURN CODE

Page 27: Adventures Using Triggers, Stored Procedures, and User ...

27

27

Triggers and User Defined Functions

Verifying the Data

Page 28: Adventures Using Triggers, Stored Procedures, and User ...

28

28

Verifying the Data guidelines• Usually with BEFORE triggers• Simple conditions in trigger• Multiple conditions in user defined

function• Avoid “fixing” the data with a trigger

DB2 puts the SQL change on the table between the time the BEFORE and AFTER triggers fire. If you are going to stop an invalid change, then DB2 performs faster by stopping the change before the row is on the table.

Page 29: Adventures Using Triggers, Stored Procedures, and User ...

29

29

History

Disallow updates to history

“ZYXWV, UPDATES ARE NOT ALLOWEDTO THE HISTORY TABLE”

BEFORE UPDATE

• Only changes allowed are INSERT and DELETE

CREATE TRIGGER schema.HISTORY1

NO CASCADE BEFORE UPDATE

ON schema.HISTORY

FOR EACH ROW MODE DB2SQL

BEGIN ATOMIC

SIGNAL SQLSTATE ‘ZYXWV' (‘UPDATES ARE NOT ALLOWED TO THE HISTORY TABLE.');

END #

Page 30: Adventures Using Triggers, Stored Procedures, and User ...

30

30

Process

Validate that a row can exist

“YXWVU, OPTION MUST BE YES TO INSERT”

Fund

BEFORE INSERTWHEN ( EXISTS (SELECT 'Y'

FROM FUND AS F WHERE F.FUND_ID = N.FUND_ID

AND F.PROCESS_CD = 'Y')

Before any rows can be added to the Process table, a matching fund row must be set up on the Fund table with the process option turned on. DST can guarantee that the processing rows will only be set up for processes that are eligible.

CREATE TRIGGER schema.PROCESS1NO CASCADE BEFORE INSERTON schema.PROCESSREFERENCING NEW AS NFOR EACH ROW MODE DB2SQLWHEN ( EXISTS (SELECT 'Y'

FROM FUND AS FWHERE F.FUND_ID = N.FUND_ID

AND F.PROCESS_CD = 'Y')BEGIN ATOMIC

SIGNAL SQLSTATE ‘YXWVU' ( ‘OPTION MUST BE YES TO INSERT’) ;END

Page 31: Adventures Using Triggers, Stored Procedures, and User ...

31

31

UDF VerifyAgreements

AFTER INSERTAFTER UPDATE

WHEN anything changes

Agreements

Agreements should not overlap

many SQLSTATEs

Verification requires AFTER triggers to SELECT

from the triggering table.

When a user defined function needs to access the table it is triggered from it must be an AFTER trigger. To verify an agreement the user defined function has to compare the one inserted with all the other agreements. If the agreements do not overlap, then it is allowed.

CREATE TRIGGER schema.AGRMNT01NO CASCADE BEFORE INSERTON schema.AGREEMENTSREFERENCING NEW AS NFOR EACH ROW MODE DB2SQL

BEGIN ATOMICSELECT CASE WHEN B.UF_STATE <> '00000' THEN COALESCE(RAISE_ERROR(B.UF_STATE

, 'SEE TABLE G66 FOR MESSAGE TEXT. SQLSTATE: ‘ CONCAT B.UF_STATE) , SPACE(1)) END

FROM SYSIBM.SYSDUMMY1 AS A INNER JOIN ( SELECT CAST( MFLFUNC.UF0002(SUBSTR( DIGITS(N.FUND_ID) CONCAT CASE WHEN N.FUND_ID < 0 THEN '-' ELSE '+' END

…many other columns…CONCAT SPACE(4000),1,4000) ) AS CHAR(5)) AS UF_STATE

FROM SYSIBM.SYSDUMMY1) AS B ON 1 = 1 ;

END #

Page 32: Adventures Using Triggers, Stored Procedures, and User ...

32

32

Triggers and Stored Procedures

Updating the Data

Page 33: Adventures Using Triggers, Stored Procedures, and User ...

33

33

Updating the data

• Usually with AFTER trigger• All updates in Stored Procedure

• Coded to handle one or more changes

• Trigger cannot handle return code• Used for “all or nothing” process

• Updating other DB2 tables• Existence Flag• Summary Information

When called by a trigger, a Stored Procedure has no advantage over a User Defined Function. Triggers cannot use anything returned from a stored procedure so multiple result sets or any return codes are useless.

With the DST user defined function invocation, all triggered programs could be user defined functions and get a return SQLSTATE. However, DST still uses Stored Procedures in update situations so modules that change the database are easy to identify.

Page 34: Adventures Using Triggers, Stored Procedures, and User ...

34

34

Fund

Automatic existence flag

DifferentProcess

SP UpdateFund

• Automatically change fund option when process is activated for a fund.

AFTER INSERTAFTER UPDATE OF PROCESS_CDAFTER DELETE

Unlike the user defined function example where an eligible fund should exist, this scenario makes the fund eligible when the process isactivated. The stored procedure can do this because this process has the fund information embedded within the data.

The triggers are written to identify whether the stored procedure is called from an INSERT, UPDATE, or DELETE. The triggers will always pass the FUND_ID. On an update it will pass the old and new values of the PROCESS_CD.

Page 35: Adventures Using Triggers, Stored Procedures, and User ...

35

35

SP UpdateSummary

AFTER INSERTAFTER UPDATEAFTER DELETE

Fund

Updating Summary Information

Account

“JKLMN, Totalmust equal zero”

“KLMNP, Totalmust match”

BEFORE INSERT

BEFORE UPDATEReferentialConstraint

No Delete trigger on Fund due to theReferential Constraint.

The Fund and Account tables have been the core of our business since DST was formed. For the retirement accounts, we need to keep track of the quantities in the account at the fund level. So we not only created a stored procedure to update the fund summary counts, wealso created a couple triggers on the fund level to ensure the information was always accurate. It is vital to our business that these totals are accurate.

These are the triggers that taught us to be aware of the Order of Operations within DB2.

Page 36: Adventures Using Triggers, Stored Procedures, and User ...

36

36

DB2 Order of Operations1.DB2 determines the rows that will be

modified on the triggering table.2.Any BEFORE triggers are executed. These

can only be row level triggers.3. The triggering event (insert, update, or

delete) is completed on the triggering table.

4.DB2 verifies that the new data matches to any check constraints and referential integrity constraints.

5.Any AFTER triggers are executed.

This list of operations is easy to overlook but can have serious impact to your design. DST learned that this list can be crucial to figuring out why the triggered update logic is not working as expected.

Page 37: Adventures Using Triggers, Stored Procedures, and User ...

37

37

Order of Operations

70013600225001240041300312002110011QtyAcct.Fund

70031100210001

TotalFund

UPDATE ACCOUNTSET QTY = 10

WHERE FUND = 1

-90

7001360022500121041103110211011QtyAcct.Fund

7003110029101

TotalFund

On a test platform, DST implemented both the fund and account triggers as row level triggers. While this will always work when there are single row updates to the account table, the graphic shows how a multiple row UPDATE will always fail from the row level triggers.

There are three possible ways to avoid this.1) Make the account triggers statement level and pass in a transition

table.2) Change the stored procedure to query the account data and post

the right number.3) Drop the fund update trigger.

Page 38: Adventures Using Triggers, Stored Procedures, and User ...

38

38

User Defined Function Hash Value• Calculating a hash value from other

columns

UDF CalculateHash

BEFORE INSERTBEFORE UPDATE OF

address columns

Address

SET N.HASH_NBR = schema.UFHASH(concatenated address lines)

The COBOL user defined function is efficient in calculating this value.

CREATE TRIGGER MYTRIG.DATYPTR1

NO CASCADE BEFORE INSERT

ON schema.DATYP

REFERENCING NEW AS N

FOR EACH ROW MODE DB2SQL

BEGIN ATOMIC

SET N.HASH_NBR = schema.UFHASH(N.ADDRESS_LINE_1

CONCAT COALESCE(N.ADDRESS_LINE_2,SPACE(35))

CONCAT CHAR(N.CITY_NAME,35)

CONCAT COALESCE(N.STATE_ABBR_CD,SPACE(2)) ) ;

END #

Page 39: Adventures Using Triggers, Stored Procedures, and User ...

39

39

Imitating Referential Constraints

Beyond 1 Parent and 1 Child

Page 40: Adventures Using Triggers, Stored Procedures, and User ...

40

40

Transmission

Parent defined AFTER Children

“YXWVU, DETAILS NOT FOUND TO TRANSMIT”

ColumnDetails

BEFORE INSERT WHEN ( EXISTS (SELECT ‘Y’FROM COLUMN_DETAIL AS CDWHERE CD.XMIT_ID = N.XMIT_ID

AND CD.TABLE_ID2= N.TABLE_ID ) )

The Column_Details table contains rows specifying which columns are included for various sets of data. One of the sets of data specifies the details to include on a transmission. The transmission should not be defined until the details are ready. The trigger checks that before we can create a transmission that the details are ready.

CREATE TRIGGER schema.XMIT0001NO CASCADE BEFORE INSERTON schema.TRANSMISSIONREFERENCING NEW AS NFOR EACH ROW MODE DB2SQLWHEN ( EXISTS (SELECT ‘Y’

FROM COLUMN_DETAIL AS CDWHERE CD.XMIT_ID = N.XMIT_ID

AND CD.TABLE_ID2 = N.TABLE_ID ) )BEGIN ATOMIC

SIGNAL SQLSTATE ‘YXWVU' ( ‘DETAILS NOT FOUND TO TRANSMIT’) ;END

Page 41: Adventures Using Triggers, Stored Procedures, and User ...

41

41

ConnectXmit

AFTER INSERTAFTER UPDATE

OF primary keyAFTER DELETE

Xmit

Connecting transmissions

Detail

VerifyXmit

ConnectDetail

AFTER INSERTAFTER UPDATE

OF primary key, flagsAFTER DELETE

BEFORE UPDATEOF flags

UDF

These triggers, stored procedures, and user defined function are used to tie two transmissions together. Our difficulty is that logically the transmission should tie together with referential constraints but physically we cannot guarantee which transmission will come in first.

Page 42: Adventures Using Triggers, Stored Procedures, and User ...

42

42

Synchronizing the Data

Page 43: Adventures Using Triggers, Stored Procedures, and User ...

43

43

SP Formatand Send

AFTER INSERTAFTER UPDATE

Banks

Synchronizing with VSAM files

AFTER DELETE“LMNPQ, DELETES ARE NOT

ALLOWED ON THIS TABLE”

MQ

CICS

Update

Banks

The Stored Procedure is formatting and sending an MQ message to CICS to update the VSAM file of Bank information. Initially DST used EXCI calls to kick off the CICS update program but ran into difficulties with committing the work. The difficulties were related to the DST environment and do not reflect on the capabilities of the components.

Page 44: Adventures Using Triggers, Stored Procedures, and User ...

44

44

Datasync Introduction• Some data

needs to be shared

• DST has all the data

There is some information that our clients want to share betweentheir platforms. A good is example is the price for shares of afund that one client has agreed to let another client sell. There are limitations on the sending and receiving side to decide whether an update should take place.

Page 45: Adventures Using Triggers, Stored Procedures, and User ...

45

45

SPSend Prices

AFTER INSERTAFTER DELETEAFTER UPDATE

Prices

Datasync trigger andStored Procedure

MQ

Routing

UpdatePrices

Prices

Parent System Child System

Again the Stored Procedure is used to format and send a message through MQ. The DST routing table has the information on which other systems to send the changes.

There are triggers on the child system that may fire to send the update elsewhere.

Page 46: Adventures Using Triggers, Stored Procedures, and User ...

46

46

Prevent an infinite update loop in Datasync (verifying the data)

UDF Findfirst parent

AFTER INSERTAFTER UPDATE

WHEN sending/receiving changes

Routing

Currently oneSQLSTATE

To avoid putting together an infinite loop of updates, we added triggers to ensure that an update cannot make it back to the parent system. This user defined function was written before v8 recursive SQL was available. After version 8 installation DST has simplified the user defined function design to use recursive SQL. Ultimately, DST would like to put the logic in the trigger.

Page 47: Adventures Using Triggers, Stored Procedures, and User ...

47

47

Conclusions• Less adventure when with guidelines in

place.• Watch for trigger interactions

• DB2 fails on the 17th nested trigger• Other DB2 objects can influence trigger

design.

With triggers you really want to aim then fire. By taking some time to figure out the best options for the environment, a company can save a lot of time learning while doing.

DST has found it useful to have someone looking for interactionsbetween triggers to avoid issues.

Page 48: Adventures Using Triggers, Stored Procedures, and User ...

48

48

Acknowledgements and Further Reading• IBM Redbook; Data Integrity with DB2 for z/OS• Special thanks to Terry Purcell and Suresh Sane

for SQL to invoke an user defined function.• V8 Trigger Tales – Hari Shanmugadhasan• DB2 War Stories and Scary Tales – Robert

Goodman• Did we just create 1,024 triggers? – Mark Harris,

Steve Heichelheim• REXX and DB2 – The Stuff NOT in the Manuals –

Rich Fazio

V8 Trigger Tales was presented at Central Canada DB2 Users Group Fall 2007.DB2 War Stories and Scary Tales was presented at IDUG NA 2007.Did we just create 1,024 triggers? was presented at IDUG NA 2004.REXX and DB2 – The Stuff NOT in the Manuals was presented at IDUG NA 2003.

Page 49: Adventures Using Triggers, Stored Procedures, and User ...

49

49

David ChurnDST Systems, Inc.

[email protected]

Session F04

Adventures Using Triggers, Stored Procedures and User Defined Functions

REXX code to parse triggers is available upon request from the email address above.