Programming Tips Db2 V7 PDF
Programming Tips Db2 V7 PDF
Bart Steegmans
Rafael Garcia
Luis Garmendia
Anne Lesell
ibm.com/redbooks
International Technical Support Organization
October 2001
SG24-6300-00
Take Note! Before using this information and the product it supports, be sure to read the general
information in “Special notices” on page 257.
This edition applies to Version 7 of IBM DATABASE 2 Universal Database Server for z/OS and OS/390 (DB2
for z/OS and OS/390 Version 7), Program Number 5675-DB2.
When you send information to IBM, you grant IBM a non-exclusive right to use or distribute the information in
any way it believes appropriate without incurring any obligation to you.
Contents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii
Figures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix
Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii
The team that wrote this redbook. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii
Special notice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xviii
IBM trademarks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix
Comments welcome. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix
Chapter 1. Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Chapter 2. Schemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.1 What is a schema? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2 Schema characteristics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.1 Authorizations on schemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.2 Schema path and special register. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2.3 How is a schema name determined? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3 The schema processor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Chapter 3. Triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.1 Trigger definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.2 Why use a trigger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.3 Trigger characteristics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.3.1 Trigger activation time. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.3.2 How many times is a trigger activated? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.3.3 Trigger action condition. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.3.4 Trigger action . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.3.5 Transition variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.3.6 Transition tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.4 Allowable combinations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.5 Valid triggered SQL statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
3.6 Invoking stored procedures and UDFs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
3.7 Setting error conditions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.8 Error handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.9 Trigger cascading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.10 Global trigger ordering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.11 When external actions are backed out . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.12 Passing transition tables to SPs and UDFs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.13 Trigger package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.14 Rebinding a trigger package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.15 Trigger package dependencies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.16 DROP, GRANT, and COMMENT ON statements. . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.17 Catalog changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Chapter 8. Savepoints. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
8.1 What is a savepoint? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
8.2 Why to use savepoints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
8.3 Savepoint characteristics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
8.4 Remote connections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
8.5 Savepoint restrictions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Contents v
11.2 Why union everywhere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
11.3 Unions in nested table expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
11.4 Unions in subqueries. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
11.4.1 Unions in basic predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
11.4.2 Unions in quantified predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
11.4.3 Unions in EXISTS predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
11.4.4 Unions in IN predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
11.4.5 Unions in selects of INSERT statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
11.4.6 Unions in UPDATE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
11.5 Unions in views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
11.6 Explain and unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
11.7 Technical design and new frontiers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
Contents vii
Locating the Web material . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
Using the Web material . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
How to use the Web material . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
Examples xiii
5-9 CHARNSI source code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
7-1 Created temporary table DDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
7-2 Created temporary table in SYSIBM.SYSTABLES . . . . . . . . . . . . . . . . . . . . . . . . . . 82
7-3 Use of LIKE clause with created temporary tables . . . . . . . . . . . . . . . . . . . . . . . . . . 83
7-4 View on a created temporary table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
7-5 Dropping a created temporary table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
7-6 Using a created temporary table in a program. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
7-7 Sample DDL for a declared temporary table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
7-8 Create a database and table spaces for declared temporary tables . . . . . . . . . . . . . 89
7-9 Explicitly specify columns of declared temporary table . . . . . . . . . . . . . . . . . . . . . . . 91
7-10 Implicit define declared temporary table and identity column . . . . . . . . . . . . . . . . . . 91
7-11 Define declared temporary table from a view . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
7-12 Dropping a declared temporary table. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
7-13 Declared temporary tables in a program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
7-14 Three-part name of declared temporary table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
8-1 Setting up a savepoint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
9-1 IDENTITY column for member number . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
9-2 Copying identity column attributes with the LIKE clause . . . . . . . . . . . . . . . . . . . . . 106
9-3 Insert with select from another table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
9-4 Retrieving an identity column value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
9-5 ROWID column . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
9-6 SELECTing based on ROWIDs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
9-7 Copying data to a table with GENERATED ALWAYS ROWID via subselect . . . . . 115
9-8 Copying data to a table with GENERATED BY DEFAULT ROWID via subselect. . 116
9-9 DCLGEN output for a ROWID column. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
9-10 Coding a ROWID host variable in Cobol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
9-11 Why not to use a ROWID column as a partitioning key . . . . . . . . . . . . . . . . . . . . . . 118
9-12 ROWID direct row access . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
9-13 Inappropriate coding for direct row access. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
10-1 SELECT with CASE expression and simple WHEN clause. . . . . . . . . . . . . . . . . . . 126
10-2 Update with CASE expression and searched WHEN clause. . . . . . . . . . . . . . . . . . 126
10-3 Three updates vs. one update with a CASE expression . . . . . . . . . . . . . . . . . . . . . 127
10-4 One update with the CASE expression and only one pass of the data . . . . . . . . . . 128
10-5 Three updates vs. one update with a CASE expression . . . . . . . . . . . . . . . . . . . . . 128
10-6 Same update implemented with CASE expression and only one pass of the data . 128
10-7 Same update with simplified logic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
10-8 Avoiding division by zero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
10-9 Avoid division by zero, second example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
10-10 Replacing several UNION ALL clauses with one CASE expression . . . . . . . . . . . . 130
10-11 Raise an error in CASE statement. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
10-12 Pivoting tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
10-13 Use CASE expression for grouping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
11-1 Unions in nested table expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
11-2 Using UNION in basic predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
11-3 Using UNION with quantified predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
11-4 Using UNION in the EXISTS predicate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
11-5 Using UNION in an IN predicate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
11-6 Using UNION in an INSERT statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
11-7 Using UNION in an UPDATE statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
11-8 Create view with UNION ALL. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
11-9 Use view containing UNION ALL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
11-10 PLAN_TABLE output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
11-11 DDL to create split tables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
11-12 DDL to create UNION in view . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
11-13 Sample SELECT from view to mask the underlying tables . . . . . . . . . . . . . . . . . . . 147
Examples xv
A-5 DDL for triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
A-6 Populated tables used in the examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
A-7 DDL to clean up the examples environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
B-1 Returning SQLSTATE to a trigger from a SP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
B-2 Passing a transition table from a trigger to a SP . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
This IBM Redbook describes the major enhancements that affect application programming
when accessing DB2 data on a S/390 or z/Series platform, including the object-oriented
extensions such as triggers, user-defined functions and user-defined distinct types, the usage
of temporary tables, savepoints and the numerous extensions to the SQL language to help
you build powerful, reliable and scalable applications, whether it be in a traditional
environment or on an e-business platform.
IBM DATABASE 2 Universal Database Server for z/OS and OS/390 Version 7 (or just DB2 V7
throughout this book) is currently at its eleventh release. Over the last couple of versions a
large number of enhancements were added to the product. Many of these enhancements
affect application programming and the way you access your DB2 data.
This book will help you to understand how these programming enhancements work and
provide examples of how to use them. It provides considerations and recommendations for
implementing these enhancements and for evaluating their applicability in your DB2
environments.
Bart Steegmans is a DB2 Product Support Specialist in IBM Belgium who has recently
joined the ITSO. He has over 12 years of experience in DB2. Before joining IBM in 1997, Bart
worked as a DB2 system administrator at a banking and insurance group. His areas of
expertise include DB2 performance, database administration, and backup and recovery.
Rafael Garcia has been in the IT business for 19 years and has held various positions. He
was a COBOL and CICS developer, an application development manager and a DB2
applications DBA for one of the top 10 banks in the US. For the last five years he has been a
Field DB2 Technical Specialist working for the IBM Silicon Valley Laboratory supporting DB2
for OS/390 customers across various industries, including migrations to data sharing. He has
an associate’s degree in Arts and an associate’s degree in Science in Business Data
Processing from Miami-Dade Community College.
Luis Garmendia is a DB2 Instructor for IBM Learning Services in Spain and has more than
three years of experience teaching DB2 application and system performance, database and
system administration, recovery, and data sharing. He has a doctorate in Mathematics (fuzzy
logic and inference) and he also teaches Computer Sciences in the Technical University of
Madrid.
Anne Lesell is a DB2 and Database Design Instructor and Curriculum Manager for IBM in
Finland. Anne has been working with DB2 since 1987. Before joining IBM in 1998, she
worked as a Database Administrator in a large Finnish bank where she specialized in
database design and DB2 application tuning.
Emma Jacobs
Yvonne Lyon
Gabrielle Velez
International Technical Support Organization, San Jose Center
Sherry Guo
William Kyu
Roger Miller
San Phoenix
Kalpana Shyam
Yunfei Xie
Koko Yamaguchi
IBM Silicon Valley Laboratory
Robert Begg
IBM Toronto, Canada
Michael Parbs
Paul Tainsh
IBM Australia
Rich Conway
IBM International Technical Support Organization, Poughkeepsie Center
Peter Backlund
Martin Hubel
Gabrielle Wiorkowski
IBM Database Gold Consultants
Special notice
This publication is intended to help managers and professionals understand and evaluate
some of the application related enhancements that were introduced over the last couple of
years into the IBM DATABASE 2 Universal Database Server for z/OS and OS/390 products.
The information in this publication is not intended as the specification of any programming
interfaces that are provided by the IBM DATABASE 2 Universal Database Server for z/OS and
OS/390. See the PUBLICATIONS section of the IBM Programming Announcement for the
IBM DATABASE 2 Universal Database Server for z/OS and OS/390 for more information
about what publications are considered to be product documentation.
Comments welcome
Your comments are important to us!
We want our IBM Redbooks to be as helpful as possible. Send us your comments about this
or other Redbooks in one of the following ways:
Use the online Contact us review redbook form found at:
ibm.com/redbooks
Send your comments in an Internet note to:
redbook@us.ibm.com
Mail your comments to the address on page ii.
Preface xix
xx DB2 for z/OS Application Programming Topics
1
Chapter 1. Introduction
New DB2 versions and releases have always been a balanced mixture between system
related enhancements and improvements that benefit database administrators and the
application programming community.
When we first started to lay out the content of this book we wanted to include all the
application programming related enhancements since DB2 Version 4, where a lot of major
programming related enhancements like OUTER JOIN and nested table expressions to name
only a few, became available. Even though these are very important enhancements and very
relevant to application programming and design, we soon had to give up the idea and decided
to concentrate mainly on the enhancements since DB2 Version 6, and even that turned out to
be over ambitious. Therefore, if a certain topic is not included in this redbook, it is most likely
because it has already been covered in some of the other available redbooks. Good
references are:
DB2 UDB Server for OS/390 and z/OS Version 7 Presentation Guide, SG24-6121
DB2 UDB Server for OS/390 Version 6 Technical Update, SG24-6108
DB2 Server for OS/390 Version 5 Recent Enhancements - Reference Guide, SG24-5421
There is also another category of enhancements, that, although application related, did not
make it into this redbook. Stored procedures and Java support are good examples. They have
a big impact on the way you develop applications and are, or therefore will be, treated in
separate redbooks like:
DB2 for OS/390 and z/OS Powering the World’s e-business Solutions, SG24-6257
DB2 Java Stored Procedures Learning by Example, SG24-5945
Cross-Platform DB2 Stored Procedures: Building and Debugging, SG24-5485
For a complete list of recent DB2 for OS/390 related redbooks, see “Related publications” on
page 253, or visit the Redbooks Web site at: ibm.com/redbooks.
This redbook is based on DB2 for z/OS Version 7 (PUT0106)1 and all the examples in this
book are written using a Version 7 system. However, since a large customer basis is still
running DB2 V6, we mention when a feature was introduced in V7. If the version is not
specifically mentioned, it is part of Version 6. (Some features that were introduced in Version
5 are also included in the book.)
1
PUT0106 indicates the maintenance level.
Triggers provide automatic execution of a set of SQL statements (verifying input, executing
additional SQL statements, invoking external programs written in any popular language)
whenever a specified event occurs. This opens the door for new possibilities in application
and database design.
User-defined distinct types can help you enforce strong typing through the database
management system. The distinct type reflects the use of the data that is required and/or
allowed. Strong typing is especially valuable for ad-hoc access to data where users don’t
always understand the full semantics of the data.
The number of built-in functions increased considerably in last versions of DB2. There are
now over 90 different functions that perform a wide range of string, date, time, and timestamp
manipulations, data type conversions, and arithmetic calculations.
In some cases even this large number of built-in functions does not fit all needs. Therefore,
DB2 allows you to write your own user-defined functions that can call an external program.
This extends the functionality of SQL to whatever you can code in an application program;
essentially, there are no limits. User-defined functions also act as the methods for
user-defined data types by providing consistent behavior and encapsulating the types.
Another set of enhancements is more geared toward giving you more flexibility when
designing databases and applications.
When you need a table only for the life of an application process, you don’t have to create a
permanent table to store this information but you can use a temporary table instead. There
are two kinds of temporary tables: created temporary tables, also know as global
temporary tables and declared temporary tables. Their implementation is different from
normal tables. They have reduced or no logging and also virtually no locking. The latter is not
required since the data is not shared between applications. The data that is stored in the
temporary table is only visible to the application process that created it.
Another major enhancement that can make you change the way you have been designing
applications in the past is the introduction of savepoints. Savepoints enable you to code
contingency or what-if logic and can be useful for programs with sophisticated error recovery,
or to undo updates made by a stored procedure or subroutine when an error is detected, and
to ensure that only the work done in a stored procedure or subroutine is rolled back.
Another area which has always caused a lot of debate is how and when to assign a unique
identification to a relational object. Often a natural key is available and should be used to
identify the relation. However, this is not always the case and this is where a lot of discussions
start. Should we use an ever-ascending or descending key? Should it be random instead?
Should we put that ever-increasing number in a table and if so, how do we access it and when
do we update the number? These are all questions that keep DBA’s from loosing their jobs.
Recently DB2 has introduced two new concepts that can guarantee unique column values
without having to create an index. In addition, you can eliminate the application coding that
was implemented to assign unique column values for those columns.
The first concept is the introduction of identity columns. Identity columns offer us a new
possibility to guarantee uniqueness of a column and enables us to automatically generate the
value inside the database management system.
Another major enhancement is scrollable cursors. The ability to be able to scroll backwards
as well as forwards has been a requirement of many screen-based applications. DB2 V7
introduces facilities not only to allow scrolling forwards and backwards, but also the ability to
jump around and directly retrieve a row that is located at any position within the cursor result
table. DB2 also can, if desired, maintain the relationship between the rows in the result set
and the data in the base table. That is, the scrollable cursor function allows the changes
made outside the opened cursor, to be reflected in the result set returned by the cursor.
DB2 utilities are normally not directly the terrain of the application programmer. However, new
DB2 utilities have been introduced and existing utilities have been enhanced in such a way
that they can take over some of the work that was traditionally done in application
programs. Some ideas where these enhancements can be used and how they compare to
implementing the same processes using application code will be provided, such as using
online LOAD RESUME versus coding your own program to add rows to a table, using
REORG DISCARD versus deleting rows via a program followed by a REORG to reclaim
unused space and restore the cluster ratio, and comparing REORG UNLOAD EXTERNAL,
the new UNLOAD utility to a home grown program or the DSNTIAUL sample program.
And last but not least, we will discuss and provide examples for a large list of enhancements
to the SQL language that will boost programmer productivity, such as CASE expressions,
that allow you to code IF-THEN logic inside an SQL statement, UNION everywhere, that
enables you to code a full select, wherever you were allowed to code a subselect before. This
also included the long-awaited union-in-view feature and will finally allow you to code a
UNION clause inside a CREATE VIEW statement. With this enhancement, DB2 is delivering
one of the oldest outstanding requirements which should make a lot of people want to migrate
to Version 7 sooner rather than later.
With such a vast range of programming capabilities, that not only provide rich functionality but
also good performance and great scalability, DB2 for z/OS Version 7 is certainly capable of
competing with any other DBMS in the marketplace.
We hope you enjoy reading this Redbook as much as we did writing it.
Chapter 1. Introduction 3
4 DB2 for z/OS Application Programming Topics
Part 1
Part 1 Object-oriented
enhancements
In this part we describe and discuss various object-oriented enhancements that can
transform DB2 from a passive database manager into an active one by allowing you to move
application logic into the database. These enhancements allow you to move application logic
that may reside in various places, platforms, and environments into one place, the database
itself.
Chapter 2. Schemas
The concept of schemas has been around for quite a some time.
In this section, we discuss the schema concept, since it is now starting to be widely used in a
DB2 for z/OS environment and is required for some features implemented in DB2 V6, like
triggers, user-defined functions, user-defined distinct types and stored procedures.
In Version 6, to be consistent with the ANSI/ISO SQL92 standard, the concept of qualified
names is extended to refer to the qualifier as a schema name. The qualifier of the new object
types introduced in Version 6 (user-defined distinct types, user-defined functions and triggers)
as well as stored procedures, is a schema name.
A schema name has a maximum length of eight bytes. All objects qualified by the same
schema name can be thought of as a group of related objects. The schema name SYSIBM is
used for built-in data types and functions, the schema name SYSPROC is used for stored
procedures migrated from Version 5 and can be used for procedures created in V6. SYSFUN
is the schema name used for additional functions shipped with other members of the DB2
family. Although DB2 for z/OS does not use the SYSFUN schema, it can be useful to have
SYSFUN in the CURRENT PATH special register when doing distributed processing and
another DB2 family member is the server.
Restriction: CREATE statements cannot specify a schema name that begins with 'SYS'
(unless it is 'SYSADM' or 'SYSPROC' for stored procedures).
The specified schemas do not need to exist when the grant is executed.
The default for the PATH bind option is “SYSIBM”, “SYSFUN”, “SYSPROC”, QUALIFIER
(plan or package). If the PATH option is not specified on a REBIND, the previous value is
retained.
The new SET CURRENT PATH statement is used to change the list of schemas held in the
CURRENT PATH special register. The CURRENT PATH special register is used to resolve
unqualified references to user-defined distinct types and user-defined functions in dynamic
SQL statements. It is also used to resolve unqualified stored procedure names when the
SQL CALL statement specifies a host variable for the procedure name.
The schemas SYSIBM, SYSFUN and SYSPROC do not need to be included in the PATH
bind option or the CURRENT PATH special register. If one is not included in the path, it is
implicitly assumed as the first schema. If more than one is not included in the path, SYSIBM
is put first in the path followed by SYSFUN and then SYSPROC. In Example 2-1, we see what
happens when the CURRENT PATH special register is set to some values.
Example 2-1 Overriding the implicit search path
Chapter 2. Schemas 9
2.2.3 How is a schema name determined?
The schema name can be specified explicitly when the object is referenced in a CREATE,
ALTER, DROP or COMMENT ON statement.
If the object is unqualified and the statement is embedded in a program, the schema name of
the object is the authorization ID in the QUALIFIER bind option when the plan or package was
created or last rebound. If QUALIFIER was not used, the schema name of the object is the
owner of the plan or package. If the object is unqualified and the statement is dynamically
prepared, the SQL authorization ID contained in the CURRENT SQLID special register is the
schema name of the object.
Another way to create objects is by means of a schema processor (program DSNHSP) and
the use of the CREATE SCHEMA statement. This statement cannot be used by ‘normal’ SQL
processor programs like SPUFI, but is only understood by the schema processor. The
schema processor allows you to create a set of related objects that belong to a single schema
(corresponding with an authorization id) in one CREATE SCHEMA operation.
Using a schema processor has some advantages over using other means of running DDL
statements. Outside of the schema processor, the order of statements is important. They
must be arranged so that all referenced objects have been previously created. For example,
in order to create an index on a table, the table must first be created. This restriction is
relaxed when the statements are processed by the schema processor, as long as the object
table is created within the same CREATE SCHEMA. The requirement that all referenced
objects have been previously created is not checked by the schema processor until all of the
statements have been processed. So, with the schema processor, it is sufficient that the
create table statement for the table is present anywhere in the input file.
Tip: The order in which statements appear is not important when using the schema
processor.
The schema processor sets the current SQLID to the value of the schema authorization ID
(which is also the schema name) before executing any of the statements in the schema. In
Example 2-2 we create a schema “SC246300”, the schema authorization is the same as the
schema name. All the objects created within this schema will have a schema name and
owner of “SC246300”. As you can see the statements after the CREATE SCHEMA can be in
any order.
Example 2-2 Schema authorization
Important: Databases and table spaces can and should be defined through the schema
processor, however they are not part of a schema (not schema objects) since database
names must be unique within the DB2 system and table space names must be unique
within a database.
All statements passed to the schema processor are considered one unit of work. If one or
more statements fail with a negative SQLCODE, all other statements continue to be
processed. However, when the end of the input is reached, all work is rolled back. You can
only process one schema per job step execution.
An example of a schema processor job can be found in DDL of the DB2 objects used in the
examples, Example A-1 on page 224.
Chapter 2. Schemas 11
12 DB2 for z/OS Application Programming Topics
3
Chapter 3. Triggers
Triggers provide automatic execution of a set of SQL statements whenever a specified event
occurs. This opens the door for new possibilities in application design. In this section we
discuss triggers, and how and when to use them.
These SQL statements can validate and edit database changes, read and modify the
database, and invoke functions that perform operations both inside and outside the database.
In Example 3-1, we show you a simple trigger to update two summary tables when a new
order comes in. The number of orders is increased by one for each region and state when a
new order is processed.
Example 3-1 Trigger to maintain summary data
Enforcement of transitional business rules: Triggers can enforce data integrity rules with
far more capability than is possible with declarative (static) constraints. Triggers are most
useful for defining and enforcing transitional business rules. These are rules that involve
different states of the data. In Example 3-11 on page 25, we show a trigger constraint that
prevents a salary column from being increased by more than twenty percent. To enforce this
rule, the value of the salary before and after the increase must be compared. This is
something that cannot be done with a check constraint.
Generation and editing of column values: Triggers can automatically generate values for
newly inserted rows, that is, you can implement user-defined default values, possibly based
on other values in the row or values in other tables. Similarly, column values provided in an
insert or update operation can be modified/corrected before the operation occurs.
An example of this can be found in Example 4-13 on page 53. This trigger generates a value
for the EUROFEE column based on a conversion formula that calculates an amount in euro
based on an amount in pesetas (PESETAFEE).
Cross-reference other tables: You can enhance the existing referential integrity rules that
are supported by DB2. For example, RI allows a foreign key to reference a primary key in
another table. With the new union everywhere feature you can change your design to split a
logical table into multiple physical tables (as an alternative to partitioning). Now it has become
impossible to use DB2 RI to implement a foreign key relationship, because you cannot refer
multiple tables to check whether it has an existing primary key value. Here triggers can come
to the rescue. You can implement a before trigger to check the different tables to make sure
the value you are inserting has a parent row.
Note: Make sure you have the PTF for APAR PQ53030 (still open at the time of writing)
applied on your system when trying this.
You can also implement so called ‘negative RI’ this way. Instead of checking whether a row
exists in a parent table (normal RI), you can use a trigger to make sure a row or column value
does not exist in another table. For example, when you want to add a new customer to the
customer table (TBCUSTOMER), you might want to check (using a trigger) whether that
customer does not already exists in the customer archive database (TBCUSTOMER_ARCH,
that contains customers that have not placed any orders the past year). If it does, you restrict
the insert of the new customer and make the application copy the existing customer
information from the customer archive database.
Chapter 3. Triggers 15
Maintenance of summary data: Triggers can automatically update summary data in one or
more tables when a change occurs to another table. For example, triggers can ensure that
every time a new order is added, updates are made to rows in the TBREGION and TBSTATE
table to reflect the change in the number of orders per region and state. Example 3-2 shows
part of the solution to implement this. You also need to define an UPDATE and DELETE
trigger to cover all cases if you want the number of outstanding orders to be correctly
reflected at the region and state level.
Example 3-2 Trigger to maintain summary data
Initiate external actions: In Example 3-3, we demonstrate how a user-defined function can
be used within a trigger to initiate an external function. Since a user-defined function can be
written in any of the popular programming languages, using a user-defined function in a
trigger gives access to a vast number of possible actions you can code. A common uses may
be to communicate with an e-mail package and send out an e-mail to the employee to let him
know that a change was made to his payroll data.
Example 3-3 Trigger to initiate an external action
Chapter 3. Triggers 17
SIGNAL SQLSTATE 'ERR10' ('NOT A VALID CITY') #
DSNT408I SQLCODE = -438, ERROR: APPLICATION RAISED ERROR WITH DIAGNOSTIC TEXT:
NOT A VALID CITY
DSNT418I SQLSTATE = ERR10 SQLSTATE RETURN CODE
AFTER
– The trigger is activated after the triggering operation is performed.
– The trigger can be viewed as a segment of application logic to:
• Perform follow-on update operations
• Perform external actions
– This type of trigger is often referred to as an after trigger.
The trigger action is activated after the triggering operation has been processed and after
all table check constraints and referential constraints that the triggering operation may be
subject to have been processed.
After triggers can be viewed as segments of application logic that run every time a specific
event occurs. After triggers see the database in the state that an application would see it
following the execution of the change operation. This means they can be used to perform
actions that an application might otherwise have performed such as maintaining summary
data or an audit log. Example 3-6 on page 20 shows an after trigger.
In a row trigger, the condition is evaluated for each row in the set of affected rows. In a
statement trigger, the condition is evaluated only once as the trigger is activated only once.
The significance of the required ATOMIC keyword (see Example 3-5), is that the set of SQL
statements is treated as an atomic unit. That is, either all of the statements are executed or
none. If, for example, the second UPDATE statement shown in Example 3-5 fails, all changes
made to the database as part of the triggered operation and the triggering operation are
backed out. For more details see 3.8, “Error handling” on page 26.
Example 3-5 Multiple trigger actions
Chapter 3. Triggers 19
Tip: SQL processor programs, such as SPUFI and DSNTEP2, might not correctly parse
SQL statements in the trigger action that are ended with semicolons. These processor
programs accept multiple SQL statements, each separated with a terminator character, as
input. Processor programs that use a semicolon as the SQL statement terminator can
truncate a CREATE TRIGGER statement with embedded semicolons and pass only a
portion of it to DB2. Therefore, you might need to change the SQL terminator character for
these processor programs. For information on changing the terminator character for
SPUFI and DSNTEP2, see DB2 UDB for OS/390 and z/OS Version 7 Application
Programming and SQL Guide, SC26-9933.
The trigger activation time determines which statements are allowed in the trigger body. See
Figure 3-1 on page 22 for a list of the allowable combinations.
When a row trigger is activated it is likely for the trigger to refer to the column values of the
row for which it was activated. The REFERENCING clause of the CREATE TRIGGER
statement enables a row trigger to refer to the column values for a row, as they were before,
and as they are after, the triggering operation has been performed. The REFERENCING
clause is specified as:
REFERENCING OLD AS correlation-name Specifies a correlation name that can be used
to reference the column values in the original state of the row, that is, before the triggering
operation is executed.
REFERENCING NEW AS correlation-name Specifies a correlation name that can be used to
reference the column values that were used to update the row, after the triggering
operation is executed.
Example 3-6 Transition variables
The above example shows how transition variables can be used in a row trigger to maintain
summary data in another table. Assume that the department table has a column that records
the budget for each department. Updates to the SALARY of any employee (for example, from
$50,000 to $60,000) in the TBEMPLOYEE table are automatically reflected in the budget of
the updated employee's department. In this case, NEW_EMP.SALARY has a value of 60000
and OLD_EMP.SALARY a value of 50000, and BUDGET is increased by 10000.
In both row and statement triggers it might be necessary to refer to the whole set of affected
rows. For example, triggered SQL statements might need to apply aggregations over the set
of affected rows (MAX, MIN, or AVG of some column values). A trigger can refer to the set of
affected rows by using transition tables. Transition tables are specified in the REFERENCES
clause of the CREATE TRIGGER statement. The columns in transition tables are referred to
using the column names of the triggering table. Like transition variables, there are two kinds
of transition tables which are specified as:
REFERENCING OLD_TABLE AS identifier: Specifies a table identifier that captures the original
state of the set of affected rows, that is, their state before the triggering operation is
performed.
REFERENCING NEW_TABLE AS identifier: Specifies a table identifier that captures the after
state of the set of affected rows, that is, their state after the triggering operation has been
performed.
In Example 3-7, we show the use of a transition table in a statement trigger. The UDF named
LARGE_ORDER_ALERT is invoked for each row in the new transition table that corresponds
to an order worth more than $10,000.
Example 3-7 Transition table
Important: Transition tables are populated by DB2 before any after-row or after-statement
trigger is activated. Transition tables are read-only.
In Example 3-8, a trigger is used to maintain the supply of parts in the PARTS table. The
trigger action condition specifies that the set of triggered SQL statements should only be
executed for rows in which the value of the ON_HAND column is less than ten per cent of the
value of the MAX_STOCKED column. When this condition is true, the trigger action is to
reorder (MAX-STOCKED - ON_HAND) items of the affected part using a UDF called
ISSUE_SHIP_REQUEST.
Example 3-8 Single trigger action
Chapter 3. Triggers 21
3.4 Allowable combinations
Some combinations of trigger granularity, trigger activation time, transition variables, and
transition tables do not make sense. For example, after-statement triggers cannot reference
transition variables because transition variables refer to column values on a per row basis
and after-statement triggers are executed once per execution of the triggering operation. (An
after-statement trigger can access individual rows in a transition table). Also, it does not make
sense to be able to define a before-statement trigger. See Figure 3-1 for a diagram of
allowable syntax.
The triggering operation also affects which transition variables and transition tables can be
referenced:
INSERT An insert trigger can only refer to new transition variables or a new transition
table. Before the execution of an insert operation the affected rows do not
exist in the database. That is, there is no original state of the rows that would
define old values before the insert operation is applied to the database.
UPDATE An update trigger can refer to both old and new transition variables and
tables.
DELETE A delete trigger can only refer to old transition variables or an old transition
table. Because the rows will be deleted, there are no new values to
reference.
S earch ed U P D A T E
(no t a curso r U P D A T E )
S earch ed D E LE T E
(no t a curso r D E LE T E )
F u ll se le ct and th e V A L U E S s tatem e nt
(ca n be us ed to inv ok e U se r D e fine d F u nctions )
C A LL p ro ced ure-nam e
S IG N A L S Q LS T A T E
The VALUES statement can be used in BEFORE and AFTER triggers to invoke UDFs:
VALUES (expression, expression...)
An example of the usage of the VALUES statement can be found in Example 3-8 on page 21.
Note: SET transition-variable and VALUES are only allowed in a trigger body
Only SQL operations are allowed in a trigger body. However, the ability to invoke an external
UDF or stored procedure from within a trigger greatly expands the types of trigger actions
possible.
If a before trigger invokes an external UDF or stored procedure, that function or procedure
cannot execute an INSERT, UPDATE, or DELETE statement because before triggers cannot
update the database. Attempts to do so results in an SQLCODE -817.
Chapter 3. Triggers 23
Attention: If the SQLCODE is ignored by the stored procedure or the UDF and returns to
the invoking trigger, the triggering action is NOT undone. More information on how to deal
with error situations in triggers can be found in Section 3.8, “Error handling” on page 26.
UDFs cannot be invoked in a stand-alone manner, that is, they must appear in an SQL
statement. A convenient method for invoking a UDF is to use a VALUES statement or a full
select as show in Example 3-9.
Example 3-9 Invoking a UDF within a trigger
SIGNAL SQLSTATE - a new SQL statement that halts processing and returns the requested
SQLSTATE and message to an application
Only valid as a triggered SQL statement
Can be controlled with a WHEN clause
In Example 3-10, we show how a trigger can be used to enforce constraints on inserts to the
TBEMPLOYEE table. Please note that at least one clause of the CASE expression must
result in a non-RAISE_ERROR condition. The constraints are:
HIREDATE must be date of insert or a future date
HIREDATE cannot be more than 1 year from date of insert
Example 3-10 Raising error conditions
The SIGNAL SQLSTATE statement is only valid in a trigger body. However, the
RAISE_ERROR built-in function can also be used in SQL statements other than a trigger.
When SIGNAL SQLSTATE and RAISE_ERROR are issued, the execution of the trigger is
terminated and all database changes performed as part of the triggering operation and all
trigger actions are backed out. The application receives SQLCODE -438 along with the
SQLSTATE (in SQLCA field SQLSTATE) and text (in SQLCA field SQLERRMC) that have
been set by the trigger. For instance, if you perform an illegal update against the CHK_SAL
trigger of Example 3-11, you will receive the following information in the SQLCA as shown in
Example 3-12.
Example 3-12 Information returned after a SIGNAL SQLSTATE
DSNT408I SQLCODE = -438, ERROR: APPLICATION RAISED ERROR WITH DIAGNOSTIC TEXT:
INVALID SALARY INCREASE - EXCEEDS 20%
DSNT418I SQLSTATE = 75001 SQLSTATE RETURN CODE
DSNT415I SQLERRP = DSNXRTYP SQL PROCEDURE DETECTING ERROR
DSNT416I SQLERRD = 1 0 0 -1 0 0 SQL DIAGNOSTIC INFORMATION
DSNT416I SQLERRD = X'00000001' X'00000000' X'00000000'
X'FFFFFFFF' X'00000000' X'00000000' SQL DIAGNOSTIC INFORMATION
Chapter 3. Triggers 25
SQLWARN6 X(1)
SQLWARN7 X(1)
SQLWARN8 X(1)
SQLWARN9 X(1)
SQLWARNA X(1)
SQLSTATE X(5) 75001
*** END OF UNFORMATTED SQLCA ***
Example 3-13 shows you what is returned in case the trigger somehow fails to execute. In this
case the UPDATE statement (not shown) fails with an SQLCODE -723. The trigger
DB28710.TR5EMP that is fired by the UPDATE statement failed because the table space
DSNDB04.LOGGING, that is part of the trigger action, is stopped for all access (x’00C90081’)
and therefore the trigger gets an SQLCODE -904. As you can see, the original error (-904) is
nicely imbedded in the SQLCODE -723 that is returned to the program.
The easiest way to format the information returned in the SQLCA is probably by calling
DSNTIAR, but you can also handle the information yourself, as shown in the bottom part of
the example.
Example 3-13 Sample information returned when trigger receives an SQLCODE
If a stored procedure (SP) or UDF is invoked by a trigger, and the SP/UDF encounters an
error, the SP/UDF can choose to ignore the error and continue or it can return an error to the
trigger.
Attention: If the SQLCODE is ignored by the SP or the UDF and returns to the invoking
trigger, the triggering action is NOT undone. To avoid data inconsistencies it is best
(easiest) for the SP/UDF to issue a ROLLBACK. This will place the SP/UDF in a
MUST_ROLLBACK state and will cause the triggering action to be rolled back also.
Another way is to return an SQLSTATE to the trigger that will translate into an
SQLCODE -723.
External UDFs or stored procedures can be written to perform exception checking and to
return an error if an exception is detected. When a SP/UDF is invoked from a trigger, and that
SP/UDF returns an SQLSTATE, this SQLSTATE is translated into a negative SQLCODE by
DB2. The trigger execution terminates and all database changes performed as part of the
triggering operation are backed out.
Remember that a stored procedure cannot return output parameters (containing for instance
the SQLCODE) when invoked from a trigger. To pass back an SQLSTATE to the invoking
trigger, the PARAMETER STYLE DB2SQL option has to be used on the CREATE
PROCEDURE or CREATE FUNCTION statement. This indicates to DB2 that additional
information can be passed back and forth between the caller and the procedure or function.
This information includes the SQLSTATE and a diagnostic string. For more details on how to
use PARAMETER STYLE DB2SQL, please refer to the DB2 UDB for OS/390 and z/OS
Version 7 Application Programming and SQL Guide, SC26-9933.
Example 3-14 shows how the error is returned to the triggering statement. A full listing and
additional information is available in the additional material that can be downloaded from the
Internet (see Appendix C, “Additional material” on page 251) as well as “Returning
SQLSTATE from a stored procedure to a trigger” on page 244. Both the SQLSTATE 38601
and diagnostic string ’SP HAD SQL ERROR’ are set by the stored procedure after it detects
its initial error.
Example 3-14 Passing SQLSTATE back to a trigger
Formatted by DSNTIAR
Chapter 3. Triggers 27
INFORMATION RETURNED: SQLCODE -443, SQLSTATE 38601, AND MESSAGE TOKENS
SD0BMS3,SD0BMS3,SP HAD SQL ERROR,
DSNT418I SQLSTATE = 09000 SQLSTATE RETURN CODE
DSNT415I SQLERRP = DSNXRRTN SQL PROCEDURE DETECTING ERROR
DSNT416I SQLERRD = -891 0 0 -1 0 0 SQL DIAGNOSTIC INFORMATION
DSNT416I SQLERRD = X'FFFFFC85' X'00000000' X'00000000'
X'FFFFFFFF' X'00000000' X'00000000' SQL DIAGNOSTIC INFORMATION
If a trigger invokes a stored procedure or external UDF and that procedure or function does
something that puts the thread into a must-rollback state, no further SQL is allowed.
SQLCODE -751 is returned to the trigger which causes the trigger to terminate. All database
changes performed as part of the triggering operation are backed out and control is returned
to the application. All subsequent SQL statements receive an SQLCODE -919.
A pplication
T R IG G E R 1
U PD ATE TABL E1 T RIG G E R 2
INSER T IN TO
TABLE2
U PDATE TABLE3
A pplication
SP1
C ALL SP1
IN SER T INTO T R IG G E R 1
TABLE1
VAL UES (UD F1) U D F1
UP DATE TABLE2
The allowed run-time depth level of a trigger, UDF or stored procedure is 16. If a trigger, UDF
or stored procedure at nesting level 17 is activated, SQLCODE -724 is returned to the
application. None of the database changes made as part of the triggering operation are
applied to the database. This provides protection against endless loop situations that can be
created with triggers.
In Example 3-15, you can see the error message you receive in your application program if
you attempt to go beyond the 16 nesting levels permitted.
Example 3-15 Cascading error message
DSNT408I SQLCODE = -724, ERROR: THE ACTIVATION OF THE TRIGGER OBJECT objectname
WOULD EXCEED THE MAXIMUM LEVEL OF INDIRECT SQL CASCADING
Tip: Remember before triggers cannot cause trigger cascading because INSERT,
UPDATE, and DELETE statements are not allowed in a before trigger.
Chapter 3. Triggers 29
When triggers are defined using the CREATE TRIGGER statement, their creation time is
registered by a timestamp in the DB2 catalog table SYSIBM.SYSTRIGGERS. The value of
this timestamp is subsequently used to order the activation of triggers when there is more
than one trigger that should be run at the same time.
Existing triggers are activated before new triggers so that new triggers can be used as
incremental additions to the logic that affects the database. For example, if a triggered SQL
statement of trigger T1 inserts a new row into a table T, a triggered SQL statement of trigger
T2 that is run after T1 can be used to update the new row table T with specific values.
This ordering scheme means that if you drop the first trigger created and re-create it, it will
become the last trigger to be activated by DB2.
Tip: If you want a trigger to remain the first one activated, you must drop all the triggers
defined on the table and recreate them in the order you want them executed.
Table locators can be passed as arguments in VALUES and CALL statements. A table locator
cannot be used as an argument outside of a trigger action.
The trigger definition shown in Example 3-16, uses the TABLE keyword to pass new transition
table NTAB as a table locator argument to a SP called SPTRTT.
Example 3-16 Using table locators
The SP SPTRTT is defined with a single table locator argument. The keyword LIKE followed
by SC246300.TBEMPLOYEE specifies that the table represented by the table locator has the
same column names and data types as table SC246300.TBEMPLOYEE.
To access a transition table in an external UDF or stored procedure, you need to:
1. Declare a parameter to receive the table locator
2. Declare a table locator host variable
3. Assign the parameter value to the table locator host variable
4. Use the table locator host variable to reference the transition table
In Example 3-17, the SQL syntax used to declare the table locator host variable TRIG-TBL-ID
is transformed by the precompiler to a COBOL host language statement. The keyword LIKE
followed by table-name specifies that the table represented by the table locator host variable
TRIG-TBL-ID has the same column names and data types as table
SC246300.TBEMPLOYEE. A full listing is available in “Passing a transition table from a
trigger to a SP” on page 246 and in the additional material downloadable from the Internet.
See Appendix C, “Additional material” on page 251 for instructions.
Using a transition table is an interesting technique. This way you call the stored procedure
only once using a statement trigger, instead of using a row trigger that would call the stored
procedure for every row that is updated. Passing a transition table to a UDF or SP allows you
to do things that you cannot do with row triggers like calculations based on the entire set of
rows that were changed by the triggering action.
Example 3-17 Sample COBOL program using a SP and table locator
IDENTIFICATION DIVISION.
PROGRAM-ID. "SPTRTT".
DATA DIVISION.
WORKING-STORAGE SECTION.
.....
* **************************************************
* 2. DECLARE TABLE LOCATOR HOST VARIABLE TRIG-TBL-ID
* **************************************************
01 TRIG-TBL-ID SQL TYPE IS
TABLE LIKE SC246300.TBEMPLOYEE AS LOCATOR.
.....
LINKAGE SECTION.
* ****************************************
* 1. DECLARE TABLOC AS LARGE INTEGER PARM
Chapter 3. Triggers 31
* ****************************************
01 TABLOC PIC S9(9) USAGE BINARY.
01 INDTABLOC PIC S9(4) COMP.
.....
PROCEDURE DIVISION USING TABLOC , INDTABLOC, P-SQLSTATE,
P-PROC, P-SPEC, P-DIAG.
* *********************************************
* 4. DECLARE CURSOR USING THE TRANSITION TABLE
* *********************************************
EXEC SQL
DECLARE C1 CURSOR FOR
SELECT EMPNO, FIRSTNME
FROM TABLE ( :TRIG-TBL-ID LIKE SC246300.TBEMPLOYEE )
END-EXEC.
* ***************************************************************
* 3. COPY TABLE LOCATOR INPUT PARM TO THE TABLE LOCATOR HOST VAR
* ***************************************************************
MOVE TABLOC TO TRIG-TBL-ID.
.....
Start processing the transition table
The name of the created trigger package is the same as the name of the created trigger. The
collection-id of the package is the same as the schema name of the trigger. The CREATE
TRIGGER statement fails if a package already exists with the same name. The BIND
PACKAGE options are set by DB2 when the trigger is created.
A trigger package is always accessible regardless of the plan or package being executed
when a trigger is activated. There is no need to include trigger packages in the package list.
Versioning is not allowed for trigger packages and the version ID column for triggers
packages in the catalog reflects an empty string.
Tip: We recommend that you treat trigger packages in the same way as standard
packages in that you REBIND them when you REBIND other types of package, for
example, when there are significant changes to the statistics. This ensures that access
paths are based on accurate information.
When an object is dropped or a privilege is revoked, any trigger package containing a trigger
action that references that object or requires that privilege, is invalidated. Like a regular
package, an automatic rebind is performed on the invalid trigger package when the trigger is
next activated. A trigger package that has been marked invalid has the VALID column set to
'N'. If a subsequent INSERT, UPDATE, or DELETE statement activates an invalid trigger, the
SQL statement will fail with a resource unavailable condition (SQLCODE -904). To make the
SQL statement work again, you must either drop the trigger or fix the problem that invalidated
the trigger and explicitly rebind the trigger package. The information that is returned to the
application, after DSNTIAR formatting, looks like Example 3-19.
(DB28710.TR5EMP.16D1060E1 is the name of the trigger package and the start of the
consistency token, since not all fits in the allotted space in the SQLERRMC field of the
SQLCA.)
Chapter 3. Triggers 33
Example 3-19 Information retuned when trigger package is invalid
Tip: If a triggering table is dropped, its triggers are also dropped. If the table is recreated,
and you want the triggers back, then they must be recreated.
The GRANT TRIGGER ON TABLE statement grants the privilege to use the CREATE
TRIGGER statement on the specified table(s).
GRANT TRIGGER ON table-name TO ...
Some new columns have been added to existing catalog tables. The TYPE column was
added to SYSIBM.SYSPACKAGE to indicate trigger packages. Type ‘T’ is for triggers. Also,
TRIGGERAUTH was added to SYSTABAUTH to record the TRIGGER privilege.
Chapter 3. Triggers 35
A violation of any constraint or WITH CHECK OPTION results in an error and all changes
made as a result of the original S1 (so far) are rolled back.
If the SAR is empty, this step is skipped.
4. Apply the SAR to the target table
The actual DELETE, INSERT, or UPDATE is applied using the SAR to the target table.
An error may occur when applying the SAR (such as attempting to insert a row with a
duplicate key where a unique index exists) in which case all changes made as a result of
the original SQL statement S1 (so far) are rolled back.
5. Process after triggers
All after triggers activated by S1 are processed in ascending order of creation.
After-statement triggers are activated exactly once, even if the SAR is empty. After-row
triggers are activated once for each row in the SAR.
An error may occur during the processing of a trigger action in which case all changes
made as a result of the original S1 (so far) are rolled back.
The trigger action of an after trigger may include triggered SQL statements that are
DELETE, INSERT or UPDATE statements. Each such statement is considered a
cascaded SQL statement because it starts a cascaded level of trigger processing. This
can be thought of as assigning the triggered SQL statement as a new S1 and performing
all of the steps described here recursively.
Once all triggered SQL statements from all after triggers activated by each S1 have been
processed to completion, the processing of the original S1 is complete.
violation
Apply constraints ROLLBACK
(CHECK and RI)
cascaded SQL
Process all statement
AFTER triggers error ROLLBACK
Now, let us review the factors that influence the cost of triggers. Understanding these factors
help you evaluate the likely cost of triggers and estimate their cost relative to an application
implementation of the logic, or the use of check constraints, or referential integrity.
The base cost of a trigger (that is, all work except for the execution of the SQL in the
trigger body) is about equivalent to the cost of a fetch. Where a trigger is defined but not
fired, for example, if the trigger is defined as AFTER/BEFORE UPDATE OF column1 and
an UPDATE statement updates column2, the trigger is not activated, and so the overhead
is normally negligible. However, a very complicated and badly coded WHEN clause in the
trigger can still impact performance whether or not the trigger is fired.
The trigger package has to be loaded into the EDM pool. I/O has to occur if it is not
already in the EDM pool. Options to alleviate problems in this area include:
– Monitor and increase the size of the EDM pool.
– Consider REBIND of the trigger package with the RELEASE(DEALLOCATE) option. Be
aware, though, that RELEASE(DEALLOCATE) results in more resources being held — you
face the same issues as binding application packages with RELEASE(DEALLOCATE).
The object descriptor (OBD) of the trigger package is a part of the table. Therefore, be
aware of the impact to the DBD size and its potential to impact the EDM pool if you already
have large DBDs.
Transition tables reside in the workfile database (usually named DSNDB07). The cost of a
trigger that processes workfiles depends critically on the amount of workfile processing
required (including row length and whether you have OLD and NEW transition variables).
Transition tables need to be processed with a table space scan for each row trigger
because there are no indexes. Trigger performance also depends on whether there is
contention for the workfiles. You should anticipate the cost to be several times greater than
the base cost of the trigger, because significantly more work has to be carried out.
As we have indicated, the use of transition variables and transition tables in AFTER
triggers represent a significant proportion of the cost of such a trigger. Where a trigger has
to manipulate workfiles, contributions to the cost are:
– Creation, use, and deletion of workfiles
– Any I/O or GETPAGE request necessary to process the data
– Base cost of trigger processing
There is no overhead for an SQL statement that is not the triggering action. For example, if
a INSERT trigger was defined on a table, it would have no overhead on update or delete
statements.
Chapter 3. Triggers 37
Note: APAR PQ34506 provides an important performance improvement for triggers
with a WHERE clause and a subselect. A where clause in the subselect can now be
evaluated as a Stage 1 predicate.
We recommend that you prototype your physical design first if you are considering using
triggers for tables that are heavily updated and/or fire SQL statements that process significant
quantities of data. You can then evaluate their cost relative to the cost of equivalent
functionality embedded in your applications. Be cautious when using triggers to create
summary tables from tables that are heavily updated, you may end up creating a locking
bottleneck on the summary table rows.
When you begin physical design, you may find that you need several triggers defined on a
single table. To avoid the overhead of multiple triggers, you can write a stored procedure to do
all the triggered processing logic. The body of the trigger could then simply consist of a CALL
stored-procedure-name.
When porting applications from other RDBMS systems, don’t forget that there may be syntax
incompatibility with these other platforms.
Tip: Within DB2’s resource limit facility, the execution of a trigger is counted as part of the
triggering SQL statement.
Triggers can be very helpful in a number of areas, but can also make the design more
complex. You can no longer just rely on looking at the programs to find out what is happening
to the data, you also have to look inside the DBMS. Adding triggers to the data model should
be implemented with great care, especially when you get into cascading situations. People
doing program design should be aware of existing triggers. Otherwise you might end up doing
the same update twice, once in the application program and again in the trigger.
Important: You should not use triggers just for the sake of using them. You should first see
if what you are trying to implement can be done with a check constraint, if not, then try with
referential integrity, if not, then try with a trigger. Do not get “trigger happy”!!!
Constraints are enforced when they are created but the creation of a trigger does not check
existing rows and does not cause check pending to be turned on.
Triggers are not triggered by utilities (except by an online LOAD RESUME). In the following
subsection, we discuss some alternatives to triggers.
User-defined defaults
If the default value can change over time, then a trigger may be a better way to implement
it since all that is required is dropping and recreate the trigger with the new values. Making
such a change to a column defined with a default would involve the unloading, dropping,
recreating of the table with the new default value, and reloading the table (this may
change in a future version of DB2). As you can see, the trigger would be much less
disruptive but takes additional CPU resources to be performed. If the default value does
not change, then use user-defined defaults instead of triggers to achieve better
performance results.
In Example 3-21, we demonstrate equivalent constraints. One is coded as a check and the
other as a trigger. Lets assume that the values of L_ITEM_NUMBER are constantly changing.
That is, new items are often added and old items removed. In order to make these changes to
the check constraint, you would have to drop the check constraint and alter the table to
re-add it. This causes the table to be in check pending status (with CURRENT RULES =
’DB2’) until the check utility completes and the table will be unavailable to the application
causing an outage. If high online availability is important, and you can afford the extra cost of
processing a trigger, then the trigger is a better choice.
Example 3-21 Check constraint is better than a trigger
Chapter 3. Triggers 39
WHEN ( N.SEX NOT IN('M','F'))
SIGNAL SQLSTATE 'ERRSX' ('SEX MUST BE EITHER M OR F')
or
or
In Example 3-23, we show another way that the trigger in Example 3-22 can be coded. This
example is more flexible since there is no need to update the trigger with new values but
merely to insert the new values in a table called TBITEMS. This could also be accomplished
more efficiently with RI because a foreign key causes less overhead and guarantees the
consistency when using utilities (the trigger is only enforced in an online LOAD RESUME
utility). However, a foreign key must reference a unique key and a trigger does not have that
requirement. The trigger can return a customized error message.
Example 3-23 Alternative trigger
SELECT SCHEMA
,NAME
,CASE WHEN TRIGTIME = 'B' THEN 'BEFORE'
WHEN TRIGTIME = 'A' THEN 'AFTER'
ELSE ' '
END AS TIME
,CASE WHEN TRIGEVENT = 'I' THEN 'INSERT OF'
WHEN TRIGEVENT = 'U' THEN 'UPDATE OF'
WHEN TRIGEVENT = 'D' THEN 'DELETE OF'
ELSE ' '
END AS EVENT
,CASE WHEN GRANULARITY = 'R' THEN 'ROW'
WHEN GRANULARITY = 'S' THEN 'STATEMENT'
ELSE ' '
END AS GR
,REPLACE(TEXT,' ','') AS TRIGGER
--TEXT IS 3460 BYTES WIDE AND HAS A LOT OF BLANKS
FROM SYSIBM.SYSTRIGGERS
WHERE TBOWNER = 'SC246300'
AND TBNAME = 'TBORDER'
ORDER BY TRIGTIME DESC, CREATEDTS, SEQNO;
In Example 3-25, we provide a query that can be used to identify all the triggers defined for all
the tables in a particular database and the order in which the triggers are executed.
Example 3-25 Identify all triggers for a database
SELECT TBOWNER
, TBNAME
, SCHEMA
, T. NAME
, CASE WHEN TRIGTIME = 'B' THEN 'BEFORE'
WHEN TRIGTIME = 'A' THEN 'AFTER'
ELSE ' '
END AS TIME
, CASE WHEN TRIGEVENT = 'I' THEN 'INSERT OF'
WHEN TRIGEVENT = 'U' THEN 'UPDATE OF'
WHEN TRIGEVENT = 'D' THEN 'DELETE OF'
ELSE ' '
END AS EVENT
, CASE WHEN GRANULARITY = 'R' THEN 'ROW'
WHEN GRANULARITY = 'S' THEN 'STATEMENT'
ELSE ' '
END AS GR
, REPLACE(TEXT,' ','') AS TRIGGER
--TEXT IS 3460 BYTES WIDE AND HAS LOT OF BLANKS
Chapter 3. Triggers 41
FROM SYSIBM.SYSTRIGGERS T, SYSIBM.SYSDATABASE D
WHERE D.NAME = 'DB246300'
AND T.DBID = D.DBID
ORDER BY TBOWNER, TBNAME, TRIGTIME DESC, T.CREATEDTS, SEQNO;
No program code other than calls to stored procedures or user-defined functions are allowed
in the body of a trigger.
Three-part names cannot be used within the body of a trigger. However, a trigger can call a
stored procedure or UDF that can access or change data on a remote system.
The maximum number of triggers, stored procedures, and user-defined functions that an SQL
statement can implicitly or explicitly reference is 16 nesting levels. If you attempt to go beyond
the 16 nesting levels, your application program receives an SQLCODE = -724.
The LOAD Utility does not cause triggers to get activated. Online LOAD RESUME does
activate the triggers.
Triggers can be activated by UPDATE, INSERT, and DELETE operations but not by SELECT
operations.
User-defined distinct types are also used in object-oriented systems for strong typing,
because the distinct type reflects the use of the data that is required and/or allowed. Strong
typing is especially valuable for ad-hoc access to data. The user taking advantage of a query
tool might attempt to compare such things as “euros” and “pesetas” without realizing his or
her error. DB2 can now prevent such comparisons through the implementation of UDTs.
Once a distinct type is defined, column definitions can reference that type when tables are
created or altered. A UDT is a schema object. If a distinct type is referenced without a
schema name, the distinct type is resolved by searching the schemas in the CURRENT
PATH.
Each column in a table has a specific data type which determines the column's data
representation and the operations that are allowed on that column. DB2 supports a number of
built-in data types, for example, INTEGER and CHARACTER. In building a database, you
might decide to use one of the built-in data types in a specialized way; for example, you might
use the INTEGER data type to represent ages, or the DECIMAL(8,2) data type to represent
amounts of money. When you do this, you might have certain rules in mind about the kinds of
operations that make sense on your data. For example, it makes sense to add or subtract two
amounts of money, but it might not make sense to multiply two amounts of money, and it
almost certainly makes no sense to add or compare an age to an amount of money.
UDTs provide a way for you to declare such specialized usages of data types and the rules
that go with them. DB2 enforces the rules, by performing only the kinds of computations and
comparisons that you have declared to be reasonable for your data. You have to define the
operations that are allowed on the UDT. In other words, DB2 guarantees the type-safety of
your queries.
The owner of the distinct type is determined in a similar way to other DDL. The owner is given
the USAGE privilege on the distinct type, and the EXECUTE privilege on each of the
generated CAST functions, with the GRANT option. (CAST functions are discussed in more
detail in 4.3, “CAST functions” on page 45.)
The distinct type shares a common internal representation with one of the built-in data types,
called its source data type. This is also known as the distinct type is sourced on a built-in data
type. Despite this common representation, the distinct type is considered to be a separate
data type, distinct from all others (hence the name).
Note: You cannot specify LONG VARCHAR or LONG VARGRAPHIC as the source type of
a distinct type.
In Example 4-1, we show three user-defined distinct types: two UDTs are based on the
built-in data type DECIMAL and are intended to represent monetary values in European
euros and in Spanish pesetas; the other is based on the built-in data type CHARACTER and
is created for customer identification.
Example 4-1 Sample DDL to create UDTs
An instance of a distinct type is considered comparable only with another instance of the
same distinct type. The WITH COMPARISONS clause serves as a reminder that instances of
the new distinct type can be compared with each other, using any of the comparison
operators. (For a list of comparison operators allowed on UDTs, see Figure 4-1 on page 49.)
This clause is required if the source data type is not a large object data type. If the source
data type is BLOB, CLOB, or DBCLOB, the phrase is tolerated with a warning message
(SQLCODE +599, SQLSTATE 01596), even though comparisons are not supported for these
source data types.
Note: Do not specify WITH COMPARISONS for sourced-data-types that are BLOBS,
CLOBs, or DBCLOBs. The WITH COMPARISONS clause is required for all other
source-data-types.
In order to enable you to convert between the built-in data type and a distinct type in both
directions, CAST functions are used. These CAST functions are automatically generated
because we specified the WITH COMPARISONS clause on the CREATE DISTINCT TYPE
SQL statements.
In Example 4-2, we show the equivalent of the CAST functions that are automatically
generated from the creates performed in Example 4-1. The CAST function to convert from the
source data type to the distinct data type has the same name as the UDT (in this case
PESETA and EURO). The CAST function to convert from the UDT to the source data type will
have the same name as the source data type (in this case DECIMAL).
Example 4-2 Automatically generated CAST functions
CAST functions are also used to convert a data type to use a different length, precision or
scale. This is explained in more detail in Section 4.5, “Using CAST functions” on page 48.
In order to be allowed to reference a distinct type in a DDL statement, you need to have the
proper authorizations.
The GRANT EXECUTE ON statement allows users to use CAST functions on a UDT.
Both the USAGE and EXECUTE privileges can be revoked. In Example 4-4, we show a few
typical grants.
Example 4-4 GRANT USAGE/ EXECUTE ON DISTINCT TYPE
In Example 4-5, we show the use of the DROP and COMMENT ON statements for distinct
types. The DATA keyword can be used as a synonym for DISTINCT.
The RESTRICT clause on the DROP statement, must be specified when dropping a distinct
type. This clause restricts (prevents) dropping a distinct type if one of the dependencies
below exist:
A column of a table is defined as this distinct type
A parameter or return value of a user-defined function or stored procedure is defined as
this distinct type.
This distinct type's CAST functions are used in:
– A view definition
– A trigger definition
– A check constraint on CREATE or ALTER table
– A default clause on CREATE or ALTER table
If the distinct type can be dropped, DB2 also drop the CAST functions that were generated for
the distinct type. However, if you have created additional functions to support the distinct type
(second bullet above), you have to drop them first before dropping the UDT.
Use COMMENT ON to document what the distinct data type is to be used for.
Example 4-5 DROP and COMMENT ON for UDTs
Suppose you want to know which contracts are more than 100000 euro. Literals are always
considered to be source-type values and therefore the query in Example 4-6 will fail.
Example 4-6 Strong typing and invalid comparisons
Because you cannot compare data of type EURO (the EUROFEE column is defined as a
EURO distinct type) with data of the source type of EURO (that is, DECIMAL) directly, you
must use the CAST function to cast data from DECIMAL to EURO. You can also use the
DECIMAL function, to cast from EURO to DECIMAL.
Either way you decide to cast, from or to the UDT, you can use:
The function name notation, data-type(argument) or
The cast notation, CAST(argument AS data-type)
In fact, the EURO CAST function can be invoked on any data type that can be promoted to
the DECIMAL data type by the rules of data type promotion. More details on data type
promotion can be found in the DB2 UDB for OS/390 and z/OS Version 7 SQL Reference,
SC26-9944.
Example 4-7 Two casting methods
Other operators and functions that might apply to the source type, such as arithmetic
operators, are not automatically inherited by the distinct type.
=
<
B ETW EEN
>
IS NULL
>=
IN
<=
N OT BETW EEN
<>
IS NOT NULL
¬=
N OT IN
¬<
¬>
Note: The LIKE and NOT LIKE comparison operators are not supported for UDTs.
Note: It is possible to invoke a function on a UDT instance that is allowed on its source
data type even though that function has not been defined on the UDT. To do this, you
must first cast the UDT instance to its source data type.
For example, you might specify that your distinct type WEIGHT inherits the arithmetic
operators “+” and “-”, and the column functions SUM and AVG, from its source type FLOAT.
By selectively inheriting the semantics of the source type, you can make sure that programs
do not perform operations that make no sense, such as multiplying two weights, even though
the underlying source type supports multiplication.
Example 4-9 uses the SUM and AVG column sourced functions.
Example 4-9 Defining sourced column sourced functions on UDTs
The two columns in Example 4-10 cannot be directly compared and the query fails with an
SQLCODE -401. This prevents you from making the mistake of directly comparing or
performing arithmetic operations with columns of different currency types.
Example 4-10 Strong typing and invalid comparisons
Example 4-12 accomplished the same thing than Example 4-11, but we have placed the
conversion factor (multiplication by 166) into a user-defined function called EUR2PES. For
more information on user-defined functions see Chapter 5, “User-defined functions (UDF)” on
page 57. In the additional material you can find an external UDF (EUR22PES) using a Cobol
program to implement the same functionality. See Appendix C, “Additional material” on
page 251 for details.
Example 4-12 Another way to compare pesetas and euros
Example 4-13 shows the use of the CAST functions EURO(DECIMAL) and
DECIMAL(PESETA) in a trigger. The trigger automatically supplies a value for the EUROFEE
column with the amount in euros when a contract is inserted in table
SC246300.TBCONTRACT with the fee in pesetas (the insert includes the PESETAFEE
column).
Example 4-13 Automatic conversion of euros
The use of this sort of trigger can be interesting during a conversion. Programs that have not
been converted can continue to INSERT values in pesetas into the PESETAFEE column,
whereas new programs can already use the new EUROFEE column. This way you don’t have
to change all the programs in one big operation, but have a more gradual migration.
Note: You probably need to implement an UPDATE trigger with similar functionality when
there is a process that can updated the PESETAFEE column.
We recommend that you put the source type of a column in the DECLARE TABLE statement
instead of the distinct data type name. This enables the precompiler to check the embedded
SQL, otherwise checking is deferred until bind time.
A host variable is compatible with a distinct type if the host variable type is compatible with
the source type of the distinct type. You can assign a column value of a distinct type to a host
variable if you can assign a column value of the distinct type's source type to the host
variable. In other words you should use the same definition for your host variables when
referring to a UDT than you would use when referring to its source data type.
If for example, a Cobol program needs to reference a distinct type named CUSTOMER that is
based on a CHAR(15) built-in data type, you should define the host variable as PIC X(15).
-- You have to convert the BUYER column into a type that allows for LIKE comparison
-- or
TEMPLATE U6830982
DSN(BART.&DB..&TS..UNLOAD)
You can define RI relationships on columns that are defined with a distinct type. However,
both the parent’s primary key column(s) must have the same data (distinct) type as the
foreign key’s. For example, a foreign key on a column (BUYER) that is defined with a data
type of CUSTOMER in TBCONTRACT, can reference the primary key column (CUSTID) in
the TBCUSTOMER table, because the primary key is also defined as a CUSTOMER distinct
type. If they don’t have a matching data type, you receive the following SQL error:
SQLCODE = -538, ERROR: FOREIGN KEY BUYERFK DOES NOT CONFORM TO THE DESCRIPTION OF A
PARENT KEY OF TABLE SC246300.TBCUSTOMER
SQLSTATE = 42830 SQLSTATE RETURN CODE
You cannot create a declared temporary table that contains a user distinct type. It is not
supported and you receive the following error:
DSNT408I SQLCODE = -607, ERROR: OPERATION OR OPTION USER DEFINED DATA TYPE IS NOT
DEFINED FOR THIS OBJECT
DSNT418I SQLSTATE = 42832 SQLSTATE RETURN CODE
A field procedure can be defined on a distinct data type column. The source type of the
distinct data type must be a short string column that has a null default value. When the field
procedure is invoked, the value of the column is casted to its source type and then passed to
the field procedure.
In some cases even this large number of built-in functions does not fit all needs. Therefore,
DB2 allows you to write your own user-defined functions (UDF) that call an external program.
This extends the functionality of SQL to whatever you can code in an application program;
essentially, there are no limits.
In this section, we discuss the different sorts of user-defined functions and how to use them.
There are different ways to categorize functions. You can classify them as:
Built-in functions Built-in functions are so-called because they are built into
DB2's system code. Examples of built-in functions are
CHAR and AVG.
These functions now reside in the SYSIBM schema.
For more details on built-in functions see Chapter 6,
“Built-in functions” on page 71.
User-defined functions UDFs allow you to write your own functions for the usage
in SQL statements. They reside in the schema you create
them in.
Another way to categorize them is by the type of arguments they use as input and the number
of arguments they return as output:
Scalar functions A scalar function is an SQL operation that returns a single
value from another value or set of values, and is expressed
as a function name, followed by a list of arguments that are
enclosed in parentheses. Each argument of a scalar
function is a single value. Examples of scalar functions are
CHAR, DATE, and SUBSTR.
Column functions A column function is an SQL operation that produces a
single value from the values of a single column (or a
subset thereof). As with scalar functions, column functions
also return a single value. However, the argument of a
column function is a set of like values. Examples of column
functions are: AVG, COUNT, MAX, MIN and SUM.
Table functions A table function is a function that returns a table to the SQL
statement that references it. A table function can be
referenced only in the FROM clause of a SELECT
statement. In general, the returned table can be referenced
in exactly the same way as any other table. Table functions
are useful for performing SQL operations on non-DB2 data
or moving non-DB2 data into a DB2 table.
Arithmetic and string operators These are the traditional operators that are allowed on
columns (depending on their data type). They can also be
thought of as functions. Arithmetic and string operators
are: “+”, ”-”, “*”, “/”, CONCAT and “||”.
The following Table 5-1shows the different combinations of function types that are allowed:
Table 5-1 Allowable combinations of function types
Scalar Column Table Arithmetic
Sourced OK OK N/A OK
When writing external UDFs, the functions have to follow certain conventions concerning the
passing and returning of arguments, but you can do pretty much what you want. If this
program contains SQL statements then there is an associated package that contains the
program's bound SQL statements.
When you source a UDF on another function, that function can be either a built-in function or
another UDF.
Tip: External functions can be either scalar functions or table functions, they cannot be
column functions.
You can overload functions. You can define multiple functions with the same name as long as
the signatures of the various functions are different. This means that the data type of at least
one parameter or the number of parameters must be different. Based on the data types of the
arguments passed, the database management system is capable of selecting the proper
function.
The number of different business applications using DB2 as a database manager is wide and
varied, and the number continues to grow. This diverse usage places demands on the
database to support a wide variety of scalar functions.
Example 5-1 creates an SQL scalar function (available in V7) that returns size of the surface
area of a circle based on its radius.
Example 5-1 Example of an SQL scalar function
Another example of an SQL scalar UDF can be found in Example 4-12 on page 52.
Example 5-2 demonstrates the definition and use of a user-defined scalar function called
CALC_BONUS. The CALC_BONUS function is defined to DB2 by using the CREATE
FUNCTION statement which associates the function with a user-written C program called
CBONUS. The function calculates the bonus for each employee based upon the employee's
salary and commission. The function takes two input parameters (both DECIMAL(9,2)) and
returns a result of DECIMAL(9,2). Note that a scalar function can only return one value. The
external name 'CBONUS' identifies the name of the load module containing the code for the
function.
A user-defined scalar function can be referenced in the same context that built-in functions
can be referenced; that is, wherever an expression can be used in a SELECT, INSERT,
UPDATE or DELETE statement. User-defined scalar functions can also be referenced in
CALL, VALUES and SET statements. The UPDATE statement in Example 5-2 shows how to
use the CALC_BONUS function.
Example 5-2 User-defined external scalar function
UPDATE SC246300.EMPLOYEE
SET BONUS = CALC_BONUS (SALARY,COMM)
Example 5-3 shows the creation of a column function used to be able to calculate the
minimum value in a PESETA UDT column.
Example 5-3 Sourced column function
User-defined column functions can only be created based on a DB2 built-in column function;
you cannot create your own programs to do that.
Example 5-4 demonstrates the definition and use of a user-defined table function called
EXCH_RATES. This function is written in Cobol and returns exchange rate information for
various currencies. The function takes no input parameters but returns a table of 3 columns.
The external name 'EXCHRATE' identifies the name of the load module that contains the
code for the function. The SELECT statement in the example shows how the EXCH_RATES
function is invoked. In the additional material you can find sample coding showing how to
implement this table UDF. See Appendix C, “Additional material” on page 251 for details.
Table UDFs can be helpful when the actual data that is returned in the table is coming from an
external resource (non-DB2 resource). In our example, currency exchange rates change
constantly and our financial analyst wants to have the latest numbers when simulating his
investment model. Since his model uses ad-hoc queries instead of regular programs, we
cannot use a stored procedure to obtain the information, because you cannot use a CALL
statement outside a program. By using a table UDF, you can provide a result table to the user.
Inside the program that is actually invoked by the UDF, you code pretty much the same things
that you would have when using a stored procedure. To keep our example simple, we obtain
the information from a sequential file. In real life you will probably connect to an external
system to obtain the exchange rate information.
Example 5-4 User-defined table function
CREATE FUNCTION
SC246300.EXCH_RATES()
RETURNS
TABLE(
CURRENCY_FROM CHAR(30) CCSID EBCDIC,
CURRENCY_TO CHAR(30) CCSID EBCDIC,
EXCHANGE_RATE DECIMAL (12,4)
)
LANGUAGE COBOL
NOT DETERMINISTIC
NO SQL
EXTERNAL NAME EXCHRATE
PARAMETER STYLE DB2SQL
CALLED ON NULL INPUT
EXTERNAL ACTION
SCRATCHPAD
FINAL CALL
DISALLOW PARALLEL
NO COLLID
ASUTIME LIMIT 5
SELECT *
FROM TABLE( SC246300.EXCH_RATES()) AS X
WHERE CURRENCY_FROM = 'USD' ;
---------+---------+---------+--------+-------+---------+
CURRENCY_FROM CURRENCY_TO EXCHANGE_RATE
---------+---------+---------+--------+-------+---------+
USD EURO 1.0970
USD FF 7.1955
USD BEF 44.6730
When considering table UDFs there are a few additional things that you have to know. Table
UDFs do not use the same mechanism to pass information back and forth as triggers do
when passing transition tables to for instance a stored procedure. There are no table locator
variables used when dealing with table UDFs.
The program that is invoking the table UDF (SPUFI in the example above) does its normal
SQL processing as with any other ’regular’ table. It will OPEN the cursor, FETCH rows from it
and CLOSE the cursor. (We are ignoring here the additional calls that can take place with
dynamic SQL like PREPARE and DESCRIBE.) When executing OPEN, FETCH and CLOSE
calls, a trip is made to the UDF program that executes in a WLM address space. On each trip
to the UDF program, a ’CALLTYPE’ parameter is passed to the (Cobol) program that is
invoked. The program uses this information to do decide what part of the code to execute. If
you are using the FINAL CALL keyword in the CREATE FUNCTION statement a couple of
extra calls (with a special CALLTYPE) are executed. Following is a list of possible
CALLTYPEs that are used with table UDFs. For more information, see section "Passing
parameter values to and from a user-defined function" in the DB2 UDB for OS/390 and z/OS
Version 7 Application Programming and SQL Guide, SC26-9933.
-2 This is the first call to the user-defined function for the SQL statement.
This type of call occurs only if the FINAL CALL keyword is specified in
the user-defined function definition.
-1 This is the OPEN call to the user-defined function by an SQL
statement.
0 This is a FETCH call to the user-defined function by an SQL
statement. For a FETCH call, all input parameters are passed to the
user-defined function. If a scratchpad is also passed, DB2 does not
modify it. You will have multiple calls of this type, basically one for
every row you want to pass back to the caller to end up in the result
table.
1 This is a CLOSE call.
2 This is a final call. This type of final call occurs only if FINAL CALL is
specified in the user-defined function definition.
255 This is another type of final call. This type of final call occurs when the
invoking application executes a COMMIT or ROLLBACK statement, or
when the invoking application abnormally terminates. When a value of
255 is passed to the user-defined function, the user-defined function
When you want to signal to the calling application (SPUFI in our example) that you have
finished passing rows (reached the end of the sequential file in our case), you set the
SQLSTATE variable to ’02000’ before returning to the invoker.
When you want to preserve information between subsequent invocations of the same table
UDF (for instance when processing/reading the sequential file - CALLTYEP=’0’) you can use
a scratchpad to store that information. In our example there is no real need to do so. The
CREATE FUNCTION does specify the SCRATCHPAD keyword because to debug the code, it
was interesting to keep a counter to track the number of invocations of the table UDF to build
the result table.
The filtering from the WHERE clause is done by DB2 not by the table UDF’s program. The
WHERE clause information is not passed to the program. So when the amount of information
that you pass back to the invoker is large (big sequential file) and almost all of the rows are
filtered out by a WHERE clause, you can end up using more resources than you might expect
based on the number of rows that actually show up in the result set.
You must code a user-defined table function that accesses external resources as a
subprogram. Also ensure that the definer specifies the EXTERNAL ACTION parameter in the
CREATE FUNCTION or ALTER FUNCTION statement. Program variables for a subprogram
persist between invocations of the user-defined function, and use of the EXTERNAL ACTION
parameter ensures that the user-defined function stays in the same address space from one
invocation to another.
Just as there are techniques to ensure efficient access paths using SQL, there are ways you
can maximize the efficiency and reduce the costs of UDFs.
However, there are several ways you can improve the efficiency of external UDFs:
You should try to avoid the cost of having to create a WLM address space and re-use an
existing WLM address space. This may not always be possible, though, if you have a
requirement to isolate different workloads and applications.
For example, if you need to translate a SMALLINT data type to a CHARACTER for some
subsequent string-based manipulation, you have several options, depending on your precise
requirements:
Write an external UDF.
SELECT CHAR_N_SI(SMALLINT(4899))
FROM SYSIBM.SYSDUMMY1;
4899 -- Result
48 -- Result
489 -- Result
48 -- Result
48 -- Result
/*******************************************************************
* Return NULL if at least one input parameter is NULL *
*******************************************************************/
if (*null1In |= 0)
{
*nullpOut = -1;
return;
}
/*******************************************************************
* Convert an integer to a string *
*******************************************************************/
return;
} /* end of CHARNSI */
In this chapter, we give an overview of the built-in functions that were added to DB2 in
versions 6 and 7 and briefly describe their characteristics.
There are a large number of DB2 built-in functions with rich functionality which perform very
well. Therefore, before you start coding your own functions, evaluate what is supplied with
DB2 and understand how to use it. This allows you to:
Maximize the efficiency of your application. Consider here not just the cost of executing
your external function compared to DB2’s built-in functions, but also the best access path
that can be achieved with a UDF as compared to a DB2 built-in function. For instance, a
UDF can be stage 2 when compared to an equivalent stage 1 built-in function.
Improve your productivity, as you do not need to develop and maintain your own code.
Column functions
AVG, COUNT, MAX, MIN and SUM.
Scalar functions
CHAR, COALESCE, DATE, DAY, DAYS, DECIMAL, DIGITS, FLOAT, HEX, HOUR, INTEGER,
LENGTH, MICROSECOND, MINUTE, MONTH, NULLIF, SECOND, STRIP, SUBSTR, TIME,
TIMESTAMP, VALUE, VARGRAPHIC and YEAR.
Column functions
COUNT_BIG Returns the number of rows or values in a set of rows or values. It
performs the same function as COUNT, except the result can be
greater than the maximum value of an integer.
STDDEV Returns the standard deviation of a set of numbers.
VAR, VARIANCE Returns the variance of a set of numbers.
Scalar functions
ABS, ABSVAL Returns the absolute value of the argument.
ACOS Returns the arccosine of an argument as an angle, expressed in
radians.
ASIN Returns the arcsine of an argument as an angle, expressed in
radians.
ATAN Returns the arctangent of an argument as an angle, expressed in
radians.
ATANH Returns the hyperbolic arctangent of an argument as an angle,
expressed in radians.
ATAN2 Returns the arctangent of x and y coordinates as an angle,
expressed in radians.
BLOB Returns a BLOB representation of a string of any type or a ROWID
type.
CEIL, CEILING Returns the smallest integer value that is greater than or equal to
the argument.
CLOB Returns a CLOB representation of a character string or ROWID
type.
COS Returns the cosine of an argument that is expressed as an angle in
radians.
COSH Returns the hyperbolic cosine of an argument that is expressed as
an angle in radians.
DAYOFMONTH Identical to the DAY function.
DAYOFWEEK Returns an integer between 1 and 7 which represents the day of
the week where 1 is Sunday and 7 is Saturday.
DAYOFYEAR Returns an integer between 1 and 366 which represents the day of
the year where 1 is January 1.
DBCLOB Returns a DBCLOB representation of a graphic string type.
DOUBLE Returns a double precision floating-point representation of a
number or character string in the form of a numeric constant.
DOUBLE_PRECISION See description for built-in function DOUBLE.
EXP Returns the exponential function of an argument.
FLOOR Returns the largest integer value that is less than or equal to the
argument.
Some of these functions provide different ways of obtaining the same result. For a detailed
description of the syntax and how to use these functions, please refer to the DB2 UDB for
OS/390 Version 6 SQL Reference, SC26-9014.
Column functions
STDDEV_SAMP Returns the sample standard deviation (/n-1) of a set of numbers.
VARIANCE_SAMP Returns the sample variance of a set of numbers.
Scalar functions
ADD_MONTHS Returns a date that represents the date argument plus the number
of months argument.
For a complete list of all the functions available and for a detailed description of the syntax
and how to use these functions, please refer to DB2 UDB for OS/390 and z/OS Version 7 SQL
Reference, SC26-9944.
Tip: When using the function WEEK, make sure that you understand that the weeks are
based on a starting day of SUNDAY. If you want your week to start on a Monday, then you
should use WEEK_ISO instead.
If the argument of a scalar function is a string from a column with a field procedure, the
function is applied to the decoded form of the value.
Part 2 Enhancements
that allow a more
flexible design
In this part we discuss several enhancements that allow for more flexible application design
including:
Temporary tables
– SQL global temporary tables (created temporary tables)
– Declared temporary tables
Savepoints
Unique column identification
– Identity columns
– ROWID and direct row access
All references to the table from References to the table in References to the table in
multiple applications are to a multiple application processes multiple application processes
single “persistent” table. refer to the same description refer to a distinct description
but a distinct instance of the and instance of the table.
table.
Locking, logging, and recovery. No locking, no logging, no Lock on DBD and intent lock on
recovery. table space, logging of UNDO
records, and limited recovery.
The table can be stored in a Logical work files are used to The table is stored in a
simple, segmented, or store the table in the segmented table space in the
partitioned table space in a WORKFILE database, usually TEMP database (a database
user-defined database or in the called DSNDB07. that is defined AS TEMP).
default database DSNDB04.
Can INSERT, DELETE, and Can INSERT, mass DELETE Can INSERT, DELETE, and
UPDATE individual rows. (without WHERE clause), UPDATE individual rows.
cannot UPDATE rows.
Threads can be reused. Threads can be reused. Threads can be reused under
certain conditions.
The term “global” is taken from the SQL92 Full-Level standard and, as applied to DB2
temporary tables, it means global to an application process at the current server. Thus, if an
instance of a global (created) temporary table exists in an application process at the current
server, that instance can be referenced by any program in the same process at the same
server.
The properties of these tables are a subset of the properties of the global temporary tables of
the SQL92 Full-Level standard.
In stored procedures, temporary tables can be very useful when there is a need to return
large result sets from a stored procedure to a calling program, especially when the caller of
the stored procedure is executing on a remote system, to avoid contention on the real table.
It can also be used as a means to pass data between subroutines within a program. Instead
of returning data in working storage as a large array, a subroutine can build a temporary table
and pass back only the name of the table to the caller that can fetch data from that table at its
convenience.
Another uses for this type of temporary table is to store data that has been read from a
non-DB2 source, like IMS, VSAM or flat files, for subsequent SQL processing, like joining the
created temporary table with other DB2 tables.
Yet another example of the usage of created temporary tables can be in a data warehouse
environment. Tables in those environments are usually very large, and some joins between
several data warehouse tables might be unsuitable. Created temporary tables can be used to
select some sample data from a large table and make a join with the temporary table (for
example, for some data mining or pattern recognition processes). However, a created
temporary table is not indexable, so it should not be used, for example, as an inner table of a
nested loop join.
The table work files are unique to an application, so there is no locking necessary or
desirable. Because they are temporary, there is no concept of recovery. If the application rolls
back, the work file data is lost and changes in the created temporary table are not undone.
Therefore, there is no need for logging.
The lack of locking is consistent with SQL92 temporary tables. The lack of logging is
consistent with the default for SQL92 temporary tables (ON COMMIT DELETE ROWS).
Additional privileges might be required when the data type of a column is a distinct type or the
LIKE clause is specified.
In Example 7-1, the CREATE GLOBAL TEMPORARY TABLE statement creates a definition
of a created temporary table called GLOBALITEM in the current server’s catalog.
Example 7-1 Created temporary table DDL
Example 7-2 shows the information that is stored in the DB2 catalog for created temporary
tables.
Example 7-2 Created temporary table in SYSIBM.SYSTABLES
In Example 7-3, the LIKE table-name or view-name specifies that the columns of the created
temporary table have exactly the same name and description as the columns from the
identified table or view. That is, the columns of SC246300.GLOBALIKE_ITEM have the same
name and description as those of SC246300.TBLINEITEM except for attributes not allowed
for created temporary tables and no default values other than NULL. The name specified
after the LIKE must identify a table, view, or created temporary table that exists at the current
server, and the privilege set must implicitly or explicitly include the SELECT privilege on the
identified table or view.
However, created temporary tables are not updatable. So if the customer would like to
change the quantity of an item during the ordering process, he has to start the order process
from the beginning.
Example 7-3 Use of LIKE clause with created temporary tables
This clause is similar to the LIKE clause on CREATE TABLE with the following differences:
If any column of the identified table or view has an attribute value that is not allowed for a
column of a created temporary table (for example, UNIQUE), that attribute value is ignored
and the corresponding column in the new created temporary table will have the default
value for that attribute unless otherwise indicated.
If a column of the identified table or view allows a default value other than the null value,
then that default value is ignored and the corresponding column in the new created
temporary table will have no default. A default value other than null value is not allowed for
any column of a created temporary table.
You can also create a view on a created temporary table. Example 7-4 show a view on the
created temporary table SC246300.GLOBALINEITEM. Every application sees different
values returned from the view SC2463.GLOBALVIEW depending on the content of their own
created temporary table SC246300.GLOBALINEITEM. The view SC246300.GLOBALVIEW is
defined in the catalog as a normal view on a base table.
Example 7-4 View on a created temporary table
Note: The SQL UPDATE statement is not in the list because updates are not allowed on
created temporary tables.
An instance of a created temporary table exists at the current server until one of the following
actions occurs:
The remote server connection under which the instance was created terminates.
The unit of work under which the instance was created completes.
When you execute a COMMIT statement, DB2 deletes the instance of the created
temporary table unless a cursor for accessing the created temporary table is defined
WITH HOLD and is open. When you execute a ROLLBACK statement, DB2 deletes the
instance of the created temporary table.
The application process ends.
Suppose that you create a temporary table GLOBALINEITEM and then run an application like
the one shown in Example 7-6:
Example 7-6 Using a created temporary table in a program
The contents of GLOBALINEITEM is not deleted until the application ends because C1, a
cursor defined WITH HOLD, is open when the COMMIT statement is executed. In either
case, DB2 drops the instance of GLOBALINEITEM when the application ends.
The logical work file for a temporary table is available for that temporary table name for the
life of the thread/connection which created the logical work file and is not used for any other
purpose, unless the work file is deleted at COMMIT or ROLLBACK/Abort.
All rows and logical work files are deleted at thread termination.
The use of a created temporary table does not preclude thread reuse, and a logical work file
for a temporary table name remains available until de-allocation (assuming
RELEASE(DEALLOCATE)). No new logical work file is allocated for that temporary table
name when the thread is reused.
A temporary table instantiated by an SQL statement using a three-part table name via DB2
private protocol, can be accessed by another SQL statement using the same three-part name
in the same application process for as long as the DB2 thread/connection which established
the instantiation is not terminated.
For example, you can access IMS data from a stored procedure in the following way:
Use the IMS ODBA interface to access IMS databases.
Considerations
Those responsible for system planning should be aware that work file and buffer pool storage
might need to be increased depending on how large a created temporary table is and the
amount of sorting activity required. Given that a logical work file will not be used for any other
purpose for as long as that instantiation of a created temporary table is active, there may be a
need to increase the size or number of physical work file table spaces, especially when there
is also a significant amount of other work (like sort activity) using the work file database
concurrently running on the system.
Those responsible for performance monitoring and tuning should be aware that for created
temporary tables, no index scans are done, only table scans are associated with created
temporary tables. In addition, a DELETE of all rows performed on a created temporary table
does not immediately reclaim the space used for the table. Space reclamation does not occur
until a COMMIT is done.
Created temporary tables are useful whenever some non-sharable temporary processing is
needed and the data does not have to be kept when an application issues a commit. Before
created temporary tables, they only way to avoid logging was to move the process out of
DB2. Remember that there is no locking for created temporary tables.
Be careful, you should not blindly move all batch processing that you used to do outside DB2
in order to avoid logging, into created temporary tables. Created temporary tables are not
indexable. Therefore, they may not provide the best access path if they are very large or if you
have to scan them many times.
For information about temporary tables and their impact on DB2 resources, see:
“Work file data sets” in the DB2 UDB for OS/390 and z/OS Version 7 Administration Guide,
SC26-9931
You can populate the declared temporary table using INSERT statements, modify the table
using searched or positioned UPDATE or DELETE statements, and query the table using
SELECT statements.
A temporary database and temporary table spaces must be created before you can start
using declared temporary tables.
The qualifier for a declared temporary table, if specified, must be SESSION. The qualifier
need not to be specified, it is implicitly defined to be SESSION. The DECLARE statement is
successful even if a table is already defined in the DB2 catalog with the same fully-qualified
name. In Example 7-7, we show you typical DDL to declared a temporary table.
Example 7-7 Sample DDL for a declared temporary table
ON COMMIT DELETE ROWS, which is the default definition, specifies that the rows of the
table are to be deleted following a commit operation (if no WITH HOLD cursors are open on
the table). To avoid mistakes, define the table always as ON COMMIT PRESERVE ROWS
when you want to preserve the rows at COMMIT. This way there is not need to have a cursor
WITH HOLD open to preserve the rows in the DTT across COMMITs.
Important: Always explicitly drop the declared temporary table when it is no longer
needed. If you use the ON COMMIT PRESERVE ROWS option, the thread cannot be
inactivated or reused unless the program explicitly drops the table before the commit. If
you do not explicitly drop the table, it is possible to run out of usable threads.
In Example 7-8, we show you how to create a database and several table spaces to be used
for the creation of declared temporary tables.
Example 7-8 Create a database and table spaces for declared temporary tables
Tip: All table spaces in a TEMP database should be created with the same segment size
and with the same primary space allocation values. DB2 chooses which table space to
place your created temporary table.
You should create table spaces in your TEMP database for all page sizes you use in base
tables. DB2 determines which table space in the TEMP database is used for a given declared
temporary table. If a DECLARE GLOBAL TEMPORARY TABLE statement specifies a row
size that is not supported by any of the table spaces defined in the TEMP database, the
Tip: You may want to isolate declared temporary tables to their own set of buffer pools.
You should bear in mind that the TEMP database could become quite large if the usage of
declared temporary tables is high. You may also need to increase the size of the EDM pool to
account for this extra database.
START, STOP and DISPLAY DB are the only supported commands against the TEMP
database. The standard command syntax should be used but please note the following:
You cannot start a TEMP database as RO (read only).
You cannot use the AT COMMIT option of the STOP DB command.
You cannot stop and start any index spaces that the applications have created.
Tip: Do not specify the following clauses of the CREATE TABLESPACE statement when
defining a table space in a TEMP database: CCSID, LARGE, MEMBER CLUSTER,
COMPRESS, LOB, NUMPARTS, DSSIZE, LOCKSIZE, PCTFREE, FREEPAGE,
LOCKPART, TRACKMOD, GBPCACHE, LOG.
You can define a declared temporary table in any of the following three ways:
Specify all the columns in the table. Unlike columns of created temporary tables, columns
of declared temporary tables can include the WITH DEFAULT clause.
Use a LIKE clause to copy the definition of a base table, created temporary table, or view,
alias or synonym that exist at the current server. The implicit definition includes all
attributes of the columns as they are described in SYSIBM.SYSCOLUMNS.
Use the AS clause and a fullselect to choose specific columns from a base table, created
temporary table or view.
If you want the declared temporary table columns to inherit the defaults of the columns from
the table or view that is named in the select, specify the INCLUDING COLUMN DEFAULTS
clause. If you want the declared temporary table columns to have default values that
correspond to their data types, specify the USING TYPE DEFAULTS clause.
In Example 7-9, the statement defines a declared temporary table called TEMPPROD by
explicitly specifying the columns.
Example 7-9 Explicitly specify columns of declared temporary table
In Example 7-10, the statement defines a declared temporary table called TEMPPROD by
copying the definition of a base table. The base table has an identity column that the declared
temporary table also uses as an identity column.
Example 7-10 Implicit define declared temporary table and identity column
In Example 7-11, the statement defines a declared temporary table called TEMPPROD by
selecting columns from a view. The view has an identity column that the declared temporary
table also uses as an identity column. The declared temporary table inherits the default
defined columns from the view definition. Notice also the DEFINITION ONLY clause. This is
to make clear that the SELECT is not copying data from the original table but merely its
definition.
Example 7-11 Define declared temporary table from a view
Example 7-12 show how to drop the definition of a declared temporary table.
Tip: Use a fully qualified name for your declared temporary tables inside your programs
If you use SESSION as the qualifier for a table name in DML but the application process has
not yet declared a temporary table with the same name, DB2 assumes that you are not
referring to a declared temporary table and looks to see if the table can be found in the
catalog. If you have used SESSION as the owner for non-declared temporary tables, you
may have a release-to-release incompatibility problem. A called program may assume that a
declared temporary table has been declared by its calling program, if a new calling program
does not declare the temporary table, the called program may end up using a base table
instead of the intended declared temporary table. Of course this assumes that the intended
declared temporary table has the same name as an existing base table with an owner of
SESSION.
When a plan or package is bound, any static SQL statement that references any table
qualified by SESSION, is not completely bound. However, the bind of the plan or package
succeeds if there are no errors. These static SQL statements which reference a table-name
qualified by SESSION are incrementally bound at run time if they are executed by the
application process. DB2 handles the SQL statements this way because the definition of a
declared temporary table does not exist until the DECLARE GLOBAL TEMPORARY TABLE
statement is executed and, therefore, DB2 must wait until the plan or package is run to
determine if SESSION.tablename refers to a base table or a declared temporary table.
A declared temporary table is automatically dropped when the application process that
declared it terminates. It is advisable, though, to explicitly drop all declared temporary tables,
when they are no longer needed. The DB2 thread used by an application process that
declares one or more temporary tables with the ON COMMIT PRESERVE ROWS attribute,
only qualifies for reuse or to become an inactive thread, if the application process explicitly
drops all such declared temporary tables before it issues its last explicit COMMIT request.
This action is not required for temporary tables declared with the ON COMMIT DELETE
ROWS attribute for thread reuse in a local environment (IMS or CICS) but is always required
for a remote connection to be eligible for reuse (or become inactive).
Suppose you execute the statements from Example 7-13 in an application program:
Example 7-13 Declared temporary tables in a program
EXEC SQL
DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMPPROD
AS (SELECT * FROM BASEPROD)
DEFINITION ONLY
INCLUDING IDENTITY COLUMN ATTRIBUTES
When the DECLARE GLOBAL TEMPORARY TABLE statement is executed, DB2 creates an
empty instance of TEMPPROD. The INSERT statement populates that instance with rows
from table BASEPROD. The qualifier, SESSION, must be specified in any statement that
references TEMPPROD. When the application issues the COMMIT statement, DB2 keeps all
rows in TEMPPROD because TEMPPROD is defined with ON COMMIT PRESERVE ROWS.
In that case you need to drop the table before the program ends to avoid problems with
thread reuse and inactive threads.
The page size of the TEMP table space must be large enough to hold the longest row in the
declared temporary table. See the DB2 UDB for OS/390 and z/OS Version 7 Installation
Guide, GC26-9936 for information on calculating the page size for TEMP table spaces that
are used for scrollable cursors.
EXEC SQL
EXEC SQL
INSERT INTO CHICAGO.SESSION.TEMPPROD /* Access the temporary table */
(VALUES 'ABCDEF') ; /* at the remote site (forward */
/* reference) */
Do not specify the following clauses of the CREATE INDEX statement when defining an index
on a declared temporary table: COPY, FREEPAGE, PART (on CLUSTER), DEFER,
PCTFREE, USING VCAT, GBPCACHE, DEFINE NO. Index spaces are created in the TEMP
database. An index on a declared temporary table is implicitly dropped when the declared
temporary table on which it was defined is explicitly or implicitly dropped.
You can specify the following clauses: Column names ASC/DESC, CLUSTER, PIECESIZE,
UNIQUE with or without WHERE NOT NULL, and USING STOGROUP.
In order to assist DB2’s access path selection for declared temporary tables, some basic
statistics are collected by DB2 but not stored in the catalog. These statistics are maintained
and used dynamically for optimizing the access path as rows are processed. If you are
concerned about access path selection, you should use EXPLAIN for SQL executed against
declared temporary tables, analyze the output in your PLAN_TABLE as you would for any
other SQL and review your indexing strategy. You cannot run RUNSTATS against a declared
temporary table or its indexes. Since there is no catalog definition for the temporary objects,
there are no statistics available for the utility or you to modify.
When considering whether to create an index on a declared temporary table, bear in mind the
overhead of creating it. A large number of create index statements can have an impact on
system performance. You see more logging occurring. In addition, large scale creation,
opening and deletion of VSAM data sets for the indexes can increase global resource
serialization (GRS) activity. In some cases it may be quicker to simply scan the table, than for
DB2 to create an index and use it to access the data. Remember that you probably only insert
the required rows into the table, so with index access you merely avoid a sort or improve joins
rather than minimize the number of rows read.
7.3.12 Authorization
No authority is required to declare a temporary table unless you use the LIKE clause. In this
case, SELECT access is required on the base table or view specified.
PUBLIC implicitly has authority to create tables in the TEMP database and USE authority on
the table spaces in that database. PUBLIC also has all table privileges (declare, access, and
drop) on declared temporary tables implicitly. The PUBLIC privileges are not recorded in the
catalog nor are they revokable.
Despite PUBLIC authority, there is no security exposure, as the table can only be referenced
by the application process that declared it.
The following statements are not allowed against a declared temporary table:
CREATE VIEW
ALTER TABLE
ALTER INDEX
RENAME TABLE
LOCK TABLE
GRANT/REVOKE table privileges
CREATE ALIAS
CREATE SYNONYM
CREATE TRIGGER
LABEL ON/COMMENT ON
Chapter 8. Savepoints
In this chapter, we discuss how savepoints can be used to create points of consistency within
a logical unit of work.
Data (DML) and schema (DDL) changes made by the transaction after a savepoint is set can
be rolled back to the savepoint, as application logic requires, without affecting the overall
outcome of the transaction. There is no limit to the number of savepoints that can be set. The
scope of a savepoint is the DBMS on which it was set.
A good example for using savepoints is when making flight reservations. John Davenport
from Australia is going on vacation in Denmark. He asks a travel agent to book the flights with
maximum 4 legs (3 stops) both ways. He leaves from Alice Springs, Australia. He has to fly to
Melbourne. From Melbourne he can either fly to Singapore and from there to Copenhagen, or
he can fly via Kuala Lumpur and Amsterdam to Copenhagen. Figure 8-1shows the possible
flying routes from Alice Springs to Copenhagen.
Kuala Lumpur
Melbourne
Alice Springs
This example shows that there is sometimes a need to additional points in time to roll back to.
They do not change the behavior - nor the need - to COMMIT.
For the complete syntax of the SAVEPOINT statement, refer to the DB2 UDB for OS/390
Version 7 SQL Reference, SC26-9944. When you issue the SAVEPOINT statement, DB2
writes an external savepoint log record.
Chapter 8. Savepoints 99
The UNIQUE clause is optional and specifies that the application program cannot activate a
savepoint with the same name as an already active savepoint with the unit of recovery. If you
plan to use a UNIQUE savepoint in a loop, and you do not release or rollback that savepoint
in the loop prior to its reuse, you get an error:
SQLCODE -881: A SAVEPOINT WITH NAME savepoint-name ALREADY EXISTS, BUT THIS
SAVEPOINT NAME CANNOT BE REUSED
SQLSTATE: 3B501
Omitting UNIQUE indicates that the application can reuse this savepoint name within the unit
of recovery. If a savepoint with the same name already exists within the unit of recovery and
the savepoint was not created with the UNIQUE option, the old (existing) savepoint is
destroyed and a new savepoint is created. This is different than using the RELEASE SAVEPOINT
statement, which releases the named savepoint and all subsequently established savepoints.
Rollback to released savepoints is not possible.
Application logic determines whether the savepoint name needs to be or can be reused as
the application progresses, or whether the savepoint name needs to denote a unique
milestone. Specify the optional UNIQUE clause on the SAVEPOINT statement when you do
not intend to reuse the name without first releasing the savepoint. This prevents an invoked
program from accidentally reusing the name.
Tip: You can reuse a savepoint that has been specified as UNIQUE as long as the prior
savepoint with the same name has been released (through the use of a ROLLBACK or a
RELEASE SAVEPOINT) prior to attempting to reuse it.
The ON ROLLBACK RETAIN CURSORS clause is mandatory and specifies that any cursors that are
opened after the savepoint is set are not tracked, and thus, are not closed upon rollback to
the savepoint. Although these cursors remain open after rollback to the savepoint, they might
not be usable. For example, if rolling back to the savepoint causes the insertion of a row upon
which the cursor is positioned to be rolled back, using the cursor to update or delete the row
results in an error:
SQLCODE -508: THE CURSOR IDENTIFIED IN THE UPDATE OR DELETE STATEMENT IS NOT
POSITIONED ON A ROW
SQLSTATE: 24504
With scrollable cursors (see “Update and delete holes” on page 170 for more details), you
would get a different error:
SQLCODE -222: AN UPDATE OR DELETE WAS ATTEMPTED AGAINST A HOLE USING cursor-name
SQLSTATE: 24510
The ON ROLLBACK RETAIN LOCKS clause specifies that any locks that are acquired after the
savepoint is set are not tracked and are not released upon rollback to the savepoint. If you do
not specify this clause, it is implied (this is the default and at the present time, there is no
other option for locks).
Any updates outside the local DBMS, such as remote DB2s, VSAM, CICS, and IMS, are not
backed out upon rollback to the savepoint, not even when under the control of RRS. Any
cursors that are opened after the savepoint is set are not closed when a rollback to the
savepoint is issued. Changes in cursor positioning are not backed out upon rollback to the
savepoint. Any locks that are acquired after the savepoint is set are not released upon
rollback to the savepoint. Any savepoints that are set after the one to which you roll back to,
are released. The savepoint to which the rollback is performed is not released. If a savepoint
name is not specified, the rollback is to the last active savepoint. If no savepoint is active, an
error occurs:
SQLCODE -880: SAVEPOINT savepoint-name DOES NOT EXIST OR IS INVALID IN THIS CONTEXT
SQLSTATE: 3B001
or
SQLCODE -882: SAVEPOINT DOES NOT EXIST
SQLSTATE: 3B502
Rolling back a savepoint has no effects on created temporary tables because there is no
logging for CTTs. Changes to declared temporary tables on the other hand, can be
‘safeguarded’ or undone by rolling back to a savepoint. For more information on declared
temporary tables, see 7.3, “Declared temporary tables” on page 88.
The ROLLBACK statement without the TO SAVEPOINT clause (this is the normal SQL ROLLBACK
statement) rolls back the entire unit of recovery. All savepoints set within the unit of recovery
are released.
The RELEASE SAVEPOINT statement is used to release a savepoint and any subsequently
established savepoints.The syntax of the COMMIT statement is unchanged. COMMIT
releases all savepoints that were set within the unit of recovery.
DRDA access using a CONNECT statement is allowed; however, the savepoints are local to
their site and do not cross an explicit CONNECT. For example, location A can connect to
location B, but the savepoint set at A is unknown to location B and does not cover any
operations performed at location B. A savepoint set prior to a CONNECT is known only at the
local site, and a savepoint set after a connect is known only at the remote site. Consequently,
application-directed connections are expected to manage the processing at the alternate site.
Recently DB2 has introduced two new concepts that can guarantee unique column values
without having to create an index. In addition, you can eliminate the application coding that
was implemented to assign unique column values for those columns.
We evaluate their characteristics, usability and especially their ability to really replace a
unique index and application generated unique numbers.
When you have a multi-column primary key and the table has several dependent tables, you
have to ‘copy’ many columns to the dependent tables to build the foreign keys. This makes
the keys very long and you have to code many predicates when joining the tables. Having too
many predicates can increase the chance that you make a mistake and sometimes forget a
join predicate. Having many columns in the index also makes the index grow quite large.
Instead of using the long key as the primary and foreign key, you can use an artificial unique
identifier for the parent table and use that generated value as a primary key and foreign key
for the dependent table.
Another use for identity columns is when you just need a generated unique column for a
table. If we do not need to reference the rows by this column, then there is no need to even
create an index for it, and uniqueness is guaranteed by the system for generated values
(when you use GENERATED ALWAYS, when you define the column as an IDENTITY
column).
Important: Please note that it is possible to have duplicate values for an identity column if
you specify the CYCLE option (introduced in V7) as one of the keywords when you define
the identity column.
Another reason to use identity columns is so you don’t have to keep track of ever increasing
numbers used as unique keys, for example, order number, employee number, and so on in
your applications. DB2 takes over the responsibility of keeping track of the last number used
so this complexity can be removed from your application.
The fact that you can create a table with an IDENTITY column as specified in Example 9-1,
does not mean that you should. More often than not, it is very important that you avoid
duplicates, so you do not specify the CYCLE keyword. Also, you should take into account
how large the number may get over a long period of time and provide for a much larger
MAXVALUE. If you reach MAXVALUE and you don’t have CYCLE specified, you are not able
to insert additional rows, you have to drop the table and recreate it with a larger MAXVALUE
and reload the rows.
The GENERATED ALWAYS attribute of the identity column definition is treated in more detail
in section 9.1.5, “How to populate an identity column” .
When a table is being created LIKE another table that contains an identity column, a new
option on the LIKE clause, INCLUDING IDENTITY COLUMN ATTRIBUTES, can be used to
specify that all the identity column attributes are to be inherited by the new table. If
INCLUDING IDENTITY COLUMN ATTRIBUTES is omitted, the new table only inherits the
data type of the identity column and none of the other column attributes. You cannot create a
table LIKE a view and specify the INCLUDING IDENTITY COLUMN ATTRIBUTES keywords.
In Example 9-2, we specify that T2 should inherit all of the identity column attributes from T1
by specifying the INCLUDING IDENTITY COLUMN ATTRIBUTES clause.
Example 9-2 Copying identity column attributes with the LIKE clause
GENERATED BY DEFAULT
When inserting a row you can either provide the value for the identity column or let DB2
generate the value for you (by not specifying a value or using the DEFAULT keyword in
the VALUES clause).
Values can be changed using an UPDATE statement.
DB2 does not guarantee the uniqueness of a ‘GENERATED BY DEFAULT’ identity column
value among all the column values, but only among the set of values it previously
generated. To guarantee uniqueness, you have to create a unique index on the identity
column.
You must code re-try logic in your application in order to handle the possibility of
duplicates.
GENERATED ALWAYS
DB2 always generates a value for the column when a row is inserted into the table, and
DB2 guarantees that all column values are unique unless you use the CYCLE keyword at
column definition time.
If an INSERT or UPDATE statement is issued in an attempt to provide an explicit value for
the identity column, DB2 returns an error and the statement fails. However, the keyword
DEFAULT can be used in the VALUES clause of the insert statement in order to have DB2
generate the value. Using the DEFAULT keyword is especially useful in dynamic SQL,
since it eliminates the need to name all the columns of a table in an INSERT statement
because there is a column we are not allowed to provide a value for. In statically bound
programs, you should always name the columns in order to avoid a table change from
impacting your program. Therefore, it is also a good idea to use the DEFAULT in the
values list of static programs.
Note: If you add an identity column to a table and run a point in time recovery for that table
space to an RBA prior to the time the REORG on the table populated the column, the table
space is again placed to REORP status.
However, if the (un)load file contains identity column information, you can use the
DSN_IDENTITY for the identity column in the LOAD control cards and specify the
IGNOREFIELDS keyword. The values in the (un)load file are ignored and a new value is
generated by the LOAD utility for the identity column.
Even when the identity column is defined as GENERATED BY DEFAULT and the (un)load file
contains identity column values, if you prefer to have DB2 generate (or re-generate) values for
the column, then you can use the name of DSN_IDENTITY for the identity column in the load
control cards and specify the IGNOREFIELDS keyword.
For more information on this, refer to DB2 UDB for OS/390 and z/OS Utility Guide and
Reference, SC26-9945.
Important: There is no means by which you can load existing values into an identity
column that is GENERATED ALWAYS. If you are reloading data to a table with such a
column, you must first drop and recreate the table and make the identity column
GENERATED BY DEFAULT. There is no way to convert from GENERATED ALWAYS to
GENERATED BY DEFAULT or visa versa.
Another way to accomplish the same thing, no matter how the column is defined, is to
explicitly specify all the columns, except for the identity column (C1), from T1 and T2.
We recommend that you do not use the OVERRIDING USER VALUE clause because if you
ever have to change the identity column definition from GENERATED ALWAYS to
GENERATED BY DEFAULT, then the BIND to pick up the new table would fail and it would
require application changes in order to correct the problem.
The OVERRIDING USER VALUE clause can be used in a single row INSERT statement.
Example 9-3 Insert with select from another table
INSERT INTO T2
OVERRIDING USER VALUE
SELECT * FROM T1
INSERT INTO T2
(C2, C3, C4, C5, C6)
SELECT C2, C3, C4, C5, C6 FROM T1 -- In this case, C1 is the identity
-- column and C2, C3, C4, C5, C6
-- are all the other columns of the
-- table, note that C1 is not coded
-- in either column list
This function returns the most recently assigned value for an identity column at the same
nesting level. (A new nesting level is initiated any time a trigger, external UDF, or stored
procedure is invoked.) The data type for the result of the IDENTITY_VAL_LOCAL() value
function is always DECIMAL(31,0), regardless of the actual data type of the identity column
whose value is being returned.
The IDENTITY_VAL_LOCAL function returns a null value in two cases. The first case is when
you run the function before inserting any rows with identity columns at the current nesting
level. The second case is when a COMMIT or ROLLBACK has been issued since the most
recent INSERT statement that assigned a value.
You always get the last identity column value inserted at the current nesting level. So if you
are inserting to many tables with identity columns, call the IDENTITY_VAL_LOCAL function
immediately after each insert if you want to know the value of each identity column inserted.
The result of the function is not affected if you insert to other tables which do not have identity
columns or process other SQL that does not insert.
In Example 9-4, we show how to set host variable :ID-MEMBNO to the value that was
assigned to the identity column MEMBNO when the first row was inserted into the table
TBMEMBER (see Example 9-1). In this case, since it is our first insert into the TBMEMBER
table, the value returned by the function is 1000.
Example 9-4 Retrieving an identity column value
Note: the insert could also have been coded like this:
INSERT INTO TBMEMBER
(MEMBNO, NAME, INCOME, DONATION)
VALUES (DEFAULT, 'Kate', 120000.00, 500) ;
or like this:
INSERT INTO TBMEMBER
VALUES (DEFAULT,'Kate', 120000.00, 500) ;
In Figure 9-1, we assume CACHE 20 (the default) is used for an identity column and member
DB2A has cached values 1-20 and member DB2B has cached values 21-40. If the application
that inserts the first row into the table runs on member DB2B, the identity column value
assigned to that row is 21. If the application that inserts the second row into the table runs on
member DB2A, the identity column value assigned to that row is 1.
In a data sharing environment, there is a synchronous forced log write each time the counter
is updated. If you do this once every 20 times instead of every insert (when NO CACHE is
used), the overhead is significantly reduced.
Tip: Increasing the CACHE value above the default of 20 probably gives negligible
benefits.
inserts
row at Identity Column Column 2 Column 3 inserts
time T2 row at
21
time T1
1
Figure 9-1 Identity column value assignment in a data sharing environment
Every data table that you want to ‘equip’ with this technique uses two tables; A table with that
contains the actual data and a table that has the identity column but no data. You use the
table with the identity column to obtain the unique number and use that number to insert the
real row in the actual data table. This way you separate the actual data from the (single row)
table with the identity column.
In order to hide the complexity of using two tables instead of one, you can mask the process
with a stored procedure or a UDF. The stored procedure or UDF can be implemented as
follows:
1. Insert a row into the identity column table.
2. Retrieve its value back using the IDENTITY_VAL_LOCAL() function.
3. Delete that row from the identity column table since we no longer need it.
4. Insert the actual row using the retrieved value as a unique identifier.
The major advantage of this approach over the old style ‘highest key assigned table’ is that
there is no locking problem. The row(s) in the identity column table are never updated. We
only INSERT and DELETE rows. In case of a very high volume transaction workload, you
might want to avoid deleting the row from the identity column table in the stored procedure or
UDF because it is an extra SQL call. Instead you can have an asynchronous process running
in the background that cleans up rows at a regular intervals. This does not cause any locking
contention either, since INSERTS take a new page in case the candidate page is locked.
Tip: If your application cannot tolerate gaps for the unique keys, then identity columns may
not be a viable solution for you.
We recommend that you use the default caching value (CACHE 20) in a data sharing
environment to reduce serialization overhead. Be aware that in a data sharing environment
with cached identity column values, it is possible for identity column values to be assigned out
of sequence.
Tip: If you are in a data sharing environment, and your application does not tolerate for
identity column values to be assigned out of sequence, then you should use the NO
CACHE option. Be aware that this can have negative performance implications on your
application and can reduce the throughput of insert transactions.
This is also very important when you have to change the table design, like eliminating a
column. A lot of these design changes require the table to be dropped and recreated. You
probably want the same identity column values to be used after the drop/create operation
than before, for example, because of RI relationships with other tables. This forces you to
define the identity column as GENERATED BY DEFAULT. In order to guarantee uniqueness
of the values, which is probably why you wanted the identity column in the first place, a
unique index is required.
Be aware that when you specify the keyword CYCLE, once you reach MAXVALUE, you start
the numbering from the MINVALUE again. DB2 does not warn you when reaching the
MAXVALUE. When using CYCLE, to be sure that the values are unique, you should create a
unique index on the identity column, even when the identity column is defined as
GENERATED ALWAYS.
Important: You cannot alter any of the definitions for an identity column. Be sure you
consider every possible situation of how the data is or may be used before adding an
identity column to a table.
When the identity column is defined as GENERATED ALWAYS and is the primary key in an
RI structure, loading data into the RI structure is extremely complicated. Since the values for
the identity column are not known until they are loaded (because of the GENERATED
ALWAYS attribute) you have to generate the foreign keys in the dependent tables based on
these values since they have to match the primary key. This can be another reason for not
using GENERATED ALWAYS.
An identity column cannot be defined with the FIELDPROC clause or the WITH DEFAULT
clause. Nor can an EDITPROC be specified for a table that has an identity column.
Also you cannot load a partition (LOAD INTO PART x) when an identity column is part of the
partitioning index.
There are two aspects to ROWIDs besides its use to retrieve LOB data:
The first one is the fact that a ROWID can be a unique random number generated by the
DBMS. This can look like a very appealing option to solve many design issues.
We try to explain that there are some advantages when using ROWIDs, but there is also a
maintenance cost (REORGs cause the external representation of the ROWIDs to change)
associated with using ROWIDs and it is easy to use them the wrong way in application
programs.
You must use have a column with a ROWID data type in a table that contains a LOB column.
The ROWID column is stored in the base table and is used to lookup the actual LOB data in
the LOB table space. However, a ROWID is not the same as a RID.
Although ROWIDs were designed to be used with LOBs, they can also be used in ‘regular’
tables that don’t contain LOB columns. In that case, a ROWID column can be used in a
WHERE predicate to enable queries to navigate directly to a row in the table.
Normally, DB2 generates the value (as indicated by GENERATED ALWAYS clause) for the
ROWID column. ROWIDs are random and unique, therefore at first glance they look like ideal
candidates to be used as a partitioning key of a partitioned table space if you need to spread
rows randomly across the partitions.
There is one ROWID column per base table, so, all LOB columns in a row have the same
ROWID. The ROWID column is a unique identifier for each row of the table and the basis of
the key for indexing the auxiliary table(s).
Example 9-5 shows a table that contains LOB columns, and therefore, a ROWID column is
required. Column IDCOL is defined with the ROWID data type. Notice that in order to be able
to work with the table that contains a LOB column, you also need to create the LOB table
space, the auxiliary table and the auxiliary index.
Example 9-5 ROWID column
TITLE IDCOL
------------------------------------------------------------------------
MACBETH CDC8A0A420E6D44F260401D370180100000000000203
DON QUIJOTE DE LA MANCHA 7B80A0A420E6D64F260401D370180100000000000204
------------------------------------------------------------------------
As mentioned in the introduction of this section, the use of a ROWID column is not restricted
to tables that have LOB columns defined. You can have a ROWID column in a table that does
not have any LOB columns defined.
SELECT TITLE,IDCOL
FROM SC246300.LITERATURE
WHERE IDCOL=ROWID(X'7B80A0A420E6D64F260401D370180100000000000204')
---------+---------+---------+---------+---------+---------+---------+-------
TITLE IDCOL
---------+---------+---------+---------+---------+---------+---------+-------
DON QUIJOTE DE LA MANCHA 7B80A0A420E6D64F260401D370180100000000000204
The value generated by DB2 is essentially a random number. If data is being propagated or
copied from one table to another, the ROWID value is allowed to appear in the VALUES
clause of an INSERT statement. In this case, the ROWID column has to be defined as
GENERATED BY DEFAULT and to ensure uniqueness, a unique index on the ROWID
column must be created.
GENERATED ALWAYS
Use this option unless copying data containing a ROWID column from another table.
ROWID column values cannot appear in the VALUES clause of an INSERT statement.
No index is required to guarantee uniqueness of the values.
In Example 9-7, we show how you can copy data from one table to another. If you have
defined the ROWID as GENERATED ALWAYS, then DB2 generates a new ROWID for each
INSERTed row (the ROWID columns cannot be copied or propagated).
Note that the LITERATURE_GA table does no longer contain the LOB columns; this shows
that you can also create a table with a ROWID column without any LOB columns. Note also
that in that case you no longer need the LOB table space, auxiliary table and index (they are
only present when you have LOB columns defined).
Example 9-7 Copying data to a table with GENERATED ALWAYS ROWID via subselect
-- The ROWID column (IDCOL) is not specified because you can not insert
-- values in a GENERATED ALWAYS column.
---------+---------+---------+---------+---------+---------+---------+-------
TITLE IDCOL
---------+---------+---------+---------+---------+---------+---------+-------
MACBETH 88DDF53DA0E6D808260401D370100100000000000207
DON QUIJOTE DE LA MANCHA D1DDF53DA0E6D818260401D370100100000000000208
Notice that when copying data to a table with a GENERATED ALWAYS ROWID column via a
subselect, the ROWIDs are completely different. New values are generated when the rows
are inserted into the LITERATURE_GA table. The ROWID column is not selected because
you cannot insert values in a GENERATED ALWAYS column.
If GENERATED BY DEFAULT was specified, you can supply a ROWID value for the
INSERTed rows.
Example 9-8 Copying data to a table with GENERATED BY DEFAULT ROWID via subselect
The only column function that allows the ROWID data type is COUNT. The ROWID data type
column is allowed in the BLOB, CAST, CHAR, CLOB, HEX, IFNULL, LENGTH, NULLIF,
VALUE, and VARCHAR scalar functions.
You can create an index on a ROWID column. DECLARE CURSOR, FETCH, DECLARE
TABLE, DESCRIBE, PREPARE INTO and EXECUTE also support ROWID columns.
The output that DCLGEN generates for a ROWID is shown in Example 9-9.
Example 9-9 DCLGEN output for a ROWID column
COBOL: 01 LITERATURE.
..
10 IDCOL USAGE SQL TYPE IS ROWID.
..
C/C++: struct
{ ..
SQL TYPE IS ROWID IDCOL;
..
} LITERATURE;
The precompiler will turn ’SQLTYPE IS ROWID’ into normal host language variable definitions
as shown in Example 9-10 in case of a Cobol program. The variable that will contain the
ROWID column is defined as a 40 byte character field and a 2 byte length field. The length
field will contain the actual length of the ROWID value (normally 22 bytes when the ROWID
column has not been added to the table after the table was created).
Example 9-10 Coding a ROWID host variable in Cobol
*01 IDCOL USAGE SQL TYPE IS ROWID. <-- What you code in Cobol
(’*’ is the comment placed by precompiler)
01 IDCOL. <-- Precompiler replacement
49 IDCOL-LEN PIC S9(4) USAGE COMP.
49 IDCOL-TEXT PIC X(40).
Although the random nature of the ROWID column eliminates insert hot-spots from the table
space, you usually have to define additional non-partitioning indexes to retrieve the data
afterwards, since the partitioning key, the ROWID column, is not a meaningful ‘natural’ key.
Especially on very large table spaces, having several non-partitioning indexes can introduce
some challenges for ’sequential’ batch runs and utilities.
There are better techniques to spread rows to be inserted randomly across the partitions of a
partitioned table space when there is no column that can serve as a good partitioning key.
You can, for example, create a hashing fieldproc.
Example 9-11 shows that you need a non-partitioning index if you choose a ROWID column
as partitioning key.
Example 9-11 Why not to use a ROWID column as a partitioning key
-- However you still need a non-partitioning index to find the rows (for the first
-- time) so you need another index.
Application programs that want to implement using direct row access have to be carefully
designed. It is very easy to code it wrong, especially when using pseudo conversational
transaction managers. If not used correctly, it can lead to serious performance problems
when the row accessed changed location in the meantime. There is no integrity exposure,
like trying to update the wrong row. The system detects that the ROWID value is no longer
‘valid’ and uses an alternate access path to access the data.
When explaining SQL statements that might qualify for direct row access, a new
PLAN_TABLE column, PRIMARY_ACCESSTYPE indicates whether (value D) or not (blank)
direct row access is attempted.
From a performance point of view, using direct row access may save some index accesses
(getpages and possibly I/Os as well), but, there is always the ‘risk’ that DB2 has to revert to
an alternate access path if direct row access cannot be used for some reason at execution
time. That alternate access path can mean degrading to a table space scan (see “Falling back
from direct row access to another access path” on page 120.
Note: ROWIDs were originally conceived to be used with LOBs. However, direct row
access has nothing to do with LOBs whatsoever. Direct row access is used to locate rows
in a table that have a ROWID column defined. Accessing LOBs in the LOB table space is
done using the index on the auxiliary table.
Direct row access can be used on any table that has a ROWID column. Example 9-12 show
an example of direct row access.
Example 9-12 ROWID direct row access
Direct row access can be very fast, but should only be used if extremely high performance is
needed. In order for an application to be able to benefit from direct row access the application
logic must first retrieve the row with data and at a later stage update or delete it.
A lot of applications cannot use a ‘for update of’ cursor. For example, an on-line IMS
transaction cannot use this type of cursor since selecting the data and displaying it on the
screen, and updating the data are separate executions of a transaction. The cursor is no
longer there when the transaction returns to do the update. Another reason for not using a ‘for
update of’ cursor by many applications is the type of locking that is involved with this type of
cursor. The UPDATE or DELETE operation takes place immediately after the row has been
fetched because of the usage of the WHERE CURRENT OF clause (that is what makes it a
positioned UPDATE or DELETE). Therefore, the X-lock on the page/row is usually held longer
than is the case when using a non-cursor update, that you usually perform as close to the
COMMIT as possible (to reduce the amount of time the X-lock is held). Therefore, using a
positioned UPDATE or DELETE may have an impact on concurrency.
Therefore, in order to be considered as a candidate for direct row access, your application:
Needs to read the data first and update/delete it later
Is unable to use a ‘for update of’ cursor
If the application does not have to read the row, you cannot use direct row access. You must
retrieve the ROWID column first and use it later on to read/update/delete the same data
again.
A good use may be if for some reason you have to update the same row several times.
Even though direct row access can be a great access path (avoiding index access and
scanning needs), storing ROWID values in a table is usually a bad idea, especially when
storing a large number of rows for a longer period of time as is explained in the next section.
This can have a profound impact on the performance of applications that count on direct row
access. So it is important that applications handle this situation. Some options are:
Ensure that your application does not try to remember ROWID values across
reorganizations of the table space or other processes that could move the row. (A
not-in-place update, either within the page or even to a different page does not invalidate
the ROWID, as such.)
– If an application variable stores a ROWID column value longer than it holds a claim on
the table space, a REORG can occur and move the row, disabling direct row access.
Remember that claims are released at commit if the cursor is not defined WITH HOLD.
Therefore, a recommended practice is to select the ROWID just prior to using it in
another SQL statement, without intermediate COMMITS, that could allow a REORG to
occur. So plan your commit processing accordingly or you could have unexpected
results, as it could happen if you code Example 9-13.
COMMIT ;
One of the checks that is performed to see if a ROWID value is valid is verifying the EPOCH
number. The EPOCH number can be found in SYSIBM.SYSTABLEPART. When a table
space is created, the initial value of EPOCH is zero, and it is incremented whenever an
operation resets the page set or partition (such as REORG or LOAD REPLACE).
Important: If you use non-IBM utilities that reset the page set or partition (like REORG or
LOAD REPLACE) and you use direct row access, make sure that these utilities update the
EPOCH column in SYSTABLEPART when they reset the page set or partition.
If the unique index defined on a ROWID column with the GENERATE BY DEFAULT attribute
is dropped, the table is marked incomplete, and access to the table is not allowed until the
index is created.
Although both identity columns and ROWIDs guarantee a unique number the nature of the
number is quite different. A ROWID value is a random hex string whereas an identity column
is an ever increasing or decreasing (when cycling is not allowed) numeric number.
Both have some similar restrictions concerning the usage of GENERATED ALWAYS and
GENERATED BY DEFAULT columns. GENERATED ALWAYS seems the more attractive as it
does not require you to have a unique index to guarantee uniqueness. However, in the case of
a GENERATED ALWAYS ROWID column it might be wise to create a unique index on the
ROWID column in case DB2 cannot use direct row access and DB2 has to revert to the
‘alternate’ access path.
A lot of installations copy (LOAD) their production data into a development and/or quality
assurance (QA) environment. This can cause problems when using GENERATED ALWAYS
columns (for both identity and ROWID columns). The LOAD will replace the values in those
columns by new values. This can pose a problem if tables are related to each other based on
such a column because the value in the ’GENERATED ALWAYS’ column will have changed
but the value in the other table has not. As an alternative you might choose to define those
columns as GENERATED BY DEFAULT in your development and/or quality assurance
environment. This will allow the LOAD utility to preserve the values from the production
environment (where the column is defined as GENERATED ALWAYS). However, you should
be aware that this can cause other problems. Programs will be allowed to specify a
’GENERATED BY DEFAULT’ column in an INSERT statement for example. This will work fine
in development and QA, but will fail once the program is moved into production. Programmers
should be made aware of this potential problem to avoid production outages.
Identity and ROWID columns pose some complications when having to add data to a
partitioned table space using the LOAD utility (for example, when GENERATED ALWAYS is
used for an identity column or to load a partition when a ROWID column is part of the
partitioning key).
The ROWID data type has no built-in function that is comparable to IDENTITY_VAL_LOCAL()
that can be used to retrieve the most recently used value for an identity column.
The use of identity columns can be a replacement for application code that generates ever
increasing or decreasing values. Identity columns do not have the locking considerations that
you need to deal with when implementing this with application logic. However, applications
that use identity columns have to tolerate gaps in the numbers that are assigned. Another
consideration is that loading data becomes more complex when using the GENERATED
ALWAYS attribute. Also all considerations associated with inserting rows at the end of a table
still apply when using identity columns.
So the truth of the matter is that although very promising at first glance, both identity columns
and ROWID columns (outside of LOBs) need to be examined very closely before you decide
to use them. Although they can serve specific needs they probably will not be that widely
used.
The data type of the expression prior to the first WHEN keyword must be comparable to the
data types of each expression that follows the WHEN keywords.
Simple-when-clause specifies that the value of the expression prior to the first WHEN
keyword is tested for equality with the value of each expression that follows the WHEN
keywords. Specifies the result for each WHEN keyword when the expressions are equal.
expression WHEN expression THEN result-expression / NULL
DEPTNO WHEN ‘C’ THEN ‘SYSTEMS’
In the Example 10-1 we select the employee number, last name and division from the
TBEMPLOYEE table. The first character of the work department number represents the
division within the organization. By using a CASE expression with a simple-when-clause, it is
possible to translate the codes and list the full name of the division to which each employee
belongs.
Example 10-1 SELECT with CASE expression and simple WHEN clause
SELECT
EMPNO
,LASTNAME
,CASE SUBSTR(WORKDEPT,1,1)
WHEN 'A' THEN 'ADMINISTRATION'
WHEN 'B' THEN 'HUMAN RESOURCES'
WHEN 'C' THEN 'DESIGN'
WHEN 'D' THEN 'OPERATIONS'
ELSE 'UNKNOWN DEPARTMENT'
END AS DIVISION
FROM SC246300.TBEMPLOYEE ;
Result-expression specifies an expression that follows the THEN and ELSE keywords. It
specifies the result of a searched-when-clause or a simple-when-clause that is true, or a
result if no case is true.
WHEN 'D' THEN 'OPERATIONS'
ELSE 'UNKNOWN DEPARTMENT'
All result-expressions must be compatible. There must be at least one result-expression in the
CASE expression with a defined data type. NULL cannot be specified for every data type.
RAISE_ERROR cannot be specified for every CASE result-expression. For an example of
using CASE expressions in a trigger see Example 3-10 on page 24.
Search-condition specifies a condition that is true, false or unknown about a row, or group of
table data.
CASE expressions can be used to replace decision logic implemented with UNION, UNION
ALL or complicated OR-clauses. These methods temporarily divide result sets based on
multiple returned values which may cause DB2 to repeatedly scan the same data pages. In
contrast, with the CASE expression it is possible to accomplish the same with one pass of the
data. For this reason, CASE expressions may significantly reduce the elapsed time of
queries.
Looking at Example 10-3 and Example 10-4, you can see how to avoid coding several update
statements by coding a single update with a CASE expressions. The examples show two
different ways to process a salary increase. The salary raise is dependent on the job class.
Certain classes get 10%, others 8%, others 5%, and yet others a 3.5% raise. You can code
several update statements, one for each job class, and scan the TBEMPLOYEE table once
for each update (assuming no index is present) as shown in Example 10-3, or you can use a
CASE expression, shown in Example 10-4, and write one SQL statement to do all the
updates with one pass through the data.
Example 10-3 Three updates vs. one update with a CASE expression
UPDATE SC246300.TBEMPLOYEE
SET SALARY = SALARY * 1.10
WHERE JOB IN (‘MANAGER’ , ‘SUPRVSR’) ;
UPDATE SC246300.TBEMPLOYEE
SET SALARY = SALARY * 1.08
WHERE JOB IN (‘DBA’ , ‘SYS PROG’) ;
UPDATE SC246300.TBEMPLOYEE
SET SALARY = SALARY * 1.035
WHERE JOB NOT IN (‘MANAGER’ , ‘SUPRVSR’, ‘DBA’ , ‘SYS PROG’, ‘PRGRMR’) ;
Example 10-4 One update with the CASE expression and only one pass of the data
UPDATE SC246300.TBEMPLOYEE
SET SALARY = CASE
WHEN JOB IN (‘MANAGER’ , ‘SUPRVSR’) THEN SALARY * 1.10
WHEN JOB IN (‘DBA’ , ‘SYS PROG’) THEN SALARY * 1.08
WHEN JOB = ‘PRGRMR’ THEN SALARY * 1.05
ELSE SALARY * 1.035
END ;
UPDATE SC246300.TBORDER
SET ORDERSTATUS = '0'
WHERE ORDERDATE <= CURRENT DATE + 30 DAYS ;
UPDATE SC246300.TBORDER
SET ORDERSTATUS = '1'
WHERE ORDERDATE > CURRENT DATE + 30 DAYS
AND ORDERDATE <= CURRENT DATE + 60 DAYS ;
UPDATE SC246300.TBORDER
SET ORDERSTATUS = '2'
WHERE ORDERDATE > CURRENT DATE + 60 DAYS ;
Example 10-6 Same update implemented with CASE expression and only one pass of the data
UPDATE SC246300.TBORDER
SET ORDERSTATUS = CASE
WHEN ORDERDATE <= CURRENT DATE + 30 DAYS THEN ‘0’
WHEN (ORDERDATE > CURRENT DATE + 30 DAYS AND
ORDERDATE <= CURRENT DATE + 60 DAYS) THEN '1'
WHEN ORDERDATE > CURRENT DATE + 60 DAYS THEN '2'
END ;
UPDATE SC246300.TBORDER
SET ORDERSTATUS = CASE
WHEN ORDERDATE <= CURRENT DATE + 30 DAYS THEN ‘0’
WHEN ORDERDATE <= CURRENT DATE + 60 DAYS THEN '1'
ELSE ‘2’
END ;
Example 10-8 uses a CASE expression to avoid ‘division by zero’ errors. The following
queries show an accumulation or summing operation. In the first query, it is possible to get an
error because PAYMT_PAST_DUE_CT can be zero and division by zero is not allowed.
Notice that in the second part of the example, the CASE statement first checks to see if the
value of PAYMT_PAST_DUE_CT is zero, if it is we return a zero, otherwise we perform the
division and return the result to the SUM operation.
Example 10-8 Avoiding division by zero
SELECT REF_ID
,PAYMT_PAST_DUE_CT
,SUM ( BAL_AMT / PAYMT_PAST_DUE_CT )
FROM PAY_TABLE
GROUP BY REF_ID
,PAYMT_PAST_DUE_CT; --This statement can get a division by zero error
versus
SELECT REF_ID
,PAYMT_PAST_DUE_CT
,SUM (CASE
WHEN PAYMT_PAST_DUE_CT = 0 THEN 0
WHEN PAYMT_PAST_DUE_CT > 0 THEN BAL_AMT / PAYMT_PAST_DUE_CT
END)
FROM PAY_TABLE
GROUP BY REF_ID
,PAYMT_PAST_DUE_CT; --This statement avoids division by zero errors
Following is another example (Example 10-9) that also shows how to use a CASE expression
to avoid division by zero errors. From the TBEMPLOYEE table, find all employees who earn
more than 25 percent of their income from commission, but who are not fully paid on
commission.
Example 10-9 Avoid division by zero, second example
SELECT
EMPNO
,WORKDEPT
,SALARY + COMM
FROM SC246300.TBEMPLOYEE
WHERE
(CASE WHEN SALARY = 0 THEN 0
ELSE COMM/(SALARY + COMM)
END) > 0.25 ;
Example 10-10 shows how to replace many UNION ALL clauses with one CASE expression.
In this example, if the table is not clustered by the column STATUS, DB2 probably does not
use an index to access the data and scan the entire table once for each SELECT. By using
the CASE expression we scan the data only once. This could be a significant performance
improvement depending on the size of the table and the number of rows that are accessed by
the query.
Example 10-10 Replacing several UNION ALL clauses with one CASE expression
SELECT
CLERK
,CUSTKEY
,'CURRENT' AS STATUS
,ORDERPRIORITY
FROM SC246300.TBORDER
WHERE ORDERSTATUS = '0'
UNION ALL
SELECT
CLERK
,CUSTKEY
,'OVER 30' AS STATUS
,ORDERPRIORITY
FROM SC246300.TBORDER
WHERE ORDERSTATUS = '1'
UNION ALL
SELECT
CLERK
,CUSTKEY
,'OVER 60' AS STATUS
,ORDERPRIORITY
FROM SC246300.TBORDER
WHERE ORDERSTATUS = '2' ;
versus
SELECT CLERK
,CUSTKEY
,CASE
WHEN ORDERSTATUS = '0' THEN 'CURRENT'
WHEN ORDERSTATUS = '1' THEN 'OVER 30'
WHEN ORDERSTATUS = '2' THEN 'OVER 60'
END AS STATUS
,ORDERPRIORITY
FROM SC246300.TBORDER ; -- this statement is equivalent to the above
-- UNION ALL but requires only one pass through
-- the data.
CASE expressions can also be used in before triggers to edit and validate input data and
raise an error if the input is not correct. This can simplify programming by taking application
logic out of programs and placing it into the data base management system, reducing the
number of lines of code needed since the code is only in one place. See the trigger example
in Example 3-10 on page 24.
Note: These CASE expressions are stage 2 if coded in a WHERE clause while the
NULLIF and COALESCE are stage 1 predicates.
CASE NULLIF(e1,e2)
WHEN e1=e2 THEN NULL
ELSE e1
END
CASE COALESCE(e1,e2)
WHEN e1 IS NOT NULL THEN e1 or
ELSE e2 VALUE(e1,e2)
END
CASE COALESCE(e1,e2,...,eN)
WHEN e1 IS NOT NULL THEN e1 or
WHEN e2 IS NOT NULL THEN e2 VALUE(e1,e2,...,eN)
WHEN ...
ELSE eN
END
SELECT
EMPNO
, CASE WHEN EDLEVEL < 16 THEN 'DIPLOMA'
WHEN EDLEVEL < 18 THEN 'GRADUATE'
WHEN EDLEVEL < 20 THEN 'POST GRADUATE'
ELSE
RAISE_ERROR ('70001', 'EDUCLVEL HAS VALUE GREATER THAN 20')
END AS EDUCLEVEL
FROM SC246300.TBEMPLOYEE ;
SELECT CUSTKEY
, SUM(CASE
WHEN TOTALPRICE BETWEEN 0 AND 99 THEN 1
ELSE 0
END) AS SMALL
, SUM(CASE
WHEN TOTALPRICE BETWEEN 100 AND 250 THEN 1
ELSE 0
END) AS MEDIUM
, SUM(CASE
WHEN TOTALPRICE > 250 THEN 1
ELSE 0
END) AS LARGE
FROM SC246300.TBORDER
GROUP BY CUSTKEY #
---------+---------+----
CUSTKEY TOTPRICE
---------+---------+----
03 224.
05 30.
03 560.
05 150.
01 50.
03 550.
01 40.
04 40.
02 438.
03 75.
---------+---------+---------+---------+---------+---------+----
CUSTKEY SMALL MEDIUM LARGE
---------+---------+---------+---------+---------+---------+----
01 2 0 0
02 0 0 1
03 1 1 2
04 1 0 0
05 1 1 0
Example 10-13 shows how a CASE statement can be used to group the results of a query
without having to re-type the expression. Using the employee table, find the maximum,
minimum, and average salary. Instead of finding these values for each department, assume
that we want to combine some departments into the same group. Combine departments A00
and E21, and combine departments D11 and E11.
Example 10-13 Use CASE expression for grouping
SELECT CASE_DEPT
SALARY WORKDEPT
70000 A00
60000 A00
50000 A00
36000 B20
40000 B20
44000 B20
42000 D11
54000 D11
45000 E11
30000 E21
40000 F20
51200 F20
80100 F20
40250 J11
50800 J11
Tip: If the best possible access path is not a table space scan and you have CASE
expressions in the WHERE clause, make sure that you also code other indexable and
filtering predicates in the WHERE clause whenever possible.
The result-expressions of a CASE statement (expressions following the THEN and ELSE
keywords) cannot be coded so that all of them are NULL. If you attempt to code a CASE
expression that always returns a NULL result, you receive an SQLCODE -580.
The data type of every result-expressions must be compatible. If the CASE condition result
data types are not compatible (either all character, graphic, numeric, date, time or timestamp)
you receive an SQLCODE -581.
Columns of data types VARCHAR (greater than 255 bytes), VARGRAPHIC (greater than 127
bytes), and LOBs (CLOB, DBCLOB or BLOB) cannot be used anywhere within a CASE
expression. In addition, the only type of user defined function that is allowed in the expression
prior to the first WHEN keyword must be a deterministic UDF and it cannot contain external
actions.
Because a UNION can now be coded everywhere that you could previously code a subselect,
this feature is also called ‘union everywhere’.
We also discuss some ways ‘union everywhere’ can be applied in the physical design of
extremely large tables.
In DB2 V7, the SQL syntax has been enhanced so that you may use a fullselect (a portion of
a select statement that contains a UNION or UNION ALL between subselects) anywhere a
subselect was previously allowed.
Prior to DB2 V7, UNIONs could not be used in a CREATE VIEW, in nested table expressions,
in subqueries, or in INSERT and UPDATE statements. Beginning with DB2 V7, the syntax of
the view definition, nested table expressions, subqueries, inserts and updates has been
enhanced to allow a fullselect clause instead of only a subselect.
Note: Subselect is no longer specified as a component of any statement syntax since the
only difference between a subselect and a fullselect was the ability to use UNION and
UNION ALL, and now you can use a fullselect everywhere only a subselect was previously
allowed.
Performance has also been recognized as being an integral part of the making of this change.
DB2 avoids materializing a view with unions as much as possible by using query rewrite.
Other performance benefits have been included to exploit this enhancement.
The union everywhere implementations enhances the compatibility with other members
within the DB2 UDB family and complies with the SQL99 standard.
The query in Example 11-1 finds the number of male and female customers and employees
in city 1010 and their average age.
Example 11-1 Unions in nested table expressions
SELECT SEX
,AVG(AGE)
,COUNT(*)
,CITYKEY
FROM TABLE (SELECT
SEX
,YEAR(DATE(DAYS(CURRENT DATE)-DAYS(BIRTHDATE))) AS AGE
Unions can now be coded within basic, quantified (ANY, SOME,ALL), EXISTS, and IN
(subquery) predicates. This gives us the ability to merge data from multiple tables and
compare to single column values within a single SQL statement.
SELECT CITYKEY
,CITYNAME
FROM SC246300.TBCITIES
WHERE CITYKEY =
(SELECT CITYKEY
FROM SC246300.TBCUSTOMER
WHERE PHONENO = :PHONENUM
UNION
SELECT CITYKEY
FROM SC246300.TBCUSTOMER_ARCH
WHERE PHONENO = :PHONENUM
) ;
--Prior to DB2 V7
--In DB2 V7
SELECT CITYKEY
,CITYNAME
FROM SC246300.TBCITIES A
WHERE CITYKEY = SOME
(SELECT CITYKEY
FROM SC246300.TBCUSTOMER
UNION
SELECT CITYKEY
FROM SC246300.TBEMPLOYEE ) ;
Note: The V7 query is coded with a UNION and not a UNION ALL. In this case, because of
the =SOME predicate, DB2 converts the UNION into a UNION ALL. This shows up in the
explain output. There is no sorting of the result of the UNION operation.
--Prior to DB2 V7
SELECT CITYKEY
,CITYNAME
FROM SC246300.TBCITIES A
WHERE EXISTS
(SELECT ‘DUMMY’
FROM SC246300.TBCUSTOMER
WHERE CITYKEY=A.CITYKEY)
OR EXISTS
(SELECT ‘DUMMY’
FROM SC246300.TBEMPLOYEE
WHERE CITYKEY=A.CITYKEY) ;
--In DB2 V7
SELECT CITYKEY
,CITYNAME
FROM SC246300.TBCITIES A
WHERE EXISTS
(SELECT ‘DUMMY’
FROM SC246300.TBCUSTOMER
--Prior to DB2 V7
SELECT CITYKEY
,CITYNAME
FROM SC246300.TBCITIES A
WHERE CITYKEY IN (SELECT CITYKEY
FROM SC246300.TBCUSTOMER)
OR CITYKEY IN (SELECT CITYKEY
FROM SC246300.TBEMPLOYEE) ;
--In DB2 V7
SELECT CITYKEY
,CITYNAME
FROM SC246300.TBCITIES A
WHERE CITYKEY IN
(SELECT CITYKEY
FROM SC246300.TBCUSTOMER
UNION ALL
SELECT CITYKEY
FROM SC246300.TBEMPLOYEE ) ;
UPDATE SC246300.INVITATION_CARDS X
SET STATUS = (
SELECT 'L'
FROM SC246300.TBEMPLOYEE Y
WHERE
CITYKEY = 22 AND
Y.PHONENO = X.PHONENO AND
Y.BIRTHDATE = X.BIRTHDATE
UNION ALL
SELECT 'L'
FROM SC246300.TBCUSTOMER Z
WHERE
CITYKEY = 22 AND
Z.PHONENO = X.PHONENO AND
Z.BIRTHDATE = X.BIRTHDATE
) ;
Before UNIONs were allowed in views, there were only two options to perform the equivalent
function:
The first option was to create a physical table containing the merged data from the
multiple tables. This required some work, you needed to unload the multiple tables and
load into the single table periodically. This meant that there was the potential for the data
to be inaccurate, since the actual tables were not directly used for the user’s queries.
The second option was for the user code UNIONs without the use of a view. However,
coding the union is a bit more complex, and had to be coded again for every query
wanting to work with the same data. Additionally, functions (such as AVERAGE, COUNT,
and SUM) could not range across the union, further complicating the process. These
functions could be accomplished through additional steps, for example, by creating a
temporary table containing the output of the SELECT statement with the union clause, and
then run another SELECT to perform the column function against the temporary table.
DB2 V7 provides a simple answer for this problem. Data from the base tables can be merged
dynamically by creating a view using unions. Once coded, the user only has to refer to the
view to have access to data across several tables and can now easily use the full suite of
functions, such as COUNT, AVG, and SUM across all the data.
To get a combined list of employees and customers, we can create a view described in
Example 11-8.
Example 11-8 Create view with UNION ALL
Example 11-9 shows a query to get the average age of employees and customers under 35
years old and how many of them there are.
SELECT AVG(AGE),COUNT(*)
FROM SC246300.CUSTOMRANDEMPLOYEE
WHERE AGE < 35
As you can see, the addition of unions in the CREATE VIEW statement simplifies the merging
of data from tables for end user queries and allows the use of the full suite of DB2 functions
against the data without the need of temporary tables or complex SQL.
T Table
W Workfile
UNION UNION
In Example 11-10 we show the partial output from the PLAN_TABLE for the explain of the
query in Example 11-6 on page 139. This EXPLAIN output shows that query block 3 and 4
(QBLOCKNO 3 and 4) have a parent query block 2 (PARENT_QBLOCKNO 2). If you see the
the query processing as a tree, the outer select is the root (PARENT_QBLOCKNO 0). At the
next level you find a union (PARENT_QBLOCKNO 1) which has 2 fullselects and each of
them has PARENT_QBLOCKNO 2.
---------+---------+---------+---------+---------+---------+---------+---------+-----
QBLOCKNO TNAME ACCESSTYPE QBLOCK_TYPE PARENT_QBLOCKNO TABLE_TYPE
---------+---------+---------+---------+---------+---------+---------+---------+-----
1 INVITATION_CARDS INSERT 0 T
Splitting a table horizontally into multiple tables is never an easy decision. Dividing is always
more or less visible for applications. Using union in view, you can hide part of the change from
the applications. This way the programs do not see any changes.
Impact on response times depends on the way the view is coded as well as the queries that
are written against the view.
If the WHERE predicates reference the columns that were used for dividing up the tables, and
these columns are compared with a constant(s) or host variables, only the table(s) having
qualifying rows are accessed, and response time should be more or less the same as with a
single large table. (This technique of limiting the number of tables that are accessed is called
subquery pruning.)
When host variables are used for those columns, DB2, at bind time, cannot know which
table(s) have to be accessed to fetch the rows from. Therefore, at bind time, the optimizer has
to choose an access path that accesses every table. However, at execution time when the
actual host variables are known, DB2 can do subquery pruning and accesses only the tables
that can have qualifying rows. The subquery pruning for host variables always takes place
and is not dependent on the use of the REOPT(VARS) bind option.
For more information about how the access path is determined and the different ways DB2
tries to optimize the execution of queries referencing views containing unions, like the
distribution of predicates, joins, and aggregations, as well as the pruning of subqueries, table
elimination and predicate distribution, see DB2 for z/OS and OS/390 Performance Topics,
SG24-6129.
Note: Each individual query block that is part of the union everywhere query can run in
parallel. However, multiple query blocks of the same query cannot run in parallel.
Sometimes a better access path can be obtained using CASE expressions. See Chapter 10,
“SQL CASE expressions” on page 125 for more details. With CASE expressions you may
save excessive access to tables with no qualifying rows.
Because views containing a UNION operator are read only, inserting, updating and deleting
cannot be hidden from the programs by using a view.
This way you can be sure that although DB2 cannot verify the data integrity by design, there
is a way to guarantee that rows are only inserted or changed in the correct tables. Anyhow,
programs still have to know in which table to insert, update or delete. Special processing is
also required in case the value in the column that was used to assign rows to different tables
is changed and the row now has to move to a different table. This type of manipulation
requires a delete from the original table and an insert into the new table, operations similar to
updating a column that is part of the partitioning key, before the introduction of the partitioning
key update feature.
Because of the complexity of handling inserts, updates and deletes correctly, this type of
design is probably most beneficial in a (mostly) read only environment, like data warehouses.
Loading the data is not a problem. DB2 can load many tables in one run. You only need to
specify the exact rules how to select the correct table to which the data belongs. (That rule is
our splitting criteria.)
Splitting tables is not relevant just because we can do it. Partitioning a table must always be
the first choice for large tables. Usually partitioning the data is sufficient to solve most
availability and manageability issues. DB2 offers more possibilities (more partitions, more
parallelism, more flexibility) for partitioned tables in each new release. Dividing tables should
be a last resort when nothing else is good enough. Note also that the ‘divided tables’ can
themselves be partitioned as well.
Let’s try to explain the concept using an example. Assume that our TBORDER table is getting
to large and we decide to go for the UNION in view design. Suppose that the application logic
uses some sort of randomizing formula to assign ORDERKEYs randomly taking into account
that ORDERKEY is an integer column. We decide to split the TBORDER table into 3 separate
tables, TBORDER_1, TBORDER_2 and TBORDER_3. Because of the randomizer, each
table receive more or less the same number of rows. Sample DDL is shown in
Example 11-11.
Example 11-11 DDL to create split tables
Create a view, as shown in Example 11-12, that is used to retrieve rows from any of the 3
tables. This way it is transparent to the user in which physical table the data is stored. The
user only uses this view to retrieve the data. the WHERE clauses are extremely important
and don’t serve just as documentation to show the ORDERKEY range of each table. This
information is used by the optimizer to eliminate the scanning of certain partitions.
Example 11-12 DDL to create UNION in view
When the user executes the query in Example 11-13, DB2 is smart enough, based on the
information in your query and the information in the view definition, to determine that the data
can only come from TBORDER_1 and there is no need to look at any row in TBORDER_2
and TBORDER_3.
However, keep in mind that DB2, although the individual subqueries against each table can
run in parallel, the first subquery has to complete before the next one starts. (DB2 has to
finish selecting from TBORDER_1 before he starts working on TBORDER_2.)
When doing an insert, update or delete against the set of tables, you should use one of the
following views shown in Example 11-14. The use of the WITH CHECK OPTION prevents you
from inserting in the wrong table. An example of an insert using a wrong view is shown in
Example 11-15. This is very important because the view that is used to retrieve rows, restricts
the data that can be retrieved from each table. If, by accident, a row with an ORDERKEY of
555 would end up into table TBORDER_2, you would not be able to retrieve it using the
VWORDER view. The WHERE clause associated with TBORDER_2 in the VWORDER view
eliminates ORDERKEY from the result set.
Example 11-14 Views to use for UPDATE and DELETE
UNION ALL
This type of view is not all that helpful when updating or deleting rows. However, you receive
an SQLCODE +100 when you try to run the statement using the wrong view. This should be a
signal that you might be using the wrong table, but is of course no guarantee that this is
actually the case. It is also possible that the row you are trying to update or delete does not
exist in the table. In trying also to close this loophole, at some extra cost, you could select the
row first using the VWORDER view before trying to update or delete it using the correct
VWORDER_xUPD view.
Note also that when your TBORDER tables need to be the parent table in an RI relationship,
this construct cannot be used. A foreign key cannot point to a set of tables (our TBORDER_x
tables).
In summary, although splitting very large tables into several smaller ones and using the union
in view concept to make it transparent to the application has some attractive features, there
are also several drawbacks that have to be carefully considered before implementing this
design.
DB2 also can, if desired, maintain the relationship between the rows in the result set and the
data in the base table. That is, the scrollable cursor function allows the changes made outside
the opened cursor, to be reflected. For example, if the currently fetched row has been
updated while being processed by the user, and an update is attempted, a warning is
returned by DB2 to reflect this. When another user has deleted the row currently fetched, DB2
returns an SQLCODE if an attempt is made to update the deleted row.
With non-scrollable cursors, if we want to move backwards through a result set, we have a
number of options. One option is to declare 2 cursors, one which uses an ascending index,
and one which uses a descending index. This requires additional program logic and
complexity since two cursors are needed. Also additional resources are consumed since a
second index is needed.
Another option is to CLOSE and reopen the current cursor to start at the beginning, then
repeat the FETCH until the desired row is reached. This can have a large negative impacted
on response times for large result sets. Many rows may be read unnecessarily on the way to
the target row. Also, if another process inserts or deletes rows, the relative position of the row
can change and may cause the wrong row to be accessed. Program logic has to be built to
either ignore or somehow handle the changes in row positions.
Yet another alternative is to cache the results in working storage. The result set can be
opened and an arbitrary number of the rows can be read into an array. The program may,
then move backward and forward within this array. This option needs to be carefully planned,
as it often wastes memory through low utilization of the space, or it restricts the number of
rows returned to an arbitrary number. If other processes are changing the data, the program
is insensitive to the changes.
With scrollable cursors, DB2 provides the ability to scroll in any direction and even the ability
to skip around within the result table. This can greatly reduce program complexity by
simplifying logic. Another benefit of scrollable cursors (compared to the case where you need
to have multiple indexes or sort the result table in order to scroll backward) is that they can
avoid some sorts and reduce the need for extra indexes on your tables. Also, since a
scrollable cursor always materializes the result table, an insensitive scrollable cursor may be
a good way to build a point in time result table that your program can process without having
to lock the base table. Scrollable cursors are also ideal for remote applications that build
screens and allow end users to scroll or skip around through the data being displayed.
Non-scrollable cursor
Used by an application program to retrieve a set of rows or retrieve a result set from a
stored procedure.
The rows must be processed one at a time.
The rows are fetched sequentially.
Result sets may be stored in a workfile.
Scrollable cursor
Used by an application program to retrieve a set of rows or retrieve a result set from a
stored procedure.
The rows can be fetched in random order.
The rows can be fetched forward or backward.
The rows can be fetched relative to the current position or from the top of the result table
or result set.
The result set is fixed at OPEN CURSOR time.
Result sets are stored in declared temporary tables.
Result sets go away at CLOSE CURSOR time.
DB2 V7 introduces new keywords INSENSITIVE and SENSITIVE STATIC for the DECLARE
CURSOR statement to control whether the data in the result set is validated against the data
in the base table. DB2 now ensures that only the current values of the base table are updated
and recognizes where rows have been deleted from the result set. It can, if required, refresh
the rows in the result set at fetch time to ensure that the data under the cursor is current.
Basically, INSENSITIVE means that the cursor is read-only and is not interested in changes
made to the base data once the cursor is opened. With SENSITIVE, the cursor is interested
in changes which may be made after the cursor is opened. The levels of this awareness are
dictated by the combination of SENSITIVE STATIC in the DECLARE CURSOR statement and
whether INSENSITIVE or SENSITIVE is defined in the FETCH statement.
Tip: INSENSITIVE cursors are strictly read-only. SELECT statements using with FOR
UPDATE OF must use SENSITIVE STATIC cursors in order to be updatable scrollable
cursors.
When creating a scrolling cursor the INSENSITIVE or SENSITIVE STATIC, keywords must be
used in the DECLARE CURSOR statement. This sets the default behavior of the cursor.
Insensitive
If an attempt is made to code the FOR UPDATE OF clause in a cursor defined as
INSENSITIVE, then the bind returns an SQLCODE:
-228 FOR UPDATE CLAUSE SPECIFIED FOR READ-ONLY SCROLLABE CURSOR USING
cursor-name.
The characteristics of an insensitive scrollable cursor are:
– The cursor cannot be used to issue positioned updates and deletes.
– FETCH processing on the result table is insensitive to changes made to the base table
after the result table is built (even if changes are made by the current agent outside the
cursor).
– The number and content of the rows stored in the result table is fixed at OPEN
CURSOR time and does not change.
Sensitive
Fundamentally, the SENSITIVE STATIC cursor is updatable. As such, the FOR UPDATE
OF clause can be coded for a SENSITIVE cursor.
If the SELECT statement connected to a cursor declared as SENSITIVE STATIC uses any
keywords that forces the cursor to be read-only, the bind rejects the cursor declaration. In
this case the bind returns an SQLCODE:
Important: Use of column functions, such as MAX and AVG, and table joins forces a
scrollable cursor into implicit read-only mode and therefore are not valid for a SENSITIVE
cursor. In contrast with non-scrollable cursors, the usage of the ORDER BY clause in a
scrollable cursor does not make it read-only.
A SENSITIVE cursor can be made explicitly read-only by including FOR FETCH ONLY in
the DECLARE CURSOR statement. Even if a SENSITIVE cursor is read-only, it is still
aware of all changes made to the base table data through updates and deletes.
The characteristics of a sensitive scrollable cursor are:
– The cursor can be used to issue positioned updates and deletes
– FETCH processing on the result table is sensitive (to varying degrees) to changes
made to the base table after the result table has been built
– The number of rows in the result table does not change but the row content can
change
– STATIC cursors are insensitive to inserts.
Furthermore, sensitivity can be limited to visibility of changes made by the same cursor and
process that is fetching rows or the sensitivity can be extended to also see updates made
outside the cursor and process by refreshing the row explicitly.
Table 12-1 can be used to help you decide which type of cursor to use. If you just want to blast
through your data then choose a forward-only cursor. If you want to scroll through a constant
copy of your data you may want to use an INSENSITIVE CURSOR instead of making a
regular cursor materialize the result table. If you want to control the cursor's position, scroll
back and forth, choose a scrollable cursor. If you don't care about the freshness of data,
choose an INSENSITIVE CURSOR. If you want fresh data some times, choose a SENSITIVE
on DECLARE CURSOR and SENSITIVE or INSENSITIVE on FETCH. If you want fresh data
all the time, choose a SENSITIVE CURSOR and SENSITIVE on FETCH or non specific
FETCH.
Table 12-1 Cursor type comparison
Cursor type Result table Visibility of Visibility of Updatability
own cursor’s other
changes cursors’
changes
**Note: Sensitive Dynamic scrollable cursors are not available as of DB2 V7 and are only
shown here for comparison purposes.
Scrollable cursors can be used in CICS conversational programs and batch processing using
TSO, batch, CAF, RRSAF, a background CICS task, DL/1 batch, and IMS BMP. They do not
apply to CICS pseudo-conversational and IMS/TM transactions because resources are freed
after displaying a screen (including declared temporary tables that scrollable cursors use
under the cover).
The new keywords INSENSITIVE and SENSITIVE STATIC of the DECLARE CURSOR
statement deal with the sensitivity of the cursor to changes made to the underlying table.
The STATIC keyword in the context of this clause does not refer to static and dynamic SQL,
as scrollable cursors can be used in both these types of SQL. Here, STATIC refers to the size
of the result table, once the OPEN CURSOR is completed, the number of rows remains
constant.
In Example 12-1, we show you how to DECLARE two scrollable cursors, one INSENSITIVE
and the other SENSITIVE STATIC
Example 12-1 Sample DECLARE for scrollable cursors
The temporary table that is created is only accessible by the agent that created it. For each
user and for each scrollable cursor a temporary table is created at OPEN CURSOR time. The
temporary table is dropped when you close the cursor or at the completion of the program
that invoked it. For more information on declared temporary tables, see “Declared temporary
tables” on page 88.
Note: DECLARE CURSOR statements that do not use the new keyword SCROLL do not
create the temporary table and are only able to scroll in a forward direction.
Once the result set has been retrieved, it is only visible to the current cursor process and
remains available until a CLOSE CURSOR is executed or the process itself completes. For
programs, the result set is dropped on exit of the current program; for stored procedures, the
cursors defined are allocated from the calling program, and the result set is dropped when the
calling program concludes.
Example 12-2 Opening a scrollable cursor
If a LOB column is selected in the DECLARE CURSOR statement, the LOB column is
represented by a LOB descriptor column in the result table. The LOB descriptor column is
120 bytes and holds information which enables DB2 to quickly retrieve the associated LOB
column value from the auxiliary table when the application fetches a row from the result table.
DB2 has to retrieve the LOB column value (from the auxiliary table) when it processes either
a sensitive or an insensitive FETCH request.
Suppose an application issues a ROLLBACK TO SAVEPOINT S1, and savepoint S1 was set
before scrollable cursor C1 was opened. Once the rollback has completed, the result table
for C1 contains the same data as it did on completion of the open cursor statement for C1. In
addition, the rollback does not change the position of cursor C1.
The OPEN CURSOR and ALLOCATE CURSOR statement return the following information in
the SQLCA for scrollable cursors regarding the sensitivity of the cursor even though the
SQLCODE and SQLSTATE are zero:
When SQLWARN1, SQLWARN4 and SQLWARN5 are set, then SQLWARN0 (the summary
flag) is NOT set for these cases.
Note: If the size of the result table exceeds the DB2 established limit of declared
temporary tables, a resource unavailable message, DSNT501I, is generated with the
appropriate resource reason code at OPEN CURSOR time.
Figure 12-1 shows the syntax diagram for the FETCH statement. The syntax has been
expanded (items in bold) to allow this statement to control cursor movement both forwards
and backwards. It also has keywords to position the cursor in specific positions within the
result set returned by the OPEN CURSOR statement.
Tip: The BEFORE and AFTER clauses are positioning orientation, which means that no
data is returned and an SQLCODE = 0 is returned to the application.
FROM
cursor-name
single-fetch-clause
single-fetch-clause
,
INTO host-variable
USING DESCRIPTOR descriptor-name
Below is a complete list of the new keywords which have been added to the FETCH
statement syntax for moving the cursor.
NEXT Positions the cursor on the next row of the result table relative to the
current cursor position and fetches the row - This is the default.
PRIOR Positions the cursor on the previous row of the result table relative to the
current position and fetches the row.
FIRST Positions the cursor on the first row of the result table and fetches the
row.
LAST Positions the cursor on the last row of the result table and fetches the
row.
CURRENT Fetches the current row without changing position within the result
table.
If CURRENT is specified and the cursor is not positioned at a valid row
(for example, BEFORE the beginning of the result table) a warning
SQLCODE +231, SQLSTATE 02000 is returned.
BEFORE Positions the cursor before the first row of the result table.
No output host variables can be coded with this keyword as no data can
be returned
AFTER Positions the cursor after the last row of the result table
No output host variables can be coded with this keyword, as no data can
be returned.
If a relative position is specified that is before the first row or after the
last row, a warning SQLCODE +100, SQLSTATE 02000 is returned, and
the cursor is positioned either before the first row or after the last row
and no data is returned .
In Figure 12-2 on page 167, we show you the results of various fetches when the cursor is
currently positioned on the 10th row. In Figure 12-3 on page 168, we show you a matrix of the
possible SQLCODEs returned after a FETCH from a scrollable cursor.
Tip: You want to specify FETCH SENSITIVE when you want DB2 to check if the
underlying data has changed since you last retrieved it from the base table and you intend
to update or delete the row or you simply need the latest data. See “Maintaining updates”
on page 174 for more details.
In SENSITIVE STATIC scrollable cursors, the number of rows in the result table is fixed but
deletes and updates to the underlying table can create delete holes or update holes. In
“Update and delete holes” on page 170 we discuss in detail how these holes are created.
Let’s take a closer look at the allowable combinations that can be used and the
characteristics of their attributes.
DB2 does not allow RIDs to be re-used in the base table space when a sensitive cursor is
open. If DB2 allowed RIDs to be re-used, then it would be possible for the following
sequence of events to occur:
1. Another application could delete a base table row and then insert a new row at the same
RID location.
2. A FETCH SENSITIVE request would retrieve the new row but this new row would not
correspond to the result table row established at open cursor time.
Suppose you have defined and opened the following scrollable cursor:
DECLARE C1 SENSITIVE STATIC SCROLL CURSOR
FOR SELECT ....
FROM BASE_TABLE
WHERE ....
FOR UPDATE OF ....
DB2 attempts to retrieve the corresponding row from the base table. If the row is not found,
DB2 marks the result table row as a delete hole and returns SQLCODE +222. If row is found,
DB2 checks to see if the base table row still satisfies the search condition specified in the
DECLARE CURSOR statement. If the row no longer satisfies the search condition, DB2
marks the result table row as an update hole and returns SQLCODE +222. If the row still
satisfies the search condition, DB2 refreshes the result table row with the column values from
the base table row and returns the result table row to the application.
Once a row in the result table is marked as a delete hole, then that row remains a delete hole.
In Example 12-3, we show a FETCH SENSITIVE request which creates an update hole in the
result table. The update hole is created because the base table row no longer satisfies the
search condition WHERE TXNID = 'SMITH'.
Example 12-3 Example of a FETCH SENSITIVE request which creates an update hole
Sequence of events:
1. Cursor C1 is opened and the result table is built.
2. Another application updates the column value of TXNID of the row with
RID A0D to “SMYTHE”.
3. A FETCH SENSITIVE is invoked that positions the cursor on the row
with RID A0D.
4. DB2 checks the base table row (using its RID), since the row no longer
satisfies the search condition WHERE TXNID = 'SMITH', DB2 changes
the current row in the result table to an update hole, and returns
an SQLCODE +222.
Cursor movement
The RELATIVE and ABSOLUTE keywords can be followed by either an integer constant or a
host variable which contains the value to be used.
The INTO clause specifies the host variable(s) into which the row data should be fetched.
This clause must be specified for all FETCH requests except for a FETCH BEFORE and a
FETCH AFTER request. An alternative to the INTO clause is the INTO DESCRIPTOR clause.
If the RELATIVE or ABSOLUTE keyword is followed by the name of a host variable, then the
named host variable must be declared with a data type of INTEGER or DECIMAL(n,0). Data
type DECIMAL(n,0) only has to be used if a number specified is beyond the range of an
integer (-2147483648 to +2147483647). If the number of the row to be fetched is specified via
a constant, then the constant must be an integer. For example, 7 is valid, 7.0 is not valid.
Absolute moves
An absolute move is one where the cursor position is moved to an absolute position within the
result table. For example, if a program wants to retrieve the fourth row of a table, the FETCH
statement would be coded as:
EXEC SQL
FETCH ... ABSOLUTE +4 FROM CUR1 INTO ...
END-EXEC.
or
Another form of the absolute move is through the use of keywords which represent fixed
positions within the result set. For example, to move to the first row of a result set, the
following FETCH statement can be coded:
There are also two special absolute keywords which allow for the cursor to be positioned
outside the result set. The keyword BEFORE is used to move the cursor before the first row of
the result set and AFTER is used to move to the position after the last row in the result set.
Host variables cannot be coded with these keywords as they can never return values.
A FETCH ABSOLUTE 0 request and a FETCH BEFORE request both position the cursor
before the first row in the result table. DB2 returns SQLCODE +100 for FETCH ABSOLUTE
0 requests (that is, no data is returned) and SQLCODE 0 for FETCH BEFORE requests.
A FETCH ABSOLUTE -1 request is equivalent to a FETCH LAST request, that is, both
requests fetch the last row in the result table. DB2 returns SQLCODE 0 for both of these
requests.
Tip: A FETCH BEFORE and a FETCH AFTER request only position the cursor, DB2
does not return any row data. The SENSITIVE and INSENSITIVE keywords cannot be
used if BEFORE or AFTER are specified on the FETCH statement.
Relative moves
A relative move is one made with reference to the current cursor position. To code a
statement which moves three rows back from the current cursor, the statement would be:
FETCH ... RELATIVE -3 FROM CUR1 INTO ...;
or
MOVE -3 TO CURSOR-MOVE.
FETCH ... RELATIVE :CURSOR-MOVE FROM CUR1 INTO ... ;
If you attempt to make a relative jump which positions you either before the first row or after
the last row of the result set, an SQLCODE of +100 is returned. In this case the cursor is
positioned just before the first row, if the jump was backwards through the result set; or just
after the last row, if the jump was forward within the result set.
The keywords CURRENT, NEXT, and PRIOR make fixed moves relative to the current cursor
position. For example, to move to the next row, the FETCH statement would be coded as:
FETCH ... NEXT FROM CUR1 ;
or
FETCH ... FROM CUR1 ;
Please refer to the DB2 UDB for OS/390 and z/OS Version 7 SQL Reference, SC26-9944, for
a complete list of synonymous scroll specifications for ABSOLUTE and RELATIVE moves
inside a scrollable cursor.
In Example 12-4 we show you sample program logic to display the last five rows from a table.
Example 12-4 Scrolling through the last five rows of a table
FETCH INSENSITIVE ABSOLUTE -6 -- Position us on the 6th row from the bottom
FROM CUR1 INTO :HV1 -- of the result table
,:HV2 ;
DO I = 1 TO 5
FETCH NEXT FROM CUR1 INTO :HV1
,:HV2 ;
END
Some examples of the full syntax are shown in Example 12-5. In this example, first we fetch
the 20th row from the result table. Then we position the cursor to the beginning of the result
table (no data is returned). Then we scroll down to the “NEXT” row (in this case the first row in
the result table). Then we scroll forward 10 rows. After that we scroll forward an additional
:rownumhv rows. Then we position the cursor at the end of the result table (no data is
returned). Then we scroll backwards 4 rows from the bottom of the result table, and finally we
scroll forward to the next row from there.
Example 12-5 Several FETCH SENSITIVE statements
In Figure 12-2 we show the effects of different FETCH requests when the cursor is currently
positioned on row number 10. NEXT is the default for a FETCH request.
Refer to Figure 12-3 for a list of cursor positioning values and the possible SQLCODEs that
may be returned.
Column functions such as MAX and AVG causes a scrollable cursor into implicit read-only
mode and are not valid for a SENSITIVE STATIC cursor.
The basic rule for using column functions in a scrollable cursor is that if the column function is
part of the predicate, you can use it in an insensitive cursor, as the one shown in
Example 12-6.
Example 12-6 Using functions in a scrollable cursor
In Example 12-7, we can see an expression (COMM + BONUS) and a column function
(AVG(SALARY)) being used in an insensitive scrollable cursor. Here, the column function and
the expression are evaluated when the cursor is opened and the results are saved by DB2.
Example 12-7 Using functions in an insensitive scrollable cursor
Scalar functions, UDFs, and expressions are re-evaluated using the base table row when a
FETCH SENSITIVE request is processed. A positioned update or delete compares the
column value in the result table with the re-evaluated value for the base table row.
External UDFs are executed for each qualifying row when a scrollable cursor is opened.
Therefore, if an external UDF sends an e-mail then an e-mail is sent for each qualifying row.
In Example 12-10 we show the scalar function SUBSTR. If the cursor is SENSITIVE, the
function is evaluated at FETCH time. If the cursor is INSENSITIVE, the function is evaluated
at OPEN CURSOR time.
Example 12-10 Scalar functions in a cursor
In Example 12-11 shows that you can also use an expression in a scrollable cursor. In this
example we use a sensitive scrollable cursor. Therefore, the expression will be re-evaluated
against the base table at each FETCH operation to make sure the rows still qualifies.
Example 12-11 Expression in a sensitive scrollable cursor
It is important to note that if an INSENSITIVE fetch is used, then only update and delete holes
created under the current open cursor are recognized. Updates and deletes made by other
processes or outside the cursor are not recognized by the INSENSITIVE fetch.
Base Table
ACCOUNT ACCOUNT_NAME TYPE
ABC010 BIG PETROLEUM C
BWH450 RUTH & DAUGHTERS C
ZXY930 MIGHTY DUCKS PLC C
MNP230 BASEL FERRARI P
BMP291 MR R GARCIA C
XPM673 SCREAM SAVER LTD C
ULP231 MS S FLYNN P
XPM961 MR CJ MUNSON C
Result table
RID ACCOUNT ACCOUNT_NAME
A04 MNP230 MR BASEL FERRARI
A07 ULP231 MS S FLYNN
It is important to note that if an INSENSITIVE fetch is used, then only update and delete holes
created under the current open cursor are recognized. Updates and deletes made by other
processes are not recognized by the INSENSITIVE fetch.
If the above SENSITIVE fetch was replaced with an INSENSITIVE fetch, the fetch would
return a zero SQLCODE, as the update to the base row was made by another process. The
column values would be set to those at the time of the OPEN CURSOR statement execution.
Example 12-13 Update holes
Base Table
ACCOUNT ACCOUNT_NAME TYPE
ABC010 BIG PETROLEUM C
BWH450 RUTH & DAUGHTERS C
ZXY930 MIGHTY DUCKS PLC C
MNP230 BASEL FERRARI P
BMP291 MR R GARCIA C
XPM673 SCREAM SAVER LTD C
ULP231 MS S FLYNN P
XPM961 MR CJ MUNSON C
Result table
RID ACCOUNT ACCOUNT_NAME
A04 MNP230 BASEL FERRARI
A07 ULP231 MS S FLYNN
DB2 now maintains a relationship between the rows returned by a SENSITIVE STATIC
scrolling cursor and those in the base table. If an attempt is made to UPDATE or DELETE the
currently fetched row, DB2 goes to the base table, using the RID, and verifies that the
columns match by value. If columns are found to have been updated, then DB2 returns the
SQL code:
-224: THE RESULT TABLE DOES NOT AGREE WITH THE BASE TABLE USING cursor-name.
When you receive this return code, you can choose to fetch the new data again by using the
FETCH CURRENT to retrieve the new values. The program can then choose to reapply the
changes or not.
Important: DB2 only validates the columns listed in the select clause of the cursor against
the base table. Other changed columns do not cause the SQLCODE -224 to be issued.
Note: A scrollable cursor never sees rows that have been inserted to the base table nor
rows that are updated and now (after open cursor time) fit the selection criteria of the
DECLARE CURSOR statement.
Let's assume isolation level CS or UR is in effect for the SENSITIVE STATIC scrollable
cursor. The application issues a FETCH SENSITIVE request which positions the cursor on a
row. Once the FETCH has completed, DB2 releases the lock (if held) on the base table row.
DB2 does this to improve concurrency. The application now decides to update the current
row. However, the base table row could have been updated or deleted by another
application between the time it was fetched and the time the positioned update is requested.
Therefore, DB2 must validate the base table row before allowing the update to proceed.
The Optimistic Locking Concur By Value technique is used by DB2 to validate a positioned
UPDATE. Lets take a look at how this process works. Suppose that we have executed the
following statements:
DECLARE C1 SENSITIVE STATIC SCROLL CURSOR
FOR SELECT ....
FROM BASE_TABLE
WHERE ....
FOR UPDATE OF ....
OPEN CURSOR C1
FETCH SENSITIVE
UPDATE BASE_TABLE
SET ... = ...,
... = ...
WHERE CURRENT OF C1
The flow chart in Figure 12-4 shows the sequence of events that occurs.
Yes
Does row
still satisfy
search No Update hole detected
condition of Returns SQLCODE - 222
declare
cursor?
Yes
Are column
values of No Intervening update detected
base table
and result Returns SQLCODE - 224
table equal?
Yes
The optimistic locking concur by value technique is also used by DB2 to validate a positioned
delete. Lets take a look at how this process works. Suppose that we have executed the
following statements:
DECLARE C1 SENSITIVE STATIC SCROLL CURSOR
FOR SELECT ....
FROM BASE_TABLE
WHERE ....
OPEN CURSOR C1
FETCH SENSITIVE
The flow chart in Figure 12-5 shows the sequence of events that occurs.
Yes
Does row
still satisfy
search No Update hole detected
condition of Returns SQLCODE - 222
declare
cursor?
Yes
Are column
values of No Intervening update detected
base table
Returns SQLCODE - 224
and result
table equal?
Yes
DB2 deletes the base row
and marks the results
table row as a delete hole
Returns SQLCODE 0
For example, during open of a cursor that requires a temporary result table:
A scrollable cursor bound with isolation level RR (Repeatable Read) keeps the lock on
every page or row it reads whether or not the row is qualified for the select.
A scrollable cursor bound with isolation level RS (Read Stability) keeps the lock on every
page or row that is qualified based on the stage 1 predicates.
A scrollable cursor bound with isolation level CS (Cursor Stability):
– When reading rows during execution of the OPEN CURSOR statement with the
CURRENTDATA setting of YES, takes a lock on the last page or row read.
– If CURRENTDATA is set to NO, does not take locks except where the cursor has been
declared with the FOR UPDATE OF clause or lock avoidance was not able to avoid
Application programs can leverage the use of SENSITIVE STATIC scrollable cursors in
combination with the Isolation level CS and the SENSITIVE option of FETCH to minimize
concurrency problems and assure currency of data when required. The STATIC cursor does
give the application a constant result table to scroll on, thus perhaps eliminating the need for
isolation level RR and RS. The SENSITIVE option of FETCH statement provides the
application a means of re-fetching any preselected row requesting the most current data
when desired. For example, when the application is ready to update a row.
Duration of locks
Locks acquired for positioned updates, positioned deletes, or to provide isolation level RR or
isolation level RS are held until commit. If the cursor is defined WITH HOLD, then the locks
are held until the first commit after the close of the cursor.
Figure 12-6 shows an example of how to use a scrollable cursor in a stored procedure using
result sets. A full listing is available in the additional material. See Appendix C, “Additional
material” on page 251 for more details.
Pro gra m
Note: If a stored procedure issues FETCH statements for a scrollable cursor, then before
ending, it must issue a FETCH BEFORE statement to position the cursor before the first
row in the result table before returning to the calling program.
Provide sufficient TEMP database table space storage to hold the result tables for all
scrollable cursors that might be open concurrently and all user-declared temporary tables that
might be in use concurrently.
Commit as often as is practical and specify WITH HOLD if you want the cursor to remain if the
application issues a commit. At commit, a scrollable cursor is closed and the result table
deleted if WITH HOLD is not specified on the DECLARE CURSOR statement.
You can now include boolean predicates (ANDed, ORed, and NOT) in the ON clause of a
join. These predicates, coded in the ON clause, are applied to the join and are called during
join predicates. The join result is built while all the predicates in the ON clause are being
applied.
For more information on predicate classification, see DB2 UDB for OS/390 Version 6
Performance Topics, SG24-5351.
Note: The ON clause extensions are not implemented for full outer joins. You receive an
SQLCODE -338.
The conditions of the ON clause are join conditions; they specify conditions for the rows to be
joined. The cartesian product is performed with the rows that satisfy the enhanced ON
condition predicate, which is not necessarily only about equality of different columns, but also
connected with predicates on single columns and expressions. You can now include any
search condition just like a WHERE clause, with one exception. Coding a subquery in the ON
clause is not allowed.
Example 13-1 shows the tables and rows that is used in the examples in this section.
Example 13-1 Sample tables and rows
SC246300.TBEMPLOYEE SC246300.TBDEPARTMENT
Example 13-2 shows an inner join with a predicate E.WORKDEPT=’A01’ in the ON clause.
Before this enhancement these predicate could not be coded in the ON clause. Only join
predicates were allowed.
In this particular case, because it is an INNER JOIN and the additional predicate in the ON
clause was ANDed, the query behaves the same way as if the ‘AND E.WORKDEPT = ‘A01’
predicate was coded in the WHERE clause. However, this is not always the case.
Example 13-2 Inner join and ON clause with AND
Example 13-3 show a left outer join with AND in the ON clause on the DEPTNO column. Now
you notice the difference between ANDing the predicate E.WORKDEPT = 'A01' in the ON
clause, and coding the sample predicate in the WHERE clause, as shown in Example 13-4.
Example 13-3 LEFT OUTER JOIN with ANDed predicate on WORKDEPT field in the ON clause
In Example 13-3, in order for the join condition to be satisfied, both conditions have to be true.
If this is not the case, the columns from the right hand table (TBDEPARTMENT) are set to
null.
This is totally different from the case where you code the E.WORKDEPT = ‘A01’ condition in
the WHERE clause, as in Example 13-4. The WHERE clause is not evaluated at the time the
join is performed. According to the semantics of the SQL language It must be evaluated after
the result from the join is built (to eliminate all rows where E.WORKDEPT is not equal to
‘A01’. (Actually this is not entirely true. Version 6 introduced a lot of performance
enhancements related to outer join and in this case, the predicate that is coded in the
WHERE clause can and is evaluated when the data is retrieved from the TBEMPLOYEE
table. The system only moves the evaluation of the predicate to an earlier stage during the
processing, if it is sure the result is not influenced by this transformation. In our example this
is the case because the WHERE predicate is not on the ‘null supplying’ table. For more
information on the outer join performance enhancements, see DB2 UDB for OS/390 Version 6
Performance Topics, SG24-5351.)
Example 13-5 shows another flavour of the ON clause extensions using an OR condition in
the ON clause. MIRIAM and EVA (rows satisfying WORKDEPT = 'A01') are joined with every
row of the TBDEPARTMENT table. The predicates on the ON clause determine whether we
have a match for the join. So in this example, If either E.WORKDEPT = D.DEPTNO or
E.WORKDEPT = ‘A01’ is true, the rows of both tables are matched up.
Example 13-5 Inner join and ON clause with OR in the WORKDEPT column
This is primarily a usability enhancement, but row expressions can impact performance.
Rewriting a query to use row expressions can change the access path used.
The performance impact may be significant if the access path changes when you rewrite the
query to use row expressions. The size of the result set of the outer table may not have much
impact, but the size of the result set of the inner table may have impact performance. If a
large number of rows qualify from the inner table, the increase of response time may be
significant. When the access path does not change with and without row expression, then
there is no performance impact either.
For example, prior to DB2 V7, you would code COL1 = :HV1 AND COL2 = :HV2; now you can
code (COL1,COL2) = (:HV1, :HV2). Both conditions have to be fulfilled for the statement to be
true. You may now code (COL1, COL2) <> (:HV1, :HV2) instead of COL1 <> :HV1 OR COL2
<> :HV2. In this case, at least one condition has to evaluate true for the predicate to be true.
In these simple cases the access path is the same with and without row expression and has
no impact on performance.
Example 13-6 shows how to use a row-value-expression containing two column and compare
(=) it to a row-value-expression that contains a constant value and a host variable. It finds all
employees in city “1” and country “34” (in this case country is a host variable :nationkey).
Example 13-6 Row expressions with equal operation
SELECT FIRSTNME
---------+---------+---------+---------+---------+-
FIRSTNME JOB WORKDEPT SEX EDLEVEL
---------+---------+---------+---------+---------+-
MIRIAM DBA A01 F 4
Note: Row expressions with equal and not equal operators are not allowed to be
compared against fullselect.
Example 13-7 uses a row expression with an = ANY operator against a subquery. This
example lists the employees that live in the same city and country as any of our customers.
Example 13-7 Row expressions with = ANY operation
SELECT FIRSTNME
,JOB
,WORKDEPT
,SEX
,EDLEVEL
FROM SC246300.TBEMPLOYEE
WHERE (CITYKEY,NATIONKEY) = ANY
(SELECT CITYKEY,NATIONKEY
FROM SC246300.TBCUSTOMER ) ;
---------+---------+---------+---------+---------+--
FIRSTNME JOB WORKDEPT SEX EDLEVEL
---------+---------+---------+---------+---------+--
EVA SYSADM A01 F 0
In order for the <>ALL comparison to evaluate to true, there must not be any row on the right
hand side of the comparison that matches the values specified on the left hand side.
Example 13-8 uses a row expression with a <> ALL operator against a subquery. This
example lists the employees that do not live in the same city and country as any of our
customers.
Example 13-8 Row expression with <> ALL operator
SELECT FIRSTNME
,JOB
,WORKDEPT
,SEX
,EDLEVEL
FROM SC246300.TBEMPLOYEE
---------+---------+---------+---------+---------+--
FIRSTNME JOB WORKDEPT SEX EDLEVEL
---------+---------+---------+---------+---------+--
MIRIAM DBA A01 F 4
JUKKA SALESMAN A02 M 7
TONI -------- M 0
GLADYS -------- F 0
ABI TEACHER B01 F 9
Note: The use of row value expressions on the left-hand side of a predicate with = SOME
or = ANY operators is the same as using the IN keyword. The <> ALL operator is the same
as using the NOT IN keywords.
SELECT FIRSTNME
,JOB
,WORKDEPT
,SEX
,EDLEVEL
FROM SC246300.TBEMPLOYEE
WHERE (CITYKEY,NATIONKEY) IN
(SELECT CITYKEY,NATIONKEY
FROM SC246300.TBCUSTOMER ) ;
---------+---------+---------+---------+---------+--
FIRSTNME JOB WORKDEPT SEX EDLEVEL
---------+---------+---------+---------+---------+--
EVA SYSADM A01 F 0
Example 13-10 is equivalent to Example 13-8 and evaluates in the same way.
Example 13-10 NOT IN row expression
SELECT FIRSTNME
,JOB
,WORKDEPT
,SEX
,EDLEVEL
FROM SC246300.TBEMPLOYEE
WHERE (CITYKEY,NATIONKEY) NOT IN
(SELECT CITYKEY,NATIONKEY
FROM SC246300.TBCUSTOMER ) ;
---------+---------+---------+---------+---------+--
FIRSTNME JOB WORKDEPT SEX EDLEVEL
---------+---------+---------+---------+---------+--
MIRIAM DBA A01 F 4
JUKKA SALESMAN A02 M 7
SELECT FIRSTNME,JOB,WORKDEPT,SEX,EDLEVEL
FROM SC246300.TBEMPLOYEE
WHERE (CITYKEY,NATIONKEY) IN (( 1, 34 ), ( 2, 34 ) );
-- SQLCODE = -104
If the number of expressions and the number of columns returned do not match, then an SQL
error is returned:
SQLCODE -216 THE NUMBER OF ELEMENTS ON EACH SIDE OF A PREDICATE OPERATOR DOES
NOT MATCH. PREDICATE OPERATOR IS IN.
SQLSTATE: 428C4
13.3 ORDER BY
The order of the selected rows depends on the sort keys that you identify in the ORDER BY
clause. A sort key can be a column name, an integer that represents the number of a column
in the result table, or an expression. DB2 sorts the rows by the first sort key, followed by the
second sort key, and so on.
You can list the rows in ascending or descending order. Null values appear last in an
ascending sort and first in a descending sort. The ordering can be different for each column in
the ORDER BY clause.
Example 13-12 shows a SELECT that lists all employees in department ‘A00’ sorted by birth
date in ascending order. Notice that the BIRTHDATE column is not in the select list.
Example 13-12 ORDER BY column not in the select list
SELECT EMPNO
,LASTNAME
,HIREDATE
FROM SC246300.TBEMPLOYEE
WHERE WORKDEPT = 'A00'
ORDER BY BIRTHDATE ASC;
Tip: When calculating the amount of sort space required for the query, all columns,
including the ones being sorted on should be included in the sort data length as well as in
the sort key length.
In Example 13-13, we have a query to retrieve the employee number, total compensation
(salary plus commission), salary, commission, for employees with a total compensation
greater than $40000. Prior to V7 we would have to code ORDER BY 2 to produce the same
result.
Example 13-13 ORDER BY expression in SELECT
-- Prior to DB2 V7
SELECT EMPNO
,SALARY+COMM AS "TOTAL COMP" -- Note: this is the second column in the list
,SALARY
,COMM
FROM SC246300.TBEMPLOYEE
WHERE SALARY+COMM > 40000
ORDER BY 2;
-- DB2 V7
SELECT EMPNO
,SALARY+COMM AS "TOTAL COMP"
,SALARY
,COMM
FROM SC246300.TBEMPLOYEE
WHERE SALARY+COMM > 40000
ORDER BY SALARY+COMM ;
-- Or even better
SELECT EMPNO
,SALARY+COMM AS "TOTAL COMP"
,SALARY
,COMM
FROM SC246300.TBEMPLOYEE
WHERE SALARY+COMM > 1000
ORDER BY "TOTAL COMP" ;
Some vendor applications generate SQL statements which contain ORDER BY expressions.
In such cases, it is often not feasible to re-write the generated SQL.
This enhancement is based on the assertion that any constant value included as part of an
ordering key can be logically removed from the ordering key without changing the order. An
ordering key can be the columns listed in an ORDER BY clause or the columns of an index
key.
In Figure 13-1, the predicates for columns C2, C4 and C5 specify a constant value. Therefore,
DB2 can logically remove these columns from the ORDER BY clause without changing the
order requested by the user. DB2 can also logically remove these columns from the index key
without changing the ordering capability of the index.
DB2 is now able to recognize that the index supports the ORDER BY clause
In Example 13-14, we show how this works. The ORDER BY in Figure 13-1 is equivalent to
an ORDER BY C1, C3. Note that index on C2, C1, C5, C4, C3 can be used and is equivalent
to an index on C1, C3 and thus avoiding a sort or the need for an additional index.
Example 13-14 Data showing improved sort avoidance for the ORDER BY clause
C2 C1 C5 C4 C3
-------+------+------+------+------
1 8 7 7 3
1 9 5 4 8
2 4 1 3 2
2 4 2 8 5
3 2 6 9 7
3 4 3 1 3
3 4 4 0 6
4 1 2 3 9
4 7 4 8 0
5 2 2 7 8 -- This row qualifies
5 2 3 8 1
5 3 2 7 5 -- This row qualifies
5 5 3 7 3
5 6 2 5 8
5 6 2 7 7 -- This row qualifies
5 8 2 7 4 -- This row qualifies
5 9 3 7 3
5 9 4 7 1
Qualified rows:
5 2 2 7 8
5 3 2 7 5
5 6 2 7 7
5 8 2 7 4
Note: Logically removing columns from an index key has no effect on the filtering
capability of the index.
13.4 INSERT
In this section, we discuss enhancements made to the INSERT statement.
In Example 13-15, we show how we can code an INSERT to take advantage of this keyword.
Example 13-15 Inserting with the DEFAULT keyword
---------+---------+---------+---------+---------+---------+---------+---------+
ITEM_NUMBER PRODUCT_NAME STOCK PRICE COMMENT
---------+---------+---------+---------+---------+---------+---------+---------+
445 TELEVISION 30 90.00 NONE
Note: This feature requires the subquery to be completely evaluated before any rows are
updated or deleted.
Example 13-18 shows how we can give a 10% salary increase to all employees with a salary
lower than the average salary.
Example 13-18 UPDATE with a self referencing non-correlated subquery
UPDATE SC246300.TBEMPLOYEE
SET SALARY = SALARY * 1.10
WHERE SALARY < (SELECT AVG(SALARY)
FROM SC246300.TBEMPLOYEE )
Example 13-19 shows how we can give a 10% salary increase to all employes whose salary
is lower than the average salary of their department.
Example 13-19 UPDATE with a self referencing non-correlated subquery
UPDATE SC246300.TBEMPLOYEE X
SET SALARY = SALARY * 1.10
WHERE SALARY < (SELECT AVG(SALARY)
FROM SC246300.TBEMPLOYEE Y
WHERE X.WORKDEPT = Y.WORKDEPT) ;
Example 13-20 shows a new way to delete the department with the lowest budget in just one
SQL sentence. However, be careful when coding such a statement because even though only
one value is returned by the self-referencing subselect, more than one row may have a
budget equal to the minimum and thus would be deleted.
Example 13-20 DELETE with self referencing non-correlated subquery
Example 13-21 shows how we can delete the employees with the highest salary for each
department. To perform this DELETE in our sample environment, we have to drop the self
referencing foreign key of the TBEMPLOYEE table since it is defined with the delete rule ON
DELETE NO ACTION.
Example 13-21 DELETE with self referencing non-correlated subquery
The enhanced support for the UPDATE and DELETE statements also improves DB2 family
compatibility.
Restrictions on usage
This enhancement does not extend to a positioned UPDATE or DELETE statement, that is,
an UPDATE or DELETE statement which uses the WHERE CURRENT OF cursor-name
clause.
DB2 positioned updates and deletes continue to return the SQLCODE -118 if a subquery in
the WHERE clause references the table being updated or which contains rows to be deleted.
Example 13-23 shows how to use a subquery that returns a single row in the SET clause of
an update. Before V6 you had to use two SQL statements.
Example 13-23 Non-correlated subquery in the SET clause of an UPDATE
UPDATE SC246300.TBEMPLOYEE
SET DEPTSIZE = (SELECT COUNT(*)
FROM SC246300.TBDEPARTMENT
WHERE DEPTNO = ‘A01’)
WHERE WORKDEPT = ‘A01’
Note: If the subselect returns no rows, the null value is assigned to the column to be
updated, if the column cannot be null, an error occurs.
The columns of the target table or view of the UPDATE can be used in the search condition of
the subselect. Using correlation names to refer to these columns is only allowed in a
searched UPDATE.
In Example 13-24 we update the manager of all employees with the manager assigned to
their department. It is up to the user to make sure only a single row is returned in the
subselect. Otherwise you receive an SQLCODE -811.
Example 13-24 Correlated subquery in the SET clause of an UPDATE
UPDATE SC246300.TBEMPLOYEE X
SET MANAGER = (SELECT MGRNO
FROM SC246300.TBDEPARTMENT Y
WHERE X.WORKDEPT = Y.DEPTNO
) #
In Example 13-25 we update the number of orders (NUMORDERS column) for all employees
with a NATIONKEY of 34.
Example 13-25 Correlated subquery in the SET clause of an UPDATE with a column function
UPDATE SC246300.TBEMPLOYEE X
SET NUMORDERS = (SELECT COUNT(*)
FROM SC246300.TBORDER
WHERE CLERK = X.EMPNO)
Example 13-26 shows how the update in Example 13-23 can be changed to provide
DEPTSIZE for employees of all departments. This is done by using an UPDATE with a
correlated subquery in the SET clause.
Example 13-26 Correlated subquery in the SET clause of an UPDATE using the same table
UPDATE SC246300.TBEMPLOYEE X
SET DEPTSIZE = (SELECT COUNT(*)
FROM SC246300.TBEMPLOYEE Y
WHERE X.WORKDEPT = X.WORKDEPT) ;
Example 13-27 shows how to move employees Mark and Ivan to department number ‘A01’
using a row expression in the SET clause of an UPDATE.
Example 13-27 Row expression in the SET clause of an UPDATE
UPDATE SC246300.TBEMPLOYEE
SET (MANAGER,WORKDEPT) = (SELECT MGRNO,DEPTNO
FROM SC246300.TBDEPARTMENT
WHERE DEPTNO = 'A01')
WHERE FIRSTNME IN ('MARK','IVAN')
The SET assignment statement assigns the value of one or more expressions or a NULL
value to one or more host-variables or transition-variables and replaces the SET
host-variable statement documented in previous releases of DB2.
The statement can be embedded in an application program or can be contained in the body
of a trigger. If the statement is a triggered SQL statement, then it must be part of a trigger
whose action is BEFORE and whose granularity is FOR EACH ROW. In this context, a
host-variable cannot be specified.
You need to ensure that the subselect does not return more than one value (row), as the
statement would then fail with SQLCODE -811. Since only one row must be returned from the
subselect, you cannot use the GROUP BY and HAVING clause, if you do, you get an
SQLCODE -815. You should also consider whether it is possible for the statement to return
no rows. In this case, the null value is assigned to the column(s) to be updated. If a column
does not accept null values an SQLCODE -407 is returned. Consequently, this function is
ideally suited when you want to update a column to the result of functions such as COUNT,
MAX, SUM or where the subselect is accessing data by its primary key.
However, for a searched update, you may reference a column in the table to be updated
within the subselect.
Performance is improved with DRDA applications, since the client application can specify
limits to the amount of data returned and DRDA closes the cursor implicitly when the limit is
met.
Example 13-28 gives a simple example of the usage of the FETCH FIRST n ROWS ONLY
clause. In this case the cursor receives an SQLCODE +100 at the sixth FETCH operations
(after 5 ROWS have been returned), even though there are more rows that qualify the
predicates.
Example 13-28 FETCH FIRST n ROWS ONLY
SELECT T1.CREATOR
,T1.NAME
FROM SYSIBM.SYSTABLES T1
WHERE T1.CREATOR = 'SYSIBM'
AND T1.NAME LIKE 'SYS%'
ORDER BY T1.CREATOR
,T1.NAME
FETCH FIRST 5 ROWS ONLY;
CREATOR NAME
---------+---------+---------+---------+---------+---------
SYSIBM SYSAUXRELS
SYSIBM SYSCHECKDEP
SYSIBM SYSCHECKS
SYSIBM SYSCHECKS2
SYSIBM SYSCOLAUTH
DSNE610I NUMBER OF ROWS DISPLAYED IS 5
DSNE616I STATEMENT EXECUTION WAS SUCCESSFUL, SQLCODE IS 100
The FETCH FIRST clause can also be used for scrollable cursors.
The OPTIMIZE FOR clause is another that you can specify in the SELECT statement.
Table 13-1 describes the effect of specifying the FETCH FIRST clause with and without the
OPTIMIZE FOR clause.
Table 13-1 How the FETCH FIRST clause and the OPTIMIZE FOR clause interact
Clauses specified on the SELECT statement OPTIMIZE value used by DB2
When both options are specified, only the OPTIMIZE FOR clause is used during access path
selection and determining how to do blocking in a DRDA environment.
If the SELECT...INTO statement is coded and returns more than one row, DB2 returns an
SQLCODE -811, and the statement would be rejected. You can use this statement for
existence checking (to see if one or more rows qualify) when you don’t actually need the data
itself. However, in order to be sure there is only one row that qualifies, DB2 has to continue
looking through the data and applying predicates to make sure there is no second row. This
does take up unnecessary resources when you want to find out if a row exists. You don’t need
to know there is that there is more than one.
Because of the SQLCODE -811, there is no guarantee that the host variables are actually
filled with the values returned from the query.
Therefore, if you needed to see the data itself, and if there was no way to ensure that only a
single row was returned, then a cursor had to be opened and the program itself would have to
read in the first row that matches the selection and join criteria and throw away all other rows
by closing the cursor.
The FETCH FIRST 1 ROWS ONLY clause can now be added to the SELECT ... INTO
statement to specify that only one row is returned to the program, even if multiple rows match
the WHERE criteria. Wherever uniqueness is not significant, this clause can be very powerful.
In case of the existence checking, DB2 does not look for a second row and not return an
SQLCODE -811.
SELECT ACCOUNT
,ACCOUNT_NAME
,TYPE
,CREDIT_LIMIT
INTO :hv_account
,hv_acctname
,:hv_type
,:hv_crdlmt
FROM ACCOUNT
WHERE ACOCUNT_NAME = :hv_in_acctname
FETCH FIRST 1 ROW ONLY ;
As you can choose to pick up only a row, the SELECT INTO statement support also the
GROUP BY and HAVING clauses.
Note: Be cautious when using this feature, make sure that logically (within your
application) it is appropriate to ignore multiple rows.
The SET assignment statement is an alternative to assign the value of one or more
expressions or a NULL value to one or more host-variables or transition-variables. In
Example 13-31, we show several ways to use the SET.
Background information
The colon was an option in DB2 V1 code and documentation. It was not an option for some
other products, and the optional colon did not get accepted into the SQL standard. With DB2
V2, the manuals were changed to indicate that the colon should always be specified, and a
warning message, DSNH315I, was implemented. Most customers noticed the warnings and
corrected their applications to comply with the standard.
DB2 V3, V4, and V5, continued to allow the optional colons with the warning messages. With
DB2 V6 the level of complexity of SQL was such that it was decided not to allow the colons to
remain optional: when DB2 V6 was announced in 1998, this incompatible change was
included in the announcement, and in the documentation.
With the DB2 V6 precompiler an error message is produced, generally DSNH104I. The only
way to correct the problem is to correct the source and precompile again. This assumes that
the source is available.
If you cannot change the source to put in the colons to fix the problem, then you can use a
precompiler from DB2 V4 or V5.
BIND and REBIND on DB2 V6 can also fail for DBRMs that were created prior to DB2 Version
2 Release 3. For example, if you drop an index that is used in a package or plan, then the
package or plan must be rebound, either by explicit command or automatic BIND. If the
DBRM was produced on DB2 Version 2 Release 2 or earlier, then the BIND or REBIND fails.
DBRMs produced by the DB2 Version 2 Release 3 or later precompiler have a colon in the
DBRM, even if the source code does not.
If you still have a host variable without a preceding colon and you have migrated to DB2 V6,
the error that you receive depends on when the application was originally precompiled. If it
was precompiled with the Version 2 Release 2 precompiler (or earlier), you get an error when
you try to rebind or when an automatic rebind occurs. If you do a second bind using the
DBRM from the Version 2 Release 2 precompiler, you get a failure as well. If you
re-precompile at V6, you receive a syntax error from the precompiler or a bind error. If the
application was bound on Version 2 release 3 or later, you should have no problems with
rebind, automatic rebind, or a second bind using the same DBRM.
The best practice is to set an application programming standard and to add the colon in all
DB2 applications. If the return code from the precompiler is not zero, then the warning could
cause problems in production.
If you do not have the source code, the options are very limited. APAR II12100 may provide
some help. APARs PQ26922 and PQ30390 may be applicable in some cases.
A sample REXX procedure, which analyzes all Format 1 DBRMs (pre V2.3) to check whether
all host variables are preceded with a colon, is available from the Internet. The REXX/DB2
interface is used, and therefore DB2 V5 or V6 is required. The procedure creates temporary
EXEC libraries, copies the REXX EXEC, executes DSNTIAUL using PARM(‘SQL’) to extract
data from the catalog, extracts DBRM listings from the catalog, executes the REXX to
analyze the output looking for missing colons preceding host variables ":hv", and produces a
report.
You need to examine the exceptions identified by the REXX program. It should be obvious
where you need to amend the source SQL and re-precompile.
The program is a sample only and is provided without any implied warranty or support. We
have not checked all eventualities, so we cannot guarantee that every invalid DBRM is found,
but it can assist you with the migration.
The procedure is called DBRM Colon Finder and it is available from the URL:
http://www.ibm.com/software/db2/os390/downloads.html
SELECT BUYER
,SELLER
,RECNO
,PESETAFEE
FROM SC246300.TBCONTRACT
WHERE BUYER IN (CUSTOMER('01'),CUSTOMER('03'))
DB2 now also supports multiple expressions (row expressions) on the left-hand side of the IN
and NOT IN predicate when a fullselect is specified on the right-hand side. Example 13-10 on
page 187 shows a row expression and a fullselect into a NOT IN list.
Note: This feature is available only for tables created under V6 (or V5 with APAR
PQ16946).
If your partitioned table is created before V6 and you want the partitioning key to be
updatable, remember that when you drop and create the table, plans and packages must be
rebound, authorizations on the dropped table re-granted, synonyms, views, and referential
constraints re-created.
Tip: Applications that use UPDATEs on the partitioning key (instead of DELETEs and
INSERTs) could be tested successfully in a test environment, but they may not run
successfully in a production environment if the production table was created prior to DB2
V6.
If an update moves a row from one partition to another, partitions are drained and the
application may deadlock or time out, because of the same restrictions on claim/drain
processing.
DB2 administrators should not allow a partitioned key to be updatable in systems with high
concurrency requirements and long running transactions. This can be controlled via a new
ZPARM (PARTKEYU). This parameter gives you the option to either disable the partitioning
key update feature, allow the update to move the key to a different partition, or limit the update
to stay in the same partition.
In this chapter we describe some ideas where these enhancements can be used and
compare them to implementing the same processes using application code, such as:
Online LOAD RESUME versus program insert
REORG DISCARD versus deleting rows via a program followed by a REORG
Using REORG UNLOAD EXTERNAL and UNLOAD instead of a home grown program or
the DSNTIAUL sample program
Using SQL statements in the utility input stream
In this section, we discuss the LOAD RESUME SHRLEVEL CHANGE that was introduced in
V7. This ‘online version’ of the LOAD utility allows other users access to the table(s) while the
data is being loaded. With this type of LOAD utility, installations should have another look
whether they still have to develop (and maintain) their own batch SQL INSERT programs.
SHRLEVEL NONE (default) specifies that applications have no concurrent access to the
table space or partition(s) being loaded, that is, the LOAD utility operates the same as prior to
version 7. This type of LOAD is also referred to as classic LOAD throughout this chapter.
The term ‘online’ refers to the mode of operation of the LOAD utility when SHRLEVEL
CHANGE is specified. When using online LOAD RESUME, other applications can
concurrently issue SQL SELECT, UPDATE, INSERT and DELETE statements against
table(s) in the table space or partition(s) being loaded.
In brief, by using online LOAD RESUME, you can load data with minimal impact on SQL
applications and without writing and maintaining INSERT programs.
Online LOAD RESUME behaves externally like a classic LOAD, but it works internally like a
mass INSERT.
Online LOAD RESUME uses a data manager INSERT process to load rows into the table
space or partition(s). Online load resume behaves like a normal insert program using up free
space, taking locks, logging the inserts and trying to maintain clustering order of the data.
Although online LOAD RESUME takes locks like a normal program, the utility monitors the
lock situation constantly and dynamically adapts the commit interval to avoid impacting other
programs.
Data integrity cannot be always assured by only using foreign keys and check constraints. In
some cases, triggers are additionally used to ensure the correctness of the data. The classic
LOAD does not activate triggers, which may result in a data integrity exposure after loading
new data. The new online LOAD RESUME utility operates like SQL INSERTs, therefore,
triggers are activated, check constraints are checked as well as referential integrity
relationships. Therefore, data integrity is guaranteed at all times. The cost is that, compared
Logging
Only LOG YES is allowed. Therefore, no COPY is required afterwards. If you are thinking
about converting from classic LOAD to online LOAD RESUME for large tables, you may want
to check your DB2 logging environment, like the number of active logs, their placement on the
disk volumes and fast devices, consider striping and increasing DB2 log output buffer size.
RI
Referential integrity is enforced when loading a table with online LOAD RESUME.
When you use online LOAD RESUME on a self-referencing table, it forces you to sort the
input in such a way that referential integrity rules are met, rather than sorting the input in
clustering sequence, which you used to do for classic LOAD.
Duplicate keys
The handling of duplicate keys is somewhat different when using online LOAD RESUME
compared to the classic LOAD utility. When using online LOAD RESUME and a unique index
is defined, INSERTs (done by the online LOAD) are accepted as long as they provide
different values for this column (or set of columns). This is different from the classic LOAD
procedure, which discards all rows that you try to load having the same value for a unique
key.
You may have setup a procedure (manual or automated) to handle the rows classic LOAD
discards. When you move over to online LOAD RESUME, you have to change handling the
discarded rows accordingly.
Clustering
Whereas the classic LOAD RESUME stores the new records (in the sequence of the input) at
the end of the already existing records, online LOAD RESUME tries to insert the records into
the available free space respecting the clustering order as much as possible. When you have
to LOAD (insert) a lot of rows, make sure there is enough free space available. Otherwise
these rows are likely to be stored out of the clustering order and you might end up having to
run a REORG to restore proper clustering (which can be run online as well).
Free space
As mentioned before, the available free space, PCTFREE or FREEPAGE, is used by online
LOAD RESUME, in contrast to the classic LOAD.
Figure 14-1 shows an example of the online LOAD RESUME syntax and the inserts that it
performs. Some differences with the classic LOAD are listed.
O n lin e L O A D R E S U M E
S y n ta x P r o c e s s in g
INSERT INTO SC246300.TBEMPLOYEE
LOAD DATA VALUES ('000001'
,'4082651590'
RESUME YES ,'A05' );
SHRLEVEL CHANGE INSERT INTO SC246300.TBEMPLOYEE
INTO TABLE SC246300.TBEMPLOYEE VALUES ('000002'
( EMPNO POSITION ( 1: 6) CHAR ,'4082651596'
, PHONENO POSITION ( 9:24) CHAR ,'C02' );
, WORKDEPT POSITION (25:28) CHAR INSERT INTO SC246300.TBEMPLOYEE
... VALUES ('000003'
,'4082651598'
,'B10' );
000001 4082651590 A05 ...
000002 4082651596 C02
000003 4082651598 B10
... C la im s ( n o t d r a in s )
L O G Y E S o n ly
S e c u r it y : F ir e T r ig g e r s
L O A D ( n o t I N S E R T ) p r iv ile g e D a t a I n t e g r it y e n f o r c e d : R I , c h e c k
T im e o u t o f t h e L O A D : c o n t r a in t s a n d u n iq u e k e y s .
L ik e u t ilit y ( n o t lik e S Q L a p p l. ) F r e e s p a c e d u s e d ( c lu s t e r in g m a y
b e p re s e rv e d )
Commit frequency
Online LOAD RESUME dynamically monitors the current locking situation for the table
spaces or partitions being loaded. This enables online LOAD RESUME to choose the commit
frequency and avoid lock contention with other SQL. This kind of commit frequency flexibility
is not possible to code at batch insert programs, though the frequency can be changed while
program is running.
Restart
During RELOAD, internal commit points are set, therefore, RESTART(CURRENT) is possible
as with the classic LOAD. When using an INSERT program, application repositioning
techniques are needed to be able to restart, which is not all that easy when sequential files
are involved, especially when writing them (for example, records that could not be inserted for
some reason).
Be careful not to produce inconsistent data for other applications. Remember that other
transactions and programs are running while you are loading data. This was not the case with
the classic LOAD. All other access was drained. No-one wants to see orders without line
items.
After running online LOAD resume, it is recommended to run RUNSTATS and depending on
the result, run a REORG to restore clustering sequence and free space.
For a complete list of options and other utilities that are incompatible with LOAD SHRLEVEL
CHANGE, see DB2 UDB for OS/390 and z/OS Version 7 Utility Guide and Reference,
SC26-9945.
Some pages might need more free space than others. For example, the table is clustered by
customer and you have some very active customers, who have a lot of transactions. You can
then run the REORG first while discarding rows from less active customers and delete the
rows for the active customers afterwards with an SQL DELETE operation to gain that extra
space near the ‘hot’ pages. This is one case where you can consider to continue using a
batch programs to do the deletes or rethink your free space strategy.
The WHEN conditions that you can specify are a simple subset of what can be coded on a
WHERE clause. WHEN conditions allow AND-ing, OR-ing of selection conditions and
predicates. Comparison operators are =, >, <, <>, <=, >=, IS (NOT) NULL, (NOT) LIKE,(NOT)
BETWEEN and (NOT) IN. The predicates that are allowed are only comparisons between a
column and a constant or a labeled-duration-expression (like CURRENT DATE + 30 DAYS).
Discarded rows are written to a SYSDISC data set or a DD-name specified with DISCARDDN
keyword.
Important: If no discard data set is specified, the discarded records are lost.
Either UNLOAD EXTERNAL or REORG DISCARD can generate the same LOAD control
statements, based on the data being processed. A sample LOAD statement generated by
REORG DISCARD is shown in Figure 14-2.
may beASCII
LOAD DATA LOG NO INDDN SYSREC unloaded insame CCSIDas stored
EBCDIC CCSID (500,0,0)
INTO TABLE "SC246300"."TBEMPLOYEE"
WHEN (00004:00005 = X'0012') this identifies thetable
( "EMPNO " POSITION(00007:00012) CHAR(006)
, "PHONENO " POSITION(00013:00027) CHAR(015)
, "WORKDEPT " POSITION(00028:00030) CHAR(003)
, ...
, ...
, ...
, "SALARY " POSITION(00091:00095) DECIMAL NULLIF(00090)=X'FF'
, ...
)
Figure 14-2 Generated LOAD statements
REORG UNLOAD ONLY is fast, but places the data in a internal format that is distinctly not
user friendly. Its only use is to be used as input for a LOAD FORMAT UNLOAD utility, and it
must be loaded back into the same table it was unloaded from.
A new option, REORG UNLOAD EXTERNAL, provides the required capability to unload data
in an external format. Like DSNTIAUL, this function also generates standard LOAD utility
statements as part of the process. The unloaded data can be loaded into another table.
The UNLOAD utility is a new member of the DB2 Utilities Suite that was introduced in V7. The
UNLOAD utility unloads the data from one or more source objects to one or more sequential
data sets in external format. The source objects can be DB2 table spaces or DB2 image copy
data sets.
Example 14-2 show a REORG UNLOAD EXTERNAL utility statement on table space
TS246304 to unload the employees from the table SC246300.TBEMPLOYEE working in
department ‘A01’.
The selection criteria that can be used to unload rows using REORG UNLOAD EXTERNAL
are identical those that are available for REORG DISCARD.
As with REORG DISCARD, REORG UNLOAD EXTERNAL does not provide you with
formatting options for data that is unloaded. This can be problematic for numeric data when
the information needs to be transported to another platform. Numeric data is unloaded in a
host based format (binary and packed decimal). Using the UNLOAD utility can provide a
solution here.
Example 14-2 REORG UNLOAD EXTERNAL
14.3.3 UNLOAD
The UNLOAD utility can unload data from one or more source objects to one or more BSAM
sequential data sets in external format. The source objects can be DB2 table spaces or DB2
image copy data sets.The UNLOAD utility does not use indexes to access the source table(s).
The utility scans the table space or partition(s) directly.
In addition to the functions that are also supported by REORG UNLOAD EXTERNAL, the
UNLOAD utility also supports the ability to:
Unload data from an image copy data set(s), including full, incremental, DSN1COPY and
inline copies.
Select columns (specifying an order of the fields in the output record).
Sample and limit the number of rows unloaded (by table).
Specify the start position, length and data type of output fields.
Format output fields.
Translate output character-type data to EBCDIC, ASCII or UNICODE.
Specify SHRLEVEL and ISOLATION level.
Unload table space partitions in parallel.
T h e c o lu m n o r d e r s p e c if ie s M a x im u m n u m b e r o f r o w s
t h e fi e ld o r d e r in th e o u t p u t w h ic h w ill b e u n lo a d e d f r o m
re c o rd s t a b le E M P
For more detailed information on how to use the UNLOAD utility refer to:
DB2 UDB for OS/390 and z/OS Version 7 Utility Guide and Reference, SC26-9945
DB2 for z/OS and OS/390 Version 7: Using the Utilities Suite, SG24-6289
Another major advantage of UNLOAD is the possibility to unload data from an image copy
data set, avoiding access to the base table and interfering with other processes. Even though
the UNLOAD utility can run with an isolation level of UR, it still access data through the buffer
pool which might still cause response time degradation for other processes when pages get
pushed out of the buffer pool by the full scan of the UNLOAD utility.
If it is necessary to access the base table to unload the latest version of the table, you could
use the SHRLEVEL and ISOLATION clauses as you do for other utilities. Otherwise unload
from an image copy.
Because the DB2 UNLOAD utility uses the DB2 buffer pools to retrieve the data, no
QUIESCE WRITE YES is required before starting the unload process.
If there are multiple tables in the table space, those not subject to the WHEN clause are
unloaded in their entirety.
The following table, Table 14-1, gives an overview of the major functions and differences
between the three alternatives. Since DSNTIAUL is a normal application program using
normal SQL requests, most of the comparison criteria also apply to home grown application
programs that might have been developed in your installation.
Notes:
(1) You can change DSNTIAUL to run with ISOLATION level UR or add the WITH UR clause to your
unload SQL statement. REORG UNLOAD EXTERNAL can only use SHRLEVEL NONE. UNLOAD
can use ISOLATION CS or UR (SHRLEVEL REFERENCE) or SHRLEVEL CHANGE.
(2) Although DSNTIAUL has limited formatting capabilities (only what you can do in an SQL
statement), if you write your own program you have of course full control. Even though the UNLOAD
utility has a lot more formatting options that REORG UNLOAD EXTERNAL, there is some room for
improvement, like standard support for common PC formats.
(3) When executing plain SQL statements, the optimizer decides whether or not to use parallelism for
a certain query (if the plan is bound with DEGREE(ANY)).
(4) Although you can unload data from the catalog, some catalog tables that contain LOB columns
might pose a problem, mostly because of the 32K record length restriction on output files.
If you are looking for a fast way to unload data either directly from the table space or from an
image copy and your requirements for the format in which the data needs to be unloaded are
not to stringent, the UNLOAD utility is an excellent choice.
Once you migrated to Version 7 there is probably no reason to continue using REORG
UNLOAD EXTERNAL. The UNLOAD utility is a complete functional replacement (even
adding a lot of extra capabilities) and its performance is slightly better.
Home grown unload applications can still be useful in some cases, for instance when you
have very specific output format requirements or when you need to unload data that is
somehow related (like a parent record with all its dependent rows) in a single unload
operation.
The EXEC SQL statement requires no extra privileges other than the ones required by the
SQL statements itself.
You can only put one SQL statement between the EXEC SQL and ENDEXEC keywords. The
SQL statement can be any dynamic SQL statement that can be used as input for the
EXECUTE IMMEDIATE statement, as listed in Example 14-3.
Example 14-3 List of dynamic SQL statements
CREATE,ALTER,DROP a DB2 object
RENAME a DB2 table
COMMENT ON,LABEL ON a DB2 table, view, or column
GRANT,REVOKE a DB2 authority
COMMIT,ROLLBACK operation
In Example 14-4 we create a new table in the default database DSNDB04 with the same
layout as SYSIBM.SYSTABLES.
Example 14-4 Create a new table with the same layout as SYSIBM.SYSTABLES
EXEC SQL
CREATE TABLE PAOLOR3.SYSTABLES LIKE SYSIBM.SYSTABLES
In the same way, we are able to create indexes on this table, create views on it, and so on. All
this is done in the utility input stream.
EXEC SQL eliminates additional job steps in a job to execute dynamic SQL statements
before, between or after the regular utility steps. It can simplify the JCL coding by eliminating
these dynamic SQL applications, like DSNTIAD or DSNTEP2 from the JCL stream, and it
enables to merge different utility steps, separated by dynamic SQL applications, into one
single utility step. But be aware of the restrictions imposed by the EXEC SQL statement.
For a more detailed description, see DB2 for z/OS and OS/390 Version 7: Using the Utilities
Suite, SG24-6289.
Part 5 Appendixes
--------------------------------------------------------------------
-- DDL TO CREATE THE DATABASE AND TS (OPTIONALLY STOGROUP)
-- OBJECTS ARE VERY SMALL (SO WE CAN AFFORD TO USE DEFAULT SPACE )
--------------------------------------------------------------------
-- CREATE STOGROUP SG246300 VOLUMES(SBOX24) VCAT DB2V710G#
--
CREATE DATABASE DB246300
BUFFERPOOL BP1
INDEXBP BP2
STOGROUP SG246300
CCSID EBCDIC #
--
CREATE TABLESPACE TS246300
IN DB246300#
--
CREATE TABLESPACE TS246301
IN DB246300#
--
CREATE TABLESPACE TS246302
IN DB246300#
--
CREATE TABLESPACE TS246303
IN DB246300#
--
CREATE TABLESPACE TS246304
IN DB246300#
--
CREATE TABLESPACE TS246305
IN DB246300#
--
CREATE TABLESPACE TS246306
IN DB246300#
--
CREATE TABLESPACE TS246307
IN DB246300#
--
CREATE TABLESPACE TS246308
IN DB246300#
--
CREATE TABLESPACE TS246309
IN DB246300#
--
CREATE TABLESPACE TS246310
IN DB246300#
--
CREATE TABLESPACE TS246311
IN DB246300#
--
CREATE TABLESPACE TS246331
IN DB246300#
--
CREATE TABLESPACE TS246332
IN DB246300#
--
CREATE TABLESPACE TS246333
IN DB246300#
--
CREATE TABLESPACE TSLITERA
--
CREATE DISTINCT TYPE SC246300.DOLLAR
AS DECIMAL(17,2)
WITH COMPARISONS#
--
CREATE DISTINCT TYPE SC246300.PESETA
AS DECIMAL(18,0)
WITH COMPARISONS #
--
CREATE DISTINCT TYPE SC246300.EURO
AS DECIMAL(17,2)
WITH COMPARISONS#
--
CREATE DISTINCT TYPE SC246300.CUSTOMER
AS CHAR(11)
WITH COMPARISONS#
--
GRANT USAGE ON DISTINCT TYPE SC246300.EURO TO PUBLIC#
-- ROWID USAGE
SC246300.TBEMPLOYEE
---------+---------+---------+---------+---------+---------+---------+-----
FIRSTNME BIRTHDATE WORKDEPT SEX BIRTHDATE JOB EDLEVEL
---------+---------+---------+---------+---------+---------+---------+-----
SC246300.TBDEPARTMENT
---------+---------+---------+---------+---------+---------+---------+
DEPTNO MGRNO ADMRDEPT LOCATION BUDGET DEPTNAME
---------+---------+---------+---------+---------+---------+---------+
B01 ------ KHI SAN JOSE 100000 DB2
A01 000001 EKV MADRID 40000 SALES
C01 ------ DAH FLORIDA 35000 MVS
A02 000001 ERG SAN FRANCISCO 32000 MARKETING
SC246300.TBCUSTOMER
---------+---------+---------+---------+---------+---------+---------+--
CUSTKEY FIRSTNAME LASTNAME SEX CITYKEY
---------+---------+---------+---------+---------+---------+---------+--
01 ADELA SALVADOR F 1
02 MIRIAM ANTOLIN F 2
03 MARK SMITH M 3
04 SILVIA YOUNG F 3
05 IVAN KENT M 3
SC246300.TBCITIES
--------+---------+---------+---------+---------+---------+---------+
CITYKEY REGION_CODE STATE CITYNAME COUNTRY
--------+---------+---------+---------+---------+---------+---------+
1 28010 MADRID SPAIN
2 15 AMSTERDAM HOLLAND
3 55 SAN FRANCISCO USA
4 42 NOKIA FINLAND
5 33076 CORAL SPRINGS USA
6 97 TOKIO JAPAN
SC246300.TBCONTRACT
---------+---------+---------+---------+---------+---------+
SELLER BUYER RECNO PESETAFEE
---------+---------+---------+---------+---------+---------+
000006 02 333 90000.
000001 03 222 10000.
000003 01 111 50000.
SC246300.TBREGION
---------+---------+---------+---------+
REGION_CODE REGION_NAME
---------+---------+---------+---------+
28010 PROVINCE OF MADRID
55 SILICON VALLEY
15 THE NETHERLANDS
SC246300.TBORDER
--------+---------+---------+---------+---------+---------+---------+---------+--
ORDERKEY CUSTKEY ORDERSTATUS ORDERPRIORITY SHIPPRIORITY REGION_CODE
--------+---------+---------+---------+---------+---------+---------+---------+--
10 03 O URGENT 1 5
1 05 M NORMAL 3 2801
2 03 M VERY URGENT 0 1
SC246300.TBLINEITEM
---------+---------+---------+---------+---------+---------+---------+
NORDERKEY LINENUMBER L_ITEM_NUMBER QUANTITY TAX
---------+---------+---------+---------+---------+---------+---------+
1 1 100 3 5
1 2 120 1 5
2 1 440 2 10
3 1 660 3 5
4 1 505 4 10
4 2 440 1 10
4 3 660 1 5
5 1 133 1 5
6 1 100 8 5
7 1 120 1 5
8 1 440 1 10
9 1 660 1 5
10 1 440 1 10
10 2 100 1 5
SC246300.TBITEMS
---------+---------+---------+---------+---------+---------+---------+---------+
ITEM_NUMBER PRODUCT_NAME STOCK PRICE COMMENT
---------+---------+---------+---------+---------+---------+---------+---------+
100 WIDGET 50 1.25 INCOMPATIBLE WITH HAMMER
120 NUT 50 1.25 FOR TYPE 20 NUT ONLY
133 WASHER 50 1.25 BRONZE
440 HAMMER 50 1.25 NONE
505 NAIL 50 1.25 GALVANIZED
660 SCREW 50 1.25 WOOD
*****************************************************
* This stored procedure shows how SQLSTATE and *
* a DIAG string can be returned to a trigger *
* so it can undo all changes done so far by the *
* trigger *
*****************************************************
DATA DIVISION.
WORKING-STORAGE SECTION.
EXEC SQL INCLUDE SQLCA END-EXEC.
01 ERROR-MESSAGE.
02 ERROR-LEN PIC S9(4) COMP VALUE +960.
02 ERROR-TEXT PIC X(120) OCCURS 8 TIMES
INDEXED BY ERROR-INDEX.
77 ERROR-TEXT-LEN PIC S9(8) COMP VALUE +120.
LINKAGE SECTION.
* INPUT PARM PASSED BY STORED PROC
01 PARM1 PIC X(20).
01 INDPARM1 PIC S9(4) COMP.
* DECLARE THE SQLSTATE THAT CAN BE SET BY STORED PROC
01 P-SQLSTATE PIC X(5).
* DECLARE THE QUALIFIED PROCEDURE NAME
01 P-PROC.
49 P-PROC-LEN PIC 9(4) USAGE BINARY.
49 P-PROC-TEXT PIC X(27).
* DECLARE THE SPECIFIC PROCEDURE NAME
01 P-SPEC.
49 P-SPEC-LEN PIC 9(4) USAGE BINARY.
49 P-SPEC-TEXT PIC X(18).
* DECLARE SQL DIAGNOSTIC MESSAGE TOKEN
01 P-DIAG.
49 P-DIAG-LEN PIC 9(4) USAGE BINARY.
49 P-DIAG-TEXT PIC X(70).
ERROR-EXIT.
GOBACK.
DBERROR.
DISPLAY "*** SQLERR FROM SD0BMS3 ***".
MOVE SQLCODE TO ERR-CODE .
IF SQLCODE < 0 THEN MOVE '-' TO ERR-MINUS.
DISPLAY "SQLCODE = " ERR-MINUS ERR-CODE "LINE " LINE-EXEC.
CALL 'DSNTIAR' USING SQLCA ERROR-MESSAGE ERROR-TEXT-LEN.
IF RETURN-CODE = ZERO
PERFORM ERROR-PRINT VARYING ERROR-INDEX
FROM 1 BY 1 UNTIL ERROR-INDEX GREATER THAN 8
* TO SHOW WHERE EVERYTHING GOES IN SQLCA
DISPLAY "*** START OF UNFORMATTED SQLCA ***"
DISPLAY "SQLCAID X(8) " SQLCAID
MOVE SQLCABC TO XSQLCABC
DISPLAY "SQLCABC I " XSQLCABC
MOVE SQLCODE TO XSQLCODE
DISPLAY "SQLCODE I " XSQLCODE
MOVE SQLERRML TO XSQLERRML
DISPLAY "SQLERRML SI " XSQLERRML
DISPLAY "SQLERRMC X(70) " SQLERRMC
DISPLAY "SQLERRP X(8) " SQLERRP
MOVE SQLERRD(1) TO XSQLERRD
DISPLAY "SQLERRD1 I " XSQLERRD
MOVE SQLERRD(2) TO XSQLERRD
DISPLAY "SQLERRD2 I " XSQLERRD
MOVE SQLERRD(3) TO XSQLERRD
DISPLAY "SQLERRD3 I " XSQLERRD
MOVE SQLERRD(4) TO XSQLERRD
DISPLAY "SQLERRD4 I " XSQLERRD
MOVE SQLERRD(5) TO XSQLERRD
DISPLAY "SQLERRD5 I " XSQLERRD
MOVE SQLERRD(6) TO XSQLERRD
DISPLAY "SQLERRD6 I " XSQLERRD
DISPLAY "SQLWARN0 X(1) " SQLWARN0
DISPLAY "SQLWARN1 X(1) " SQLWARN1
DISPLAY "SQLWARN2 X(1) " SQLWARN2
DISPLAY "SQLWARN3 X(1) " SQLWARN3
DISPLAY "SQLWARN4 X(1) " SQLWARN4
DISPLAY "SQLWARN5 X(1) " SQLWARN5
DISPLAY "SQLWARN6 X(1) " SQLWARN6
DISPLAY "SQLWARN7 X(1) " SQLWARN7
DISPLAY "SQLWARN8 X(1) " SQLWARN8
Select the Additional materials and open the directory that corresponds with the redbook
form number, SG246300.
The publications listed in this section are considered particularly suitable for a more detailed
discussion of the topics covered in this redbook.
IBM Redbooks
For information on ordering these publications, see “How to get IBM Redbooks” on page 254.
DB2 for z/OS and OS/390 Version 7: Using the Utilities Suite, SG24-6289
DB2 for OS/390 and z/OS Powering the World’s e-business Solutions, SG24-6257
DB2 for z/OS and OS/390 Version 7 Performance Topics, SG24-6129
DB2 UDB Server for OS/390 and z/OS Version 7 Presentation Guide, SG24-6121
DB2 UDB Server for OS/390 Version 6 Technical Update, SG24-6108
DB2 Java Stored Procedures Learning by Example, SG24-5945
DB2 UDB for OS/390 Version 6 Performance Topics, SG24-5351
DB2 for OS/390 Version 5 Performance Topics, SG24-2213
DB2 for MVS/ESA Version 4 Non-Data-Sharing Performance Topics, SG24-4562
DB2 UDB for OS/390 Version 6 Management Tools Package, SG24-5759
DB2 Server for OS/390 Version 5 Recent Enhancements - Reference Guide, SG24-5421
DB2 for OS/390 Capacity Planning, SG24-2244
Cross-Platform DB2 Stored Procedures: Building and Debugging, SG24-5485
DB2 for OS/390 and Continuous Availability, SG24-5486
Connecting WebSphere to DB2 UDB Server, SG24-6219
Parallel Sysplex Configuration: Cookbook, SG24-2076
DB2 for OS/390 Application Design Guidelines for High Performance, SG24-2233
Using RVA and SnapShot for BI with OS/390 and DB2, SG24-5333
IBM Enterprise Storage Server Performance Monitoring and Tuning Guide, SG24-5656
DFSMS Release 10 Technical Update, SG24-6120
Storage Management with DB2 for OS/390, SG24-5462
Implementing ESS Copy Services on S/390, SG24-5680
Other resources
These publications are also relevant as further information sources:
DB2 UDB for OS/390 and z/OS Version 7 What’s New, GC26-9946
DB2 UDB for OS/390 and z/OS Version 7 Installation Guide, GC26-9936
DB2 UDB for OS/390 and z/OS Version 7 Command Reference, SC26-9934
DB2 UDB for OS/390 and z/OS Version 7 Messages and Codes, GC26-9940
DB2 UDB for OS/390 and z/OS Version 7 Utility Guide and Reference, SC26-9945
Also download additional materials (code samples or diskette/CD-ROM images) from this
Redbooks site.
Redpieces are Redbooks in progress; not all Redbooks become Redpieces and sometimes
just a few chapters will be published this way. The intent is to get the information out much
quicker than the formal publishing process allows.
References in this publication to IBM products, programs or services do not imply that IBM
intends to make these available in all countries in which IBM operates. Any reference to an
IBM product, program, or service is not intended to state or imply that only IBM's product,
program, or service may be used. Any functionally equivalent program that does not infringe
any of IBM's intellectual property rights may be used instead of the IBM product, program or
service.
Information in this book was developed in conjunction with use of the equipment specified,
and is limited in application to those specific hardware and software products and levels.
IBM may have patents or pending patent applications covering subject matter in this
document. The furnishing of this document does not give you any license to these patents.
You can send license inquiries, in writing, to the IBM Director of Licensing, IBM Corporation,
North Castle Drive, Armonk, NY 10504-1785.
Licensees of this program who wish to have information about it for the purpose of enabling:
(i) the exchange of information between independently created programs and other programs
(including this one) and (ii) the mutual use of the information which has been exchanged,
should contact IBM Corporation, Dept. 600A, Mail Drop 1329, Somers, NY 10589 USA.
Such information may be available, subject to appropriate terms and conditions, including in
some cases, payment of a fee.
The information contained in this document has not been submitted to any formal IBM test
and is distributed AS IS. The use of this information or the implementation of any of these
techniques is a customer responsibility and depends on the customer's ability to evaluate and
integrate them into the customer's operational environment. While each item may have been
reviewed by IBM for accuracy in a specific situation, there is no guarantee that the same or
similar results will be obtained elsewhere. Customers attempting to adapt these techniques to
their own environments do so at their own risk.
Any pointers in this publication to external Web sites are provided for convenience only and
do not in any manner serve as an endorsement of these Web sites.
C-bus is a trademark of Corollary, Inc. in the United States and/or other countries.
Java and all Java-based trademarks and logos are trademarks or registered trademarks of
Sun Microsystems, Inc. in the United States and/or other countries.
Microsoft, Windows, Windows NT, and the Windows logo are trademarks of Microsoft
Corporation in the United States and/or other countries.
PC Direct is a trademark of Ziff Communications Company in the United States and/or other
UNIX is a registered trademark in the United States and other countries licensed exclusively
through The Open Group.
SET, SET Secure Electronic Transaction, and the SET Logo are trademarks owned by SET
Secure Electronic Transaction LLC.
Other company, product, and service names may be trademarks or service marks of others.
Index 263
partitioning 144 ROLLBACK 84, 85
partitioning key update 202 ROLLBACK TO SAVEPOINT 156
PARTKEYU 202 row expressions 185
PATH 9 quantified predicates 186
plan 92 restrictions 188
PLAN_TABLE 142 types 185
populate 85, 88 row size 89
positioned DELETE 176 row trigger 18
positioned UPDATE 175 ROWID 87, 112, 113
powerful SQL 123 casting 117
PQ16946 202 comparison 122
PQ19897 210, 212 DCLGEN 117
PQ23219 210, 212 direct row access 119
PQ34506 38 EPOCH 121
PQ53030 15 GENERATED BY DEFAULT 115
PRIMARY_ACCESSTYPE 119 implementation 113
PRIOR 165 IMS 120
privilege 82, 83 LOB 113
propagation 39 partitioning 118
restrictions 121
storing 120
Q UPDATE 114
QBLOCK_TYPE 142 USAGE SQL TYPE IS 117
QMF 154 row-value-expression 185
QUALIFIER 9, 10
qualifier 8
quantified predicates 185 S
SAR 35, 36
savepoint 98
R characteristics 99
RAISE_ERROR 25, 127 CONNECT 101
read stability 177 ON ROLLBACK RETAIN CURSORS 100
read-only cursor 153 ON ROLLBACK RETAIN LOCKS 100
REBIND 37, 200 RELEASE SAVEPOINT 99
rebind 33 remote connections 101
REBIND TRIGGER PACKAGE 33 restrictions 102
recovery 82 ROLLBACK TO SAVEPOINT 99
Redbooks Web site 254 UNIQUE 100
Contact us xix why 98
referential constraint 35, 38, 87 scalar function 58, 60, 61, 72, 168
referential integrity 28 scalar subquery 195
RELATIVE 164 schema 7, 8, 10, 72
relative moves 165 authorization 8
RELEASE(DEALLOCATE) 37 authorization ID 10
remote server 84, 93 characteristics 8
REORG name 16, 32, 44
DISCARD 205, 210 object 14
DISCARD restrictions 211 processor 10
UNLOAD EXTERNAL 205, 211, 212, 213 scratchpad 64
comparing 215 SCROLL 155, 156
UNLOAD ONLY 212 scrollable cursor 93, 149
repeatable read 177 absolute moves 164
restart 216 allowable combinations 160
RESTRICT 47 characteristics 151
result set 85, 88 choose the right type 153
result table 154, 162, 166 CLOSE CURSOR 156
REXX 154 cursor movement 164
REXX procedure 201 declaring 155
RI 39 delete hole 170
RID 113 FETCH 154, 157
RID list processing 121 FETCH ABSOLUTE 159
Index 265
SIGNAL SQLSTATE 24 LOB table spaces 214
table locator 31 pitfalls 215
transition tables 21 restriction 214
transition variables 20 unqualified table reference 92
useful queries 41 UPDATE 195
valid statements 22 scalar subquery 195
VALUES 23 self-referencing 193
WHEN 19 update hole 162, 170, 172
trigger action condition 19 update trigger 22
trigger characteristics 16 update with subselect
trigger happy 38 conditions 196
trigger package 33 self referencing 197
dependencies 33 user-defined 58
TRIGGERAUTH 35 user-defined column function 62
triggered operation 19 user-defined distinct type 7, 9, 44, 72
triggering event 17 user-defined function 7, 9, 15, 42, 58, 59, 60, 64, 72
triggering operation 16, 19, 22, 29 user-defined scalar function 61
triggering table 16 user-defined table function 62
two-part name 8, 44 USING CCSID 90
U V
UDF 9, 15, 21, 23, 24, 27, 28, 29, 30, 42, 57, 58, 59, 64 validproc 87
column functions 61 VALUES INTO 199
definition 59 view 35, 42, 47, 83
design considerations 64 views 8
efficiency 64
implementation and maintenance 60
scalar functions 60 W
sourced 66 WHEN 126
sourced function 65 WHERE CURRENT OF 87, 161
table functions 62 WITH CHECK OPTIO 144
UDT 9, 43, 44, 45, 47, 48, 54 WITH CHECK OPTION 35, 36, 87
CAST 44 WITH COMPARISONS 45
catalog tables 56 WITH HOLD 84, 85, 89
COMMENT ON 47 WITH HOLD, 85
DROP 47 workfile 81, 82, 83, 86
EXECUTE 44
GRANT EXECUTE ON 47 Z
privileges 46 ZPARM 202
USAGE 44
UNDO record 79
UNICODE 90, 213
UNION 127, 136, 142
UPDATE statement 139
UNION ALL 136
union everywhere 136
basic predicates 137
EXISTS predicates 138
explain 142
IN predicates 139
INSERT statements 139
nested table expressions 136
quantified predicates 137
subqueries 137
UPDATE statements 140
views 140
UNIQUE 83
unit of work 11, 84
UNLOAD 205, 212, 213
comparing 215