User login

Syndicate

Syndicate content

Recent blog posts

DB2 SQL PL - MESSAGE_TEXT

When you want SP to return message_text and you also want to handle error code as per your choice, follow this simple example.

CREATE PROCEDURE myproc(
        OUT v_msg VARCHAR(300))
LANGUAGE SQL
BEGIN
   DECLARE V_ERROR INTEGER DEFAULT 0;
   DECLARE CONTINUE HANDLER FOR SQLEXCEPTION 
      BEGIN
         GET DIAGNOSTICS EXCEPTION 1 V_MSG = MESSAGE_TEXT;
         SET V_ERROR = SQLCODE;
      END;

   SET v_msg = '';

   -- DO SOME SQL that throws the error and do conditional processing but want to see detailed 
   -- error message so that you can see what went wrong.

   IF V_ERROR < 0 THEN
      RETURN -6786; -- Code that want to process later on return through application
   END IF;
END

The OUT parameter v_msg will give you detailed error message that DB2 will throw and you handled error as per your condition code.

New Exciting Features in DB2 9.7

This is by no means an exhaustive list of new features in DB2 9.7. But, it is the list of features that are most important to me as a DBA or a developer.

Online Schema Changes

See Burt's article on Online Schema changes.

CGTT - Created Global Temporary Table

Now, it is possible to create GLOBAL TEMPORARY TABLE so that they are in catalog instead of them only declarabale in a session. This has several benefits including - 1. CGTT under authorization controls, 2. Views, indexes and triggers can be defined on CGTT, 3. CGTT and DGTT can contain LOBS.

CREATE GLOBAL TEMPORARY TABLE DGTT_TABLE
(
   EMP_ID INTEGER,
   HIRE_DATE TIMESTAMP(3)‏
)‏

Parameter Markers

Parameters in a procedure can have default values.
You can skip parameters with default values in a CALL statement.
Named parameters allows values to be assigned by name in any order.

SSL Connection in DB2

DB2 allows to use SSL connection between a java client and server. Server and client configurations are required for SSL connection to take place.

Server Configuration

  • Use the iKeyman GUI tool (comes with GSKit) to create a certificate database (a.k.a KeyStore file)
  • Import the server digital certificate you purchased from a certificate authority (CA) into the certificate database
  • Set the following DBM configuration parameters
    • SSL_SVR_KEYDB: Key store file
    • SSL_SVR_STASH: Stash file
    • SSL_SVCENAME: SSL port
  • Optionally, select a ciphers suite
    • SSL_CIPHERSPECS: Allowed ciphers suite
    • SSL_VERSIONS: Allowed SSL/TLS versions
  • Enable SSL communication for the instance
    • db2set DB2COMM=SSL or db2set DB2COMM=SSL,TCPIP

Client Configuration

  • Use the ikeyman GUI tool to create a signer certificate database
  • Import the public key of the certificate authority into the certificate database

Change in connection properties

      ....
      properties.put("sslConnection", "true");
      System.setProperty("javax.net.ssl.trustStore", "/home/db2inst1/client.jks");
      System.setProperty("javax.net.ssl.trustStorePassword", "myPassword");
      ....
      con = java.sql.DriverManager.getConnection(url, properties);

How SSL Handshake works?

  • Client requests an SSL connection listing its SSL version and supported cipher suites
  • Server responds with a selected cipher suite
  • Server sends its digital certificate to the client
  • Client verifies the validity of the server's certificate (server authentication)
  • Client and server securely negotiate a session key
  • Client and server securely exchange information using the key selected above

XML Enhancements

Several new features for XML processing has been added. Few notables are:

  • Full support of pureXML
  • XML docs in XDA object can also be compressed
  • Online reorg of XML indexes
  • XML column can be used in UDFs (Not external UDF as you will still use XML as CLOB)
  • Allow XML columns in a MDC table
  • DECOMP from query CLP command : To shred XML documents. DECOMPOSE XML DOCUMENTS IN <select_statement>
    XMLSCHEMA <xmlschemaname>
  • DECOMP from query Stored Procedure : sysproc.xdb_Decomp_XML_From_Query
  • ADMIN_IS_INLINED and ADMIN_EST_INLINE_LENGTH functions can be used to know if XML or LOB is inlined and what is the length.

Examples of DECOMP:

DECOMPOSE XML DOCUMENTS IN 'SELECT DOCID, SALESDOC 
FROM ABC.SALESTAB' XMLSCHEMA ABC.SALES 
MESSAGES /home/myid/errors/errorreport.xml 

CALL XDB_DECOMP_XML_FROM_QUERY ('ABC', 'SALES', 
'SELECT DOCID, SALESDOC FROM ABC.SALESTAB',0, 0, 0, NULL, NULL, 1,  
:hnumInput, :hnumDecomposed, :herrorreportBuf);

Note: XDA = object where XML documents are stored that are not inlined in the base table

CLPPLUS vs SQL*Plus

DB2 has an equivalent of Oracle's SQL*Plus which is known as CLPPlus. If you know how to use SQL*Plus, you will find it almost same as SQL*Plus to use.

PL/SQL in DB2

This sounds the most promising new feature of DB2 where you can use PL/SQL of Oracle in DB2. But, remember Oracle has more than 20 years of development in PL/SQL so there will still be some missing functionality but it will come in the product as time passes and based upon demands from people.

The PL/SQL compiler in DB2 is native and not some form of masking layer that will translate PL/SQL to DB2 SQL PL. This allows PL/SQL code to be profiled and source level debugging can be done.

List of DBMS packages supported:

  • DBMS_OUTPUT
  • UTL_FILE
  • DBMS_ALERT
  • DBMS_PIPE
  • DBMS_JOB
  • DBMS_LOB
  • DBMS_SQL
  • DBMS_UTILITY
  • UTL_MAIL
  • UTL_SMTP

List of PL/SQL Features supported:

  • All language logic supported like IF, WHILE := etc
  • EXCEPTION - Try/catch handling
  • User Defined Exceptions - Define conditions with or without SQLCODEs
  • Constant variables - Variables that cannot be set
  • FOR over range - Step through numbers
  • FOR over SELECT - Step through result set of query
  • FOR over cursor - Step through result set of cursor
  • %TYPE - Anchored scalar data types
  • %ROWTYPE - Anchored row types
  • BULK COLLECT/FETCH - Aggregate result set into array
  • FORALL - Pipe array into SQL statement
  • AUTOMONOUS transaction - Executes a procedure in an independent TX
  • Anonymous block
  • Scalar function
  • Procedure
  • Package
  • Trigger
  • SYNONYM ON PACKAGE

List of Oracle Types in DB2

  • NUMBER
  • VARCHAR2 - NULL = '', trailing blank sensitive collation
  • TIMESTAMP(n) - 0 (date + time) <= N <= 12 (date + time + picoseconds)
  • DATE - Year to seconds, SYSDATE
  • BOOLEAN - In procedural code
  • INDEX BY - In procedural code
  • VARRAY - In procedural code
  • Row Type - In procedural code
  • Ref Cursor - Allows passing, and predefining of cursors

Partial List of important Oracle functions in DB2

  • TO_CHAR, TO_DATE, TO_TIMESTAMP, TO_NUMBER, TO_CLOB
  • EXTRACT, ADD_MONTHS, ...
  • INITCAP, RPAD, LPAD, INSTR, REVERSE, ...
  • DECODE, NVL, LEAST, GREATEST, BITAND

List of Oracle SQL in DB2

  • CONNECT BY
  • (+)-join
  • DUAL
  • ROWNUM
  • NEXTVAL/CURRVAL
  • MINUS
  • Unnamed inline views
  • TRUNCATE table
  • Public synonym
  • CREATEd temp table

Separation of duties

Gone are the days when SYSADM used to be GOD in DB2. Not any more with separation of duties in place now.

  • With the advent of SECADM (database level), implicit DBADM has been removed from SYSADM
  • SECADM can be granted to ROLE and GROUPS now
  • SECADM can now grant/revoke database and object auths
  • DBADM can be setup in such a fashion so that it can not see data and do grant/revoke
  • DBADM does not get secondary grants implicitly
  • New Authorities are created such as EXPLAIN, DATAACCESS, ACCESSCTRL, SQLADM, WLMADM

Automatic Storage

Several new features are added to automatic storage in DB2. Few notable are:

  • You can alter a database to add additional storage paths but they are used when existing storage paths become full. What if you want to strip data across all 4 storage paths immediately to gain parallelism. You use ALTER TABLESPACE myts REBALANCE to rebalance the data. You can use ALTER TABLESPACE myts REDUCE command to reduce the high water mark.
  • You can DROP a storage path by using ALTER DATABASE DROP STORAGE PATH ON 'path1'. The storage path is changed to DROP PENDING and all table spaces are marked as An underlying storage Path(s) has been dropped. The DROP PENDING storage path is removed when all table spaces using it are rebalanced using ALTER TABLESPACE ts1 REBALANCE
  • ALTER TABLESPACE ts1 REDUCE command will free trapped spaces which did not allow high water mark to come down in earlier DB2 versions.
  • You can convert NON AUTOMATIC STORAGE database to AUTOMATIC STORAGE (AS) database.
    • ALTER DATABASE ADD STORAGE is supported on non-AS databases
    • Above command adds a storage pool for the database
    • This allows creation of new table spaces with automatic storage
  • You can convert existing non-AS DMS table space to an AS table space
    • ALTER TABLESPACE … MANAGED BY AUTOMATIC STORAGE
    • The new growth comes from new storage paths added to the database
    • Old containers can be removed via DROP or REBLANCE.
  • REDIRECTED RESTORE can be done from old DMS to new table space with automatic storage.

ONLINE TABLE MOVE

The online table move can be used in several circumstances as mentioned below:

  • You want to do an ONLINE REORG or REDISTRIBUTE
  • Want to do ONLINE TABLE compression
  • Change pagesize ONLINE
  • Online conversion to LARGE table space
  • Move data/index/long data to new/different tablespaces
  • Add or remove column, change column data type, enlarge columns
  • Add / change MDC dimensions, range partitioning or partitioning key

The ONLINE TABLE move is accomplished by system stored procedure ADMIN_MOVE_TABLE(). This stored procedure allows to move data from an existing table to a new table object. When the data is moved, access to the source table is still available for SELECT, INSERT, UPDATE and DELETE operations.

This stored procedure has 4 phases.

  1. INIT: Select copy index, create triggers, create target and staging tables. Triggers are created to capture changes to the source table.
  2. COPY: Row by row copy (over index) of rows from source to target. LOAD may also be used optionally.
  3. REPLAY: Rows captured in the staging table are re-copied from the source table. It may require multiple iterations, depending on transaction volume.
  4. SWAP: Ready target table for swap – create indexes, statistics, optional REORG, etc. Source table is locked in share mode for final replay. Source table is renamed, target table renamed to original source name.

COMPACT MDC Tables

The MDC tables in DB2 are very unique and normally they do not require REORG if no DELETE are happening. The DELETE operation can make a MDC table sparse and how to get those unused pages back to the table space so that they can be used by other tables.

REORG TABLE mdctable RECLAIM EXTENTS ONLY

The above command is very fast as it is really not a reorg but marks empty blocks as unallocated in MDC table's block map and in table space SMPs.

Inline LOBS

Take advantage of compression by inlining LOBS. This is how this can be done.

CREATE TABLE … EMP_PHOTO BLOB(10MB) INLINE LENGTH 20000;
ALTER TABLE … ALTER COLUMN EMP_PHOTO SET INLINE LENGTH 20000;

LOBs greater than inline length will be stored on LOBS table space but others will be stored inline alone with regular table data in the data page thus making it eligible for the table compression. There can be a mix of inline or non-inline LOBS in a table.

DB2 can still put LOBS inline even if no INLINE LENGTH is set. It will happen when a LOB's actual length is less than the size of its descriptor.

Local Range Partition Indexes

DB2 had global indexes on range partition tables. Now, you can have local indexes on range partition tables leading to faster Roll-in and Roll-out of the data.

C:\>clpplus db2admin/password@localhost:50001/SAMPLE
Database Connection Information

Hostname = localhost
Database server = DB2/NT  SQL09070
SQL authorization ID = db2admin
Local database alias = SAMPLE
Port = 50001

CLPPlus: Version 1.0
Copyright (c) 2009, IBM CORPORATION.  All rights reserved.

SQL> create tablespace tbsp1;
DB250000I: The command completed successfully.
SQL> create tablespace tbsp2;
DB250000I: The command completed successfully.
SQL> create tablespace tbsp3;
DB250000I: The command completed successfully.
SQL> create tablespace tbsp4;
DB250000I: The command completed successfully.
SQL> create tablespace tbsp5;
DB250000I: The command completed successfully.
SQL> create tablespace tbsp6;
DB250000I: The command completed successfully.

SQL> CREATE TABLE TAB1(c1 INT)
  2  PARTITION BY RANGE (c1)
  3  (STARTING FROM (1) ENDING (50) IN tbsp1 INDEX IN tbsp2,
  4  ENDING(100) IN tbsp3 INDEX IN tbsp4,
  5  ENDING(150) IN tbsp4 INDEX IN tbsp6);

DB250000I: The command completed successfully.
   
SQL> CREATE INDEX IDX1 ON TAB1(c1) PARTITIONED;
DB250000I: The command completed successfully.

You can do partition level INDEX REORG, partition level TABLE REORG but RUNSTATS is still operates on TABLE level.

When you do an ATTACH and required indexes are already created, it is a very quick linked-in to the main table otherwise indexes will be created automatically. DETACH unlinks the partition from the main table quickly and it becomes just a regular table.

New Currently Committed (CC) Isolation

It is just a variation of CURSOR STABILITY (CS) isolation level of DB2 where writers do not block readers. It is just to please people who are very fond of Oracle's snapshot based concurrency model. Oracle implements the theory of writers not blocking readers with snapshot technology whereas DB2 uses regular LOGS to implement this.

The CC isolation level is the new DEFAULT isolation level instead of old CS isolation level. DB2 can do full lock avoidance with CURRENTLY COMMITTED isolation level thus requiring no application level change in the application if porting from Oracle to DB2. CC is based on data available on the logs. DB2 first goes to the log buffer to find the data. Since the updating transaction is still active, probably the data is still in the log buffers. Otherwise, it goes to the log files on disk. This type of lock avoidance does not need ROLLBACK SEGMENTS as required by Oracle since DB2 uses LOG BUFFER records or at most going back to LOGS on disks.

Statement Concentrator

Do you remember that Oracle used CURSOR_SHARING=YES to convert literals into parameter markers to avoid repeated compilation of SQL statements? Do you also remember lots of pain associated with this approach to have sub-optimal plan generated by Oracle optimizer? I remember people hated this feature in Oracle due to serious performance problems but DB2 has implemented this feature with some serious thought to avoid sub-optimal plan which can be avoided with the use of REOPT parameter for STATIC or DYNAMIC statements. Consider using DB2 Statement Concentrator with REOPT for applications that are notorious to use literals across the board.

TEMP TABLE Compression

The temp tables are compressed automatically if deep compression is licenced. There is no additional step required by the user to use the TEMP TABLE compression feature.

Index Compression

You can now compress indexes in DB2 and you do it by adding COMPRESS YES in CREATE INDEX or ALTER INDEX syntax.

Query to estimate compression index savings:

SELECT index_name, pages_saved_percent, compress_attr, index_compressed 	
FROM TABLE SYSPROC.ADMIN_GET_INDEX_COMPRESS_INFO
('T', 'myschema', 'T1', '', '')) AS T

How much space was saved by using index compression?

After compressing index, rerun above query to find out actual saving.

ALTER INDEX index1 COMPRESS YES
REORG INDEXES ON TABLE t1
RUNSTATS ON TABLE t1

Unused indexes, tables and packages in DB2

Starting version DB2 9.7, you can use following query to detect unused indexes in DB2.

SELECT INDSCHEMA, INDNAME, TABNAME 
  FROM SYSCAT.INDEXES
WHERE  LASTUSED = '01/01/0001';

The column LASTUSED tells when that index was last accessed in DB2. This is not per se a guaranteed solution that will show right value in LASTUSED column when you query it since DB2 will figure out when to update this column on its own. However, you can use db2pd tool to know definitely when an index was last used since database was activated.

This query will work only in DB2 V9.7 or higher but you can still use db2pd tool for other versions. The LASTUSED column has been added to SYSCAT.TABLES, SYSCAT.INDEXES and SYSCAT.PACKAGES. So, you will be able to figure out unused tables, indexes and packages starting DB2 9.7 using simple queries rather than interpreting output from the db2pd tool.

$ db2pd -d sample -tcbstats index

When you run db2pd tools for SAMPLE database, use tcbstats option and provide index argument to it. You will see a long output but you need to look for a column SCANS when you see TCB Index information. Again, you have to co-relate the Index ID (IID) to the index name through the catalog tables.


Database Partition 0 -- Database SAMPLE -- Active -- Up 0 days 00:09:45

TCB Table Information:
Address    TbspaceID TableID PartID MasterTbs MasterTab TableName
0x7C6EF8A0 0         1       n/a    0         1         SYSBOOT  
0x7A0AC6A0 2         -1      n/a    2         -1        INTERNAL 

TCB Table Stats:
Address    TableName          Scans      UDI        RTSUDI
0x7C6EF8A0 SYSBOOT            1          0          0     
0x7A0AC6A0 INTERNAL           0          0          0     

TCB Index Information:
Address    InxTbspace ObjectID TbspaceID TableID MasterTbs 
0x7A0ABDA8 0          5        0         5       0         
0x7A0ABDA8 0          5        0         5       0         

TCB Index Stats:
Address    TableName          IID   EmpPgDel   RootSplits BndrySplts PseuEmptPg Scans   
0x7A0ABDA8 SYSTABLES          9     0          0          0          0          0       
0x7A0ABDA8 SYSTABLES          8     0          0          0          0          0       

The output above is trimmed for brevity. So, relate IID to the index name and look for the indexes having Scans=0.

If your database has been active for a month, you can run the db2pd tool and find out the indexes which have not been scanned in last one month.

All of this information is only transient and available from the time your database was activated when you use db2pd tool. However, the LASTUSED column in SYSCAT.TABLES, SYSCAT.INDEXES and SYSCAT.PACKAGES will persist the information and it is meant to give you an idea when that object was last accessed. Please remember that DB2 on z/OS provided this capability for a long time and DB2 LUW is now providing same capability.

Online Schema Changes - The Easy Way

I am going to show you an easy way to do online schema changes in DB2. The best to learn things is by doing some simple examples. My intent is to make you an expert in 1/2 hour if you learn the following concepts.

  • Create a database with and without automatic storage enabled
  • Add automatic storage to a database that does not have it enabled
  • Convert a DMS table space to use automatic storage that was not previously using it
  • Use DB2 administration views to check for automatic storage status
  • Set up your database for automatic revalidation of objects
  • Use create with error (create objects that reference other objects that do not yet exist and marking them invalid)
  • Revalidate invalid objects at access time
  • Alter a table to change data types, change column names and to drop a column
  • Show how a single alter table commands forces a table reorg if you wish to write to it
  • Show how 3 alter table commands force a table reorg to be able to do more alter table commands
  • Show how to use administration views to see invalid objects

Prerequisite

  1. Install DB2 9.7 on Windows if you do not already have it.
  2. Download scripts from here
  3. Unzip files in C:\

New automatic storage features

Adding automatic storage to a database

1. Open a Windows Command prompt

2. Change your directory to: C:\COBRA_LAB_SCRIPTS\SCHEMA


3. To open a DB2 CLP window, type: db2cmd


4. In the Windows Explorer, review command script SCHEMA01.CMD (Right click-> Edit. Do not double click!)



5. Notice this script calls SCHEMA02.DDL. Review this script now. Notice this creates a database called SCHEMADB without automatic storage. It also creates a DMS table space that is of course not using automatic storage either.



6. Execute script SCHEM01.CMD.

7. The script takes about 90 seconds to create the database. Wait for the script to finish before trying to run any more commands from the CLP. While this is running, review the SCHEMA03.SQL script to see how the administrative view works that we will be running when this finishes. SCHEMA03.SQL checks to see if any table spaces are using automatic storage in our database.



8. When the first scrip finishes, execute the SCHEMA03.SQL script this way:

db2 -td; -vf SCHEMA03.SQL

9. When it is finished, notice the state of all the table spaces in our database:


10. Another way to verify that automatic storage is not enabled in this database is to try to create a table space using automatic storage. To do this, type the following in the DB2 CLP window:

db2 create tablespace new_ts

Note: We can run the above command because the previous script connected to the schemadb database for us. Since we have stayed in our CLP window we are still connected. If you accidentally close your CLP window, you might need to connect to the database again. To do this, use command “db2 connect to schemadb”.



11. The SQL20317N error message states that automatic storage has not been defined for this database. This is what some DB2 customers encounter in real life situations when the DBA either forgot to enable automatic storage or thought they did not need it and then turned out later they did need it after all. Prior to Cobra, there was nothing you could do about this except to backup the database and do a redirected restore to a newly created database with automatic storage enabled. Painful.

12. Another way you can check to see if your database is using automatic storage by running this command that uses a DB2 administration view.

db2 select db_storage_path from sysibmadm.snapstorage_paths

13. 0 records are selected because there are no storage paths for your database. A database storage path is the path (or paths) defined when you enable automatic storage.

14. DB2 now makes it easy to enable automatic storage. Let’s do that now. From the DB2 CLP window, enable automatic storage by typing the following:

db2 alter database add storage on ‘C:’

15. Make sure you use single quotes as shown.

16. You should get a message “SQL Command Completed Successfully”. You have now enabled automatic storage for this database with one easy command!

17. To check it, issue this command again. (hint: use up arrow key twice.)

db2 select db_storage_path from sysibmadm.snapstorage_paths

18. Your query returns one storage path, which is C:

19. Now, try to create a table space using automatic storage again. (hint: use up arrow key)

db2 create tablespace new_ts

20. This command is successful because we can now use the automatic storage feature in our database. Verify this table space creation by running the script that lists the table spaces and their automatic storage usage. (Again, use up arrow key.)

db2 -td; -vf SCHEMA03.SQL

So, we verified that NEW_TS was created using automatic storage.

Adding automatic storage to a table space

21. Now we want to see if there are any DMS table spaces that we can convert to automatic storage that are not using it. Run this SQL, which uses a DB2 administration view to see this information. (Hint: use the up arrow key to bring your last command back and change the script name.)

db2 -td; -vf SCHEMA04.SQL

22. Your output shows table space called SYSTOOLSPACE is DMS and does not use automatic storage



23. Let’s list the containers for these two table spaces. Use this command which will call SQL that uses another DB2 administration view.

db2 -td; -vf SCHEMA05.SQL

24. Notice that NEW_TS is using the automatic storage path and SYSTOOLSPACE is using a default DMS path.



25. We can convert table space SYSTOOLSPACE to use automatic storage with the following command:

db2 alter tablespace systoolspace managed by automatic storage

26. You have now added the automatic storage path to this table space. To see this run this command again:

db2 -td; -vf SCHEMA05.SQL

27. Notice that SYSTOOLSPACE is using both its old container and the new automatic storage container.


28. DB2 will keep this scenario and use both containers unless you force it to do otherwise. Use this command to force a rebalance:

db2 alter tablespace systoolspace rebalance

29. When the rebalance is done, check your containers again by using this script one last time:

db2 -td; -vf SCHEMA05.SQL

30. Notice now that SYSTOOLSPACE only uses the automatic container.



31. You can also check to see if SYSTOOLSPACE is using automatic storage by running this script again:

db2 -td; -vf SCHEMA04.SQL

32. Notice SYSTOOLSPACE is now using automatic storage

Automatic Object Revalidation

1. At the heart of the online schema changes capability, is a new database configuration parameter called AUTO_REVAL. In order to let DB2 defer the checking of the validation of objects, set this parameter with this command:

db2 update db cfg using auto_reval deferred_force

2. Execute this command to check if this is set (note: this is case sensitive):

db2 get db cfg | FindStr AUTO_REVAL



3. Use the Windows Explorer to review script SCHEMA10.CMD. (Right click-> Edit.) See that it calls SCHEMA11.DDL. Review SCHEMA11.DDL carefully.



4. First, notice DB2 will perform the CREATE OR REPLACE functionality. This is an important online schema change ability. It means that we can avoid using DROP in order to recreate a view which preserves grants to the view.

5. Second, notice also that the view references a function not yet created, and then the function references a table not yet created. Won’t this DDL fail if it is attempted to be run in this order? Normally, yes, but with AUTO_REVAL set as we did earlier, it will work.

6. Run SCHEMA10.CMD by typing its name:

SCHEMA10.CMD

7. Review the output file SCHEMA10_OUTPUT.TXT. Notice that the view is created, but marked invalid. The function is also created, but marked invalid. This is referred to as create with error.



8. Keep reviewing this output file. Notice that the view and function are both auto revalidated when referenced later with the SELECT. Notice that the SELECT is from the view, which uses the function, which references the table. Everything revalidates and works just fine!


NOTE: The table, function and view created here can be deleted using script SCHEMA49.DDL. But only use this if you are playing with this feature after the lab is complete. Don’t use it now.

Altering a table and keeping it online

Multiple table alters in a script

1. Next, we will alter our table SCHEMA_CHANGE_TB to see how DB2 handles doing table alters. Review both command scripts: SCHEMA12.CMD which executes SCHEMA13.DDL. (Use right click -> Edit)




2. Notice that the column called salary will change name to salary_amount.

3. Notice that the column called dept will change from an SMALLINT to a CHAR(10)

4. Notice that column ssn is dropped.

5. Also notice the script has a REORG at the end of it. We’ll explain why later.

6. Run this script to see how the alter works:

SCHEMA12.CMD

7. Review file SCHEMA12_OUTPUT.TXT. Notice the alter commands succeed and the table stays read accessible.

Alter table #1

8. So, how did this alter work? Let’s look more closely at altering column data types to understand what DB2 is doing. Use this command to do our first alter to our table:

db2 alter table schema_change_tb alter column dob set data type timestamp

9. The command is successful. What does this mean for our table? Let’s try to select from it:

db2 select * from schema_change_tb fetch first 1 rows only

10. The command is successful. What does this mean for our table? Let’s try to select from it:



11. Notice the DATE column is now TIMESTAMP. The ALTER worked just fine and so did the SELECT after it was altered.

12. Try an update to the table. Use this command:

db2 update schema_change_tb set job_code = ‘WORKR’ where job_code=’WKR’

13. Oops. We are not allowed to do a write to the table. UPDATES and INSERTS will not work while this table is in this state. We will have to do a REORG of the table to make it write accessible. We’ll do this later in the lab.


14. Recap: We can read the table after the alter data type, but we cannot write to it.

Alter table #2

15. Now, let’s experiment with altering this table some more (hint: use your up arrow key and make changes to your previous script…)

db2 alter table schema_change_tb alter column job_code set data type integer



16. Notice, DB2 does not allow this. You have character data in the job_code column and DB2 knows it cannot be changed to integer data. If your job_code column had only ‘1’, ‘2’, ‘3’, ‘555’, etc. then this command could be done because DB2 could cast that data to an integer.

17. Now try this command (hint: use your up arrow key again…)

db2 alter table schema_change_tb alter column job_code set data type char(1)



18. DB2 does not allow this either and gives you the same error message. It knows that even though the source and target columns are both CHAR, there is data in the table that would be truncated if it reduced the column size, so it will not let you do this operation.

19. Try this command:

db2 alter table schema_change_tb alter column job_code set data type char(10)

20. This worked. This is the second successful alter command to this table in this series. Column JOB_CODE was originally data type CHAR(4), so moving this data into a larger character column CHAR(10) is OK.

Alter table #3

21. Try this command:

db2 alter table schema_change_tb alter column job_code set data type varchar(20)

22. This succeeded too.

23. DB2 can move data within compatible data types and the target data type is larger than the source, so the data fits nicely and the command is successful.

24. This is the third successful alter command in this series.

25. Try to do one more alter table command:



26. DB2 gives us a new error message: an invalid state error code. What this means is that DB2 only allows you three successful sessions of altering the same table before it requires a REORG on that table.

27. To be able to perform writes to an altered table or to be able to perform more alters on this table, use this command:

db2 reorg table schema_change_tb

28. When DB2 returns that “the reorg command completed successfully”, then you can do more alters to that table.

29. Now try this same command again (hint: use up arrow)

db2 alter table schema_change_tb alter column job_code set data type char(5)

30. The command is successful now for two reasons:

  • We performed a reorg on the table, which restarts the 3 alter limit
  • We are allowed to truncate the table to a smaller column size because DB2 knows that the largest data value in that column is 4 bytes (what it was originally.) Even though that column was expanded to CHAR(10) and later to VARCHAR(20), the new room we gave this column was not used. Had we inserted data of more than 5 characters after growing the column size, then we would not have room to shrink this column to CHAR(5) later.

31. Now make sure you reorg this table so you can write to it:

db2 reorg table schema_change_tb

32. Now, try writing to the table (use up arrow key to bring this command back)

db2 update schema_change_tb set job_code = ‘WORKR’ where job_code=’WKR’

33. The command succeeds because we did a reorg on the table after the alter.

Revalidating invalidated objects

34. So, how does all of this altering of our table affect other related objects? If you want to check if this has invalidated anything, use this command:

db2 select objectname from syscat.invalidobjects



35. Notice the view that references this table has been made invalid. If you want to try to revalidate this view, simply access it:

db2 select * from schema_change_supervisors_vw



36. We have a problem. The column SALARY is referenced in our view, but we changed it earlier to SALARY_AMOUNT. Auto revalidation did not work in this case because the view definition is not able to work with the altered table as-is.

37. We will have to fix our view. Review and run script

db2 -td; -vf SCHEMA14.DDL



Our view has been fixed and it runs fine.



38. Now check the invalid objects catalog view again:

db2 select objectname from syscat.invalidobjects


A word about SOFT invalidation: Soft invalidation allows for you to drop an object, even when running transactions are using it. The Cobra default for soft invalidation is ON. If you wish to turn this off and require an exclusive lock on an object in order to drop it, set the following registry variable like this:
db2set DB2_DDL_SOFT_INVAL=OFF

Java Program Name for DB2

Finally DB2 implemented a long standing demand for setting the Java application name so that you could see the name through DB2 utilities instead of seeing db2jcc_applica.

You set a connection property clientProgramName to your chosen name and the db2 utilities will pick up this name instead of the default name of db2jcc_application.

java.util.Properties conProp = new java.util.Properties();
String url = "jdbc:db2://localhost:50000/GSDB";
Class.forName("com.ibm.db2.jcc.DB2Driver");
conProp.put("user", "myUserName");
conProp.put("password", "myPassword");
conProp.put("clientProgramName", "MyProgram");
connection = DriverManager.getConnection(url, conProp);

When you list the application, you will see the name that you set.

C:\Documents and Settings\Administrator>db2 list applications
Auth Id  Application    Appl.      Application Id                      DB     #
-------- -------------- ---------- ------------------------------ -------- ----
DBAPOT   db2jcc_applica 993        127.0.0.1.48648.090430221610       GSDB     1
DBAPOT   MyProgram      997        127.0.0.1.50184.090430221627       GSDB     1
DBAPOT   db2jcc_applica 996        127.0.0.1.49416.090430221613       GSDB     1
DBAPOT   db2jcc_applica 995        127.0.0.1.49160.090430221612       GSDB     1
VIKRAM   db2bp.exe      988        *LOCAL.DB2.090430221558            GSDB     1
DBAPOT   db2jcc_applica 994        127.0.0.1.48904.090430221611       GSDB     1

DB2 LUW Automatic Memory Management Configuration Tips on LINUX Servers

There are DB2 LUW configuration parameters that can be set to AUTOMATIC and managed by the DB2 automatic memory manager (STMM). One advantage of this is that DB2 will automatically adjust the memory it uses to get the work done based on the characteristics of the current work load and it will continue to adjust itself dynamically as processing continues. Below is a list of configuration parameters for DB2 V9.5 that can be set to AUTOMATIC.

Here is a list of database manager (instance) configuration values for a partitioned environment that can be set to AUTOMATIC. Note that the SHEAPTHRES can be set to 0 and this combined with other database level configuration parameters can result in DB2 sort memory being managed by the automatic memory manager.

db2 attach to db2inst1

db2 get dbm cfg show detail

Database monitor heap size (4KB) (MON_HEAP_SZ) = AUTOMATIC(4096)
Size of instance shared memory (4KB) (INSTANCE_MEMORY) = AUTOMATIC(889708)
Agent pool size (NUM_POOLAGENTS) = AUTOMATIC(0)
Max number of coordinating agents (MAX_COORDAGENTS) = AUTOMATIC(2000)
Max number of client connections (MAX_CONNECTIONS) = AUTOMATIC(MAX_COORDAGENTS)
Number of pooled fenced processes (FENCED_POOL) = AUTOMATIC(MAX_COORDAGENTS)
No. of int. communication buffers(4KB)(FCM_NUM_BUFFERS) = AUTOMATIC(4096)
No. of int. communication channels (FCM_NUM_CHANNELS) = AUTOMATIC(2048)
Sort heap threshold (4KB) (SHEAPTHRES) = 0

Here is a list of database configuration values that can be set to AUTOMATIC. Note that the CATALOGCACHE_SZ may be set to -1 and that this results in it’s memory size being derived by the formula (MAXAPPLS*4). If MAXAPPLS is set to AUTOMATIC then the databases catalog cache memory size is indirectly managed by DB2’s automatic memory manager.

db2 connect to db2db1

db2 get db cfg for db2db1

Size of database shared memory (4KB) (DATABASE_MEMORY) = CALCULATED
Max storage for lock list (4KB) (LOCKLIST) = AUTOMATIC(12097)
Percent. of lock lists per application (MAXLOCKS) = AUTOMATIC(98)
Package cache size (4KB) (PCKCACHESZ) = AUTOMATIC(3465)
Sort heap thres for shared sorts (4KB) (SHEAPTHRES_SHR) = AUTOMATIC(152076)
Sort list heap (4KB) (SORTHEAP) = AUTOMATIC(3041)
Database heap (4KB) (DBHEAP) = AUTOMATIC(20000)
SQL statement heap (4KB) (STMTHEAP) = AUTOMATIC(4096)
Default application heap (4KB) (APPLHEAPSZ) = AUTOMATIC(2000)
Application Memory Size (4KB) (APPL_MEMORY) = AUTOMATIC(6928)
Statistics heap size (4KB) (STAT_HEAP_SZ) = AUTOMATIC(6600)
Number of asynchronous page cleaners (NUM_IOCLEANERS) = AUTOMATIC(1)
Number of I/O servers (NUM_IOSERVERS) = AUTOMATIC(18)
Default prefetch size (pages) (DFT_PREFETCH_SZ) = AUTOMATIC
Max number of active applications (MAXAPPLS) = AUTOMATIC(430)
Average number of active applications (AVG_APPLS) = AUTOMATIC(1)
Catalog cache size (4KB) (CATALOGCACHE_SZ) = (MAXAPPLS*4)

Change self tuning memory to off and STMM is off. If you do not change the configuration values then they are set to the last value STMM used.

Self tuning memory (SELF_TUNING_MEM) = ON (Active)

LINUX Configuration Tip: If you want to use the DB2 LUW Automatic Memory Manager feature on a LINUX server then the maximum segment size (SHMMAX) should be configured to the same size as the maximum total shared memory (SHMALL) and the maximum total shared memory (SHMALL) should be configured to the total physical memory on the server.

DB2 Configuration Tip: If the maximum segment size is less than the maximum total shared memory for LINUX then you should avoid using AUTOMATIC on configuration parameters that manage the amount of shared memory that DB2 will use or you will experience problems if DB2 exceeds these limits. The same is true for the maximum total shared memory if it is set to something smaller than the maximum physical memory on the server. The SHMMAX and SHMALL LINUX configuration values limit the amount of shared memory DB2 can use on a LINUX server. If DB2 is configured to exceed these limits or if the DB2 automatic memory manager adjusts DB2 shared memory so that it exceeds these limits then LINUX will stop the DB2 threads when these limits are exceeded.

Here is the formula to use when configuring DB2 to run within the max segment memory size of an operating system like LINUX.

(util_heap_sz * 4096) +
(dbheap * 4096) +
(catalogcache_sz * 4096) +
(locklist * 4096) +
(pckcachesz * 4096) +
(bufferpool_1 * bufferpool_page_size) +
(bufferpool_n * bufferpool_page_size)
<= LINUX maximum segment size (SHMMAX)

You can determine the current values of the DB2 database utility heap size, database heap, catalog cache, lock list and package cache by connecting to the database and running the db2 get database configuration command with the show detail option.

db2 connect to db2db1

db2 get db cfg for db2db1 show detail

You can determine the current values for the DB2 bufferpools in a database by running the SQL select statement listed below. If you see a -2 value then the bufferpool is configured to have it’s memory managed by DB2’s automatic memory manager. Note, that size in bytes of memory for a bufferpool may be calculated by multiplying page size times the number of pages (PAGESIZE * NPAGES).

db2 -v "select substr(bpname,1,15) as bpname,pagesize,npages from syscat.bufferpools order by bpname"

select substr(bpname,1,15) as bpname,pagesize,npages from syscat.bufferpools order by bpname

BPNAME PAGESIZE NPAGES
--------------- ----------- -----------
BPTBL32K 32768 40960
BPTMP04K 4096 1000
BPTMP08K 8192 1000
BPTMP16K 16384 1000
BPTMP32K 32768 4395
IBMDEFAULTBP 16384 19000

In order to determine how a LINUX server is configured run the command ipcs –l.

------ Shared Memory Limits --------
max number of segments = 4096
max seg size (kbytes) = 262144 SHMMAX
max total shared memory (kbytes) = 8388608 SHMALL
min seg size (bytes) = 1

------ Semaphore Limits --------
max number of arrays = 1024
max semaphores per array = 250
max semaphores system wide = 32000
max ops per semop call = 32
semaphore max value = 32767

------ Messages: Limits --------
max queues system wide = 1024
max size of message (bytes) = 65535
default max size of queue (bytes) = 65536

If you multiply the SHMMAX size times 1024 (Kbytes) then you will have the number of bytes available for a segment on the LINUX server. If this matches the SHMALL value and if the SHMALL value matches the total physical memory on the server then you are free to use AUTOMATIC for DB2 LUW database configuration values for utility heap size, database heap, catalog cache, lock list, package cache and bufferpools. If SHMMAX is less than SHMALL or if SHMALL is less than the physical memory available on the LINUX server then you should configure utility heap size, database heap, catalog cache, lock list, package cache and bufferpools so that the total bytes of shared memory they use is equal to or less than the LINUX segment maximum limit of shared memory limit, which ever is smaller.

You can set up a spreadsheet with formulas that will help you make sure that your DB2 LUW database configuration and bufferpool configuration will be equal to or less than the max seg size limit on the LINUX server. Here is an example of one way you could do this.

Build an INSERT statement

Use this DB2 Stored Procedure to build an INSERT statement. This is very useful when you are developing some Java or .Net program or any other program and need an easy way to do this.

Thanks to Serge Rielau of DB2 Toronto lab for sharing this with the DB2 community. You can also download the script from here.

CREATE TABLE RESULT(text CLOB(2M));

DROP PROCEDURE insertscript;

--#SET TERMINATOR @
CREATE PROCEDURE insertscript(IN ptabschema  VARCHAR(128),
                              IN ptabname    VARCHAR(128),
                              IN pbatchsize  INTEGER,
                              IN pterminator VARCHAR(2))
BEGIN
  DECLARE SQLCODE      INTEGER;
  DECLARE SQLSTATE     CHAR(5);
  DECLARE inserttext   CLOB(2M);
  DECLARE selecttext   CLOB(2M);
  DECLARE insertheader CLOB(2M);
  DECLARE colname      VARCHAR(128);
  DECLARE colcount     INTEGER;
  DECLARE colno        INTEGER;
  DECLARE rowcount     INTEGER;
  DECLARE insertcount  INTEGER;
  DECLARE colvalue     CLOB(128); 
  DECLARE typeid       SMALLINT;
  DECLARE done         SMALLINT;
  DECLARE selectstmt   STATEMENT;
  DECLARE selectcur    CURSOR WITH HOLD FOR selectstmt;
  
  IF NOT EXISTS(SELECT 1 FROM SYSCAT.TABLES 
                 WHERE tabschema = ptabschema
                   AND tabname = ptabname) THEN
    SIGNAL SQLSTATE '78000' SET MESSAGE_TEXT = 'Table not found (remember case!)';
  END IF; 
  
  DELETE FROM result;  
  SET insertheader = 'INSERT INTO "' || ptabschema || '"."'
                   || ptabname || '" VALUES ';
  
  SET selecttext = 'SELECT colno, colvalue, typeid FROM "'
                 || ptabschema || '"."' || ptabname || '" AS T, LATERAL(VALUES ';
  FOR column AS SELECT colno, colname, typename, codepage FROM SYSCAT.COLUMNS 
                 WHERE tabschema = ptabschema
                   AND tabname   = ptabname
                 ORDER BY colno
  DO
    SET typeid = CASE WHEN typename IN ('SMALLINT', 'INTEGER', 'BIGINT',
                                        'FLOAT', 'DOUBLE', 'REAL', 'DECFLOAT',
                                        'DECIMAL') THEN 1
                      WHEN typename IN ('CHAR', 'VARCHAR', 'GRAPHIC', 'VARGRAPHIC',
                                        'CHARACTER', 'CLOB', 'DBCLOB') THEN
                            CASE WHEN codepage = 0 THEN 3 ELSE 2 END
                      WHEN typename = 'BLOB' THEN 4
                      ELSE RAISE_ERROR('78000', 'Unsupported type') END; 
    SET selecttext = selecttext 
                   || CASE WHEN colno > 0 THEN ',' ELSE '' END 
                   || '(SMALLINT(' || CHAR(colno)
                   || '), ' || CASE WHEN typeid = 1
                                    THEN 'TRIM(CHAR(T."' || colname || '")), '
                                    WHEN typeid = 3 
                                    THEN 'HEX(T."' || colname || '"), ' 
                                    WHEN typeid = 4
                                    THEN 'HEX(VARCHAR(T."' || colname || '")), '
                                    ELSE 'VARCHAR(T."' || colname || '"), ' END 
                   || CHAR(typeid) || ')';
    SET colcount = colno;
  END FOR;
  SET selecttext = selecttext || ') AS S(colno, colvalue, typeid)';
  PREPARE selectstmt FROM selecttext;

  SET rowcount    = 0;
  SET insertcount = 0;
  SET done        = 0;
  OPEN selectcur;
mainloop: WHILE rowcount < pbatchsize DO
    IF rowcount = 0 THEN
      SET inserttext = insertheader;
    END IF;
      
subloop: LOOP
      FETCH selectcur INTO colno, colvalue, typeid;
      IF SQLCODE = 100 THEN 
        SET done = 1;
        LEAVE subloop; 
      END IF;
      SET inserttext = inserttext 
                     || CASE WHEN colno = 0 AND rowcount = 0 THEN '('
                             WHEN colno = 0                  THEN '), ' || CHR(10) || chr(13) || '('
                             ELSE                                 ',' END
                     || CASE WHEN colvalue IS NULL THEN 'NULL'
                             WHEN typeid = 1       THEN colvalue
                             WHEN typeid = 2       THEN '''' || colvalue || ''''
                             WHEN typeid = 4       THEN 'BLOB(x''' || colvalue || ''')'
                             ELSE 'x''' || colvalue || '''' END;
      IF colno = colcount THEN
        SET rowcount = rowcount + 1;
      END IF;
      
      IF rowcount >= pbatchsize THEN LEAVE subloop; END IF;
    END LOOP;        
    SET inserttext = inserttext || ')' || pterminator;
    IF rowcount > 0 THEN 
      INSERT INTO RESULT VALUES inserttext;
      SET rowcount = 0;
      SET insertcount = insertcount + 1;
      IF MOD(insertcount, 100) = 0 THEN
        COMMIT;
      END IF; 
    ELSE 
      LEAVE mainloop;
    END IF; 
  END WHILE;
END
@
--#SET TERMINATOR ;
CALL insertscript('myschema', 'mytab', 10, ';');

DB2 Update Statement using sub-query - Use MERGE

Thanks to Serge Rielau for insight in DB2 MERGE.

For example, if you have this Oracle UPDATE statement and wondering how can you use this in DB2. Here is the solution.

Oracle UPDATE

UPDATE 
(
      SELECT clm.c2 wc1,
             m.c2 wc2
      FROM   schema1.table1 m,  
             schema1.table2 clm
      WHERE  clm.c1 = m.c1
         and clm.date1 between to_date('10012008','MMDDYYYY') and  to_date('12312008','MMDDYYYY')
         and m.date2 between to_date('10012008','MMDDYYYY') and  to_date('12312008','MMDDYYYY')
         and m.flag = 'N'
) joined
SET joined.wc1 = joined.wc2;

DB2 Merge

MERGE INTO schema1.table1 m
  USING  schema1.table2 clm
   ON  clm.c1 = m.c1
         and clm.date1 between to_date('10012008','MMDDYYYY') and  to_date('12312008','MMDDYYYY')
         and m.date2 between to_date('10012008','MMDDYYYY') and  to_date('12312008','MMDDYYYY')
         and m.flag = 'N'
WHEN MATCHED THEN UPDATE SET m.c2 = clm.c2;

SQL Server Update - 2 table issue

update t
 set book = r.book
 from #RecLookup r
	 JOIN TABLE_TOBE_UPDATED t
		 ON t.history_date = CONVERT ( char(8) , @CobDate, 112)
			 and t.rec_id = r.rec_id
 where (r.update_options & 0x1) = 0x1 
	AND ((@MinRecId = 0 and @MaxRecId = 0x7FFFFFFF) OR 
	     (t.rec_id >= CONVERT(varchar(12),@MinRecId) 
   		 and t.rec_id <= CONVERT(varchar(12),@MaxRecId)))

DB2 using MERGE

MERGE INTO TABLE_TOBE_UPDATED T
USING  SESSION.TT_RECLOOKUP R
  ON   T.HISTORY_DATE = V_COBDATE
     AND  T.REC_ID = R.REC_ID
     AND (BITAND(R.UPDATE_OPTIONS,1)) = 1 
     AND  ((V_MINRECID = 0 AND V_MAXRECID = 2147483647) OR 
           (T.REC_ID >= V_MINRECID AND T.REC_ID <= V_MAXRECID))
WHEN MATCHED THEN UPDATE SET
   T.BOOK = R.BOOK;

Or, another MERGE. The explain plan will tell you which one is better.

MERGE INTO 
(
  SELECT REC_ID, BOOK FROM TABLE_TOBE_UPDATED
  WHERE HISTORY_DATE = V_COBDATE
  AND ((V_MINRECID = 0 AND V_MAXRECID = 2147483647) OR  
        (REC_ID >= V_MINRECID AND REC_ID <= V_MAXRECID))

) AS T
USING 
(
   SELECT REC_ID, BOOK FROM SESSION.TT_RECLOOKUP
   WHERE (BITAND(UPDATE_OPTIONS,1)) = 1 
) AS R
ON T.REC_ID = R.REC_ID 
  WHEN MATCHED THEN UPDATE SET
     T.BOOK = R.BOOK;

SQL Server UPDATE returning more than one row for each matched row

Take this example of notorious UPDATE in SQL Server where more than one row is returned for each row matched in the table_tobe_updated. This is a feature of SQL Server you realize when porting to DB2. When you try to match this in DB2, you might get -811 error.

The result of a scalar fullselect, SELECT INTO statement, or VALUES INTO statement is more than one row.. SQLCODE=-811, SQLSTATE=21000

SQL Server Update returning more than one row for each matched row

update   tmp                                                     
set                                                              
   trading_book_id = bk_Riskmap.book_id,                         
   trading_book_name = bk_Riskmap.book_name,                     
   party_book_name = bk_Riskmap.counterparty_book_name,          
   party_book_id = bk_Riskmap.counterparty_book_id,              
   cost_center = bk_Riskmap.cost_center,                         
   Risk_Reason = bk_Riskmap.risk_reason,                         
   mapping_type = (tmp.mapping_type | 0x2)                       
from  TABLE_TOBE_UPDATED tmp                                     
   JOIN (                                                        
      select book_name, edsi_id, book_hist_id = MIN(book_hist_id)
      from dbo.book_hist                                         
      where history_date = @CobDate                              
         and (options & 0x2) = 0x2            
      group by book_name, edsi_id                                
   ) UList                                                       
      ON tmp.book = UList.book_name                              
         and tmp.edsi_id = UList.edsi_id                         
   JOIN dbo.book_hist bk_Riskmap                                 
      ON bk_Riskmap.history_date = @CobDate                      
         and UList.book_hist_id = bk_Riskmap.book_hist_id        

DB2 UPDATE - USING MERGE

MERGE INTO TEMP.TABLE_TOBE_UPDATED TMP
USING
( 
   SELECT ULIST.BOOK_NAME, ULIST.EDSI_ID, MIN(ULIST.BOOK_HIST_ID) BOOK_HIST_ID    
     FROM BOOK_HIST UList                                           
     WHERE UList.HISTORY_DATE = '2009-03-31'                            
        AND BITAND(UList.OPTIONS,2) = 2                                             
     GROUP BY ULIST.BOOK_NAME, ULIST.EDSI_ID   
) AS U (BOOK_NAME, EDSI_ID, BOOK_HIST_ID)
ON TMP.BOOK = U.BOOK_NAME
  AND TMP.EDSI_ID = U.EDSI_ID
WHEN MATCHED THEN UPDATE SET                                    
   (TRADING_BOOK_ID,TRADING_BOOK_NAME,PARTY_BOOK_NAME,
    PARTY_BOOK_ID,COST_CENTER,RISK_REASON,MAPPING_TYPE) =  
(
SELECT BOOK_ID, BOOK_NAME, 
       COUNTERPARTY_BOOK_NAME,
       COUNTERPARTY_BOOK_ID, 
       COST_CENTER,
       RISK_REASON, 
       BITOR(TMP.MAPPING_TYPE, 2) 
FROM  BOOK_HIST BK_RISKMAP
WHERE  BK_RISKMAP.HISTORY_DATE = '2009-03-31'                       
AND U.BOOK_HIST_ID = BK_RISKMAP.BOOK_HIST_ID
)@

Rule of thumb: You should never have to use TARGET table in the USING clause.

Run DB2 Scripts from Java Programs

If you have DB2 scripts that you want to run through your Java programs, you normally use Runtime.getRuntime().exec method to run system commands. The following sample code tells how to run DB2 scripts.

The following code shows it for both Windows and Unix systems. The trick here is to run db2cmd with /c /i /w switches to make it working properly. For Unix systems, it assumes that you are running this program from db2 enabled shell or you have sourced your db2profile. Same way, you use ksh shell with -c switch and pass all arguments through a array variable.

Through this program, you are passing full path name of the db2 script. We also change directory to the parent of the script so that all other dependent scripts residing in the directory do not fail. This is not so much about Java programming but how to use shell capability to run multiple commands.

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.Date;

public class RunDB2Script implements Runnable
{
   private static final SimpleDateFormat timestampFormat = 
      new SimpleDateFormat("yyyy-MM-dd HH.mm.ss.SSS");
   private static String osType = (System.getProperty("os.name").toUpperCase().
         startsWith("WIN")) ? "WIN" : (System.getProperty("os.name").toUpperCase()
               .startsWith("Z/OS")) ? "z/OS" : "OTHER";  
   private String db2ScriptName = "";

   public RunDB2Script(String db2ScriptName)
   {
      this.db2ScriptName = db2ScriptName;
   }
   
   public void run()
   {
      String line = null;
      Process p = null;
      BufferedReader stdInput = null, stdError = null;

      try
      {
         File f = new File(db2ScriptName);
         String dirName = f.getParent();
         if (osType.equalsIgnoreCase("win"))
         {
            p = Runtime.getRuntime().exec("db2cmd /c /i /w " + "cd " + dirName + 
                  " && db2 -tvf " + db2ScriptName);
            stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
            stdError = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            while ((line = stdInput.readLine()) != null)
            {
               if (!(line.equals("")))
                 log(line);
            }
            while ((line = stdError.readLine()) != null)
            {
               if (!(line.equals("")))
                  log(line);
            }
         } else
         {
            String cmd[] = {"/bin/ksh","-c", "cd " + dirName + 
                  " ; db2 -tvf " + db2ScriptName};
            p = Runtime.getRuntime().exec(cmd);
            stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
            stdError = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            while ((line = stdInput.readLine()) != null)
            {
               log(line);
            }
            while ((line = stdError.readLine()) != null)
            {
               if (!(line.equals("")))
                  log(line);
            }
         }
         stdInput.close();
         stdError.close();
         p.getInputStream().close();
         p.getErrorStream().close();
      } catch (Exception e) 
      {  
         e.printStackTrace();
      }
    }
   
    private static void log(String msg)
    {
      if (osType.equals("z/OS"))
      {
            System.out.println(timestampFormat.format(new Date()) + ":" + msg);        
      } else 
      {
            System.out.println("[" + timestampFormat.format(new Date()) + "] " + msg);          
      }
    } 
}

Matt Devlin's Perl Scripts for DB2

Are you looking for Matt Devlin's Perl Scripts for DB2? Even though, they are old but still they are relevant with new release of DB2 particularly if you know how to modify them. Matt Devlin's website http://mattdevlin.net is no longer available but you can get download his scripts here.

DB2 LOAD using ASC option through MTK

If you use MTK (Migration Toolkit from IBM) to migrate your database to DB2 on Windows platform, the migration goes smooth but you may see problem when you FTP data file and LOAD script to Unix platform if you do not do it right.

For example, you chose ASC (ASCII) option since you do not have an option of specifying a right delimiter for DEL option since your data contains CR, LF, TAB, Tilde(~), vertical bar(|) etc.

A sample LOAD statement generated by MTK using ASC option might look like following:

load from datafile.out of ASC MODIFIED BY reclen=2066 codepage=1208 
nochecklengths striptblanks METHOD L 
(1 12,14 523,525 536,538 549,551 750,752 1261,1263 1462,1464 1469,1471 1492,1494 
 1505,1507 1507,1509 1534,1536 2045) NULL INDICATORS (0,2048,0,2050,2052,2054,
2056,2058,2060,2062,0,2064,2066) messages messages.msg 
replace INTO SCHEMA.TABLENAME nonrecoverable 

You will notice a record length of 2066 and null indicators are specified at fixed position.

When you FTP data, you need to make sure that you use BINARY option since you do not want embedded CRLF to be replaced by LF using ASCII transfer option.

This simple trick will save you tons of time figuring out why LOAD does not work on Unix platform.

DB2 Extract Database Size Information from UNIX List Commands

Mark Mulligan – (mark.mulligan.sr@sbcglobal.net) DB2 DBA

29 November 2008

Download Scripts

Introduction

Have you ever run the db2 "call get_dbsize_info(?,?,?,0)" command to get the size of your DB2 LUW database and noticed that the size is much smaller than the space used on your UNIX file systems? Do you need to monitor database growth over time to help plan for future storage requirements?

This article describes a script that will calculate the size of a DB2 LUW database on a UNIX server using UNIX commands that will give you a more accurate measure of the space used by the database. This script also stores this information in an operational table that you may use as part of your DBA Monitoring Dash Board to help you monitor and manage space in relation to growth trends.

Pros and cons

The advantage of monitoring database sizes over time is that you can see trends and show evidence to make the case for future storage acquisitions. This information can also be helpful in showing you when significant spikes in data growth appear and you can confirm that these are expected and not due to operational problems with application jobs or programs.

A disadvantage to this approach is that it does not include the size of transaction log files on disk, archive log files if you keep them on disk or temporary tablespaces. If you wish to include that information in the database size you will have to enhance the script to do this.

Description

The DB2 list tablespace command and list tablespace container command can be used to obtain the path or file for tablespace containers in a database. The UNIX list command (ls) can be used to obtain the size of the tablespace container paths and files and accumulated by partition and summarized by database. This script captures partition and database size information and stores it in a table. If the script is run daily then the size information can be seen over time.
Having this partition and database size information available in a table named OPERATIONS.DATABASE_INFORMATION can show database size information for one day or over time.

In the two examples shown below, the call to the get_dbsize_info stored procedure returns a database size of roughly 1.36 terabytes and the db2extract_database_size.ksh script returns a database size of roughly 3.09 terabytes. The difference between these two approaches is significant and when it comes to terabytes of storage it is important to be accurate in your measurements for storage planning purposes. Also, if you notice significant size differences in the database partition sizes then this could indicate an out of balance condition that you may need to correct at the tablespace or table level in the database.

db2 "call get_dbsize_info(?,?,?,0)"

  Value of output parameters
  --------------------------
  Parameter Name  : SNAPSHOTTIMESTAMP
  Parameter Value : 2008-11-26-09.11.44.205752

  Parameter Name  : DATABASESIZE
  Parameter Value : 1367017938944

  Parameter Name  : DATABASECAPACITY
  Parameter Value : -1

  Return Status = 0

db2extract_database_size_information.ksh prodin01 proddb01
Server 	        Instance 	Database 	Datetime 	Size 		Partition
prodsvr 	prodin01 	PRODDB01 	20081126091344 2186406912 	0
prodsvr 	prodin01 	PRODDB01 	20081126091344 784969437184 	1
prodsvr 	prodin01 	PRODDB01 	20081126091344 781603337216 	2
prodsvr 	prodin01 	PRODDB01 	20081126091344 780613206016 	3
prodsvr 	prodin01 	PRODDB01 	20081126091344 750529480704 	4
prodsvr 	prodin01 	PRODDB01 	20081126091344 3099901868032

Environment

The script in this article was tested on a server running the AIX operating system and DB2 versions 9.1 and 9.5. The KORN shell script should work on any server that has a UNIX-like operating system that supports these types of scripts. (Linux and Linux/390 come to mind.) This script is designed to run in a native UNIX environment and is not intended to run under the DB2 Script Center Client Window. The extract script must be run under the DB2 instance id or an id that has the correct UNIX permissions to be able to run the UNIX list command (ls) against the database tablespace container paths and files.

Before you get started, a couple of words of caution:

  • Practice running the script and verify the results.
  • Create the table before running the script. The DDL for this is provided below.
  • Create tablespaces for the table and it’s index.
  • Make sure the id you use to run the script has privileges to run the UNIX list (ls) command against the tablespace container paths and files and to insert rows in the OPERATIONS.DATABASE_INFORMATION table. Running this under a DB2 instance id would be best.
  • Schedule this script to run in cron or some other scheduling system daily under a DB2 instance id and you will have a history of the database size information for you to use over time.
  • WARNING: If you keep store other files on file systems in paths where DB2 keeps tablespace container files, these could be picked up and counted as part of your database size.
  • WARNING: If IBM changes the output format of the DB2 list tablespaces show detail or DB2 list tablespace containers for id commands then this script may have to be modified to work with the new format.

CRONTAB schedule example

00 22 * * * /db2util/scripts/db2extract_database_size_information.ksh prodin02 proddb02 >/dev/null 2>&1

The script

#!/bin/ksh
#----------------------------------------------------------------------t
#  Program          : db2extract_database_size_information.ksh
#  Description      : This script runs the DB2 list tablespace command
#                   : to identify the tablespace id's in a partition
#                   : and then runs the db2 list tablespace containers
#                   : for each id that is not a temporary tablespace
#                   : and extracts the name for each container path
#                   : or file and then runs a UNIX list (ls) command
#                   : and accumulates the size of the files to
#                   : calculate the size of the partition and
#                   : ultimately the size of the database.
#  Author           : Mark Mulligan
#  Date             : 04/07/2005
#  Input Arguments  : 1st Parameter = (Required) Instance.
#                   : 2nd Parameter = (Required) Database.
#  Output           : None.
#  Required Files   : None.
#  Maintenance      :
#-----------------------------------------------------------------------
ScriptVer='04/07/2005'
ScriptName='db2extract_database_size_information'

Server=$(hostname)

if [ "$1" = "" ]
then    echo ""
        echo "Parm 1 Instance                     (required)"
        echo "Parm 2 Database                     (required)"
        echo 'Parm 3 Script Output Directory Path (optional) Defaults to /dbawork/reports'
        echo ""
        return
else    Instance=`echo $1 | tr '[A-Z]' '[a-z]'`
fi

if [ "$2" = "" ]
then    echo ""
        echo "Parm 2 Database                     (required)"
        echo 'Parm 3 Script Output Directory Path (optional) Defaults to /dbawork/reports'
        echo ""
        return
else    Database=`echo $2 | tr '[a-z]' '[A-Z]'`
fi

if [ "$3" = "" ]
then    OutputDir='/dbawork/reports'
else    OutputDir=$3
fi

#.${HOME)/sqllib/db2profile
. ${HOME}/.${Server}_${Instance}
Datetime="`date +%Y%m%d%H%M%S`"
FileName=${OutputDir}/${ScriptName}_${Server}_${Instance}_${Database}_${Datetime}
OutputFile=${FileName}.txt

NumberOfNodes=`cat $INSTHOME/sqllib/db2nodes.cfg | wc -l`
TotalFileSize=0
PartitionNodeSize=0
LastNode=0
echo "Server Instance Database Datetime Size Partition" | tee $OutputFile
cat $INSTHOME/sqllib/db2nodes.cfg | while read node1 server node2
do
        export DB2NODE=$node1
        db2 terminate >/dev/null 2>/dev/null
        db2 connect to $Database >/dev/null 2>/dev/null
        db2 list tablespaces | grep "Tablespace ID" | while read tablespace ID equals id
        do
                db2 -x "select tbspaceid,datatype from syscat.tablespaces where tbspaceid=$id and datatype!='T'" >/dev/null 2>&1
                if [ "$?" = "0" ]
                then
                db2 list tablespace containers for $id | grep Name | while read name equals path
                do
                        echo ${path} >> $OutputFile.detail
                        ls -l ${path} | while read permissions number id group size month day hourminute filename
                        do
                                if [ "$permissions" = "total" ]
                                then    :
                                else
                                        # if [ "$path" = "$filename" ]
                                        # then echo "$filename   $size" >> $OutputFile.detail
                                        # else echo "$path   $filename   $size" >> $OutputFile.detail
                                        # fi
                                        if [ "$node1" = "$LastNode" ]
                                        then    :
                                        else    
                                            echo "$Server $Instance $Database $Datetime $PartitionNodeSize $LastNode" | tee -a $OutputFile
                                            db2 -v "insert into operations.database_information values \
                                                  ('${Server}','${Instance}','${Database}','${LastNode}', \
                                                    ${PartitionNodeSize},current timestamp)" \
                                                    >/dev/null 2>&1
                                            LastNode=$node1
                                            PartitionNodeSize=0
                                        fi
                                        ((PartitionNodeSize=PartitionNodeSize+size))
                                        ((TotalFileSize=TotalFileSize+size))
                                fi 
                        done
                done
                fi
        done
done
echo "$Server $Instance $Database $Datetime $PartitionNodeSize $LastNode" | tee -a $OutputFile
db2 -v "insert into operations.database_information values \
       ('${Server}','${Instance}','${Database}','${LastNode}',${PartitionNodeSize},current timestamp)" \
>/dev/null 2>&1
echo "$Server $Instance $Database $Datetime $TotalFileSize" | tee -a $OutputFile
db2 -v "insert into operations.database_information values ('${Server}','${Instance}','${Database}',' \
       ',${TotalFileSize},current timestamp)" 2>/dev/null 2>&1

echo "Output located in" | tee -a $OutputFile
ls $OutputFile*  | tee -a $OutputFile

The Table

------------------------------------------------
-- DDL Statements for table "OPERATIONS"."DATABASE_INFORMATION"
------------------------------------------------

CREATE TABLE "OPERATIONS"."DATABASE_INFORMATION"  (
                  "SERVER" CHAR(8) NOT NULL WITH DEFAULT  ,
                  "INSTANCE" CHAR(8) NOT NULL WITH DEFAULT  ,
                  "DATABASE" CHAR(8) NOT NULL WITH DEFAULT  ,
                  "DBPARTITIONNUM" CHAR(3) ,
                  "SIZE" BIGINT NOT NULL WITH DEFAULT  ,
                  "SIZE_TIMESTAMP" TIMESTAMP NOT NULL WITH DEFAULT  )
                 DISTRIBUTE BY HASH("SIZE_TIMESTAMP")
                   IN "TSOPERATIONS01" INDEX IN IXOPERATIONS01 NOT LOGGED INITIALLY ;

Example

If you run the script in foreground without passing it any parameters, it will echo display the parameters that the script uses. The first two parameters for the script are required so that the common reusable DBA script will know what DB2 instance and database to use. The third parameter is optional.


db2extract_database_size_information.ksh

Parm 1 Instance                     (required)
Parm 2 Database                     (required)
Parm 3 Script Output Directory Path (optional) Defaults to /dbawork/reports

When you run the script in foreground and pass it a DB2 instance id and database, you should see output displayed that is similar to the example shown below.

db2extract_database_size_information.ksh prodin01 proddb01

Server 	       Instance 	Database 	Datetime 	Size 		Partition
prodsvr 	prodin01 	PRODDB01 	20081126091344 2186406912 	0
prodsvr 	prodin01 	PRODDB01 	20081126091344 784969437184 	1
prodsvr 	prodin01 	PRODDB01 	20081126091344 781603337216 	2
prodsvr 	prodin01 	PRODDB01 	20081126091344 780613206016 	3
prodsvr 	prodin01 	PRODDB01 	20081126091344 750529480704 	4
prodsvr 	prodin01 	PRODDB01 	20081126091344 3099901868032
Output located in
/dbawork/reports/db2extract_database_size_information_prodsvr_prodin02_PRODDB02_20081126125715.txt
/dbawork/reports/db2extract_database_size_information_prodsvr_prodin02_PRODDB02_20081126125715.txt.detail

Select Examples

The output created by this script is stored in a DB2 table. You may develop SQL or a script that runs different types of select SQL statements that will help you use this information to help you monitor database size changes over time. The first select statement shows you the size of all partitions in the database and the total database size yesterday. The second select statement shows you the database sizes only over time. This information could be presented in a graph to show the database size growth over time.

select database,dbpartitionnum,size,date(size_timestamp) date 
from   operations.database_information 
where  date(size_timestamp) = current date - 1 day 
order by database,size_timestamp

DATABASE DBPARTITIONNUM SIZE                 DATE
-------- -------------- -------------------- ----------
PRODDB02   0                        2186406912 11/25/2008
PRODDB02   1                      784969437184 11/25/2008
PRODDB02   2                      781603337216 11/25/2008
PRODDB02   3                      780613206016 11/25/2008
PRODDB02   4                      750529480704 11/25/2008
PRODDB02                         3099901868032 11/25/2008

select database,dbpartitionnum,size,date(size_timestamp) date 
from   operations.database_information 
where  dbpartitionnum = ' ' 
order by database,size_timestamp

DATABASE DBPARTITIONNUM SIZE                 DATE
-------- -------------- -------------------- ----------
PRODDB02                         3095516920832 10/26/2008
PRODDB02                         3095545461760 10/27/2008
PRODDB02                         3095662148608 10/28/2008
PRODDB02                         3095763762176 10/29/2008
PRODDB02                         3095895849984 10/30/2008
PRODDB02                         3096048811008 10/31/2008
PRODDB02                         3096162221056 11/01/2008
PRODDB02                         3096359111680 11/03/2008
PRODDB02                         3096465312768 11/03/2008
PRODDB02                         3096645143552 11/04/2008
PRODDB02                         3096794696704 11/05/2008
PRODDB02                         3096930421760 11/06/2008
PRODDB02                         3097016273920 11/07/2008
PRODDB02                         3097127816192 11/08/2008
PRODDB02                         3097199893504 11/09/2008
PRODDB02                         3097231940608 11/10/2008
PRODDB02                         3097399696384 11/11/2008
PRODDB02                         3097587784704 11/12/2008
PRODDB02                         3097786883072 11/13/2008
PRODDB02                         3097978125312 11/14/2008
PRODDB02                         3098132265984 11/15/2008
PRODDB02                         3098199473152 11/16/2008
PRODDB02                         3098299218944 11/17/2008
PRODDB02                         3098505722880 11/18/2008
PRODDB02                         3098718649344 11/19/2008
PRODDB02                         3098936228864 11/20/2008
PRODDB02                         3099159116800 11/21/2008
PRODDB02                         3099320859648 11/22/2008
PRODDB02                         3099425651712 11/23/2008
PRODDB02                         3099529197568 11/24/2008
PRODDB02                         3099901868032 11/25/2008
PRODDB02                         3099901868032 11/26/2008

Conclusion

If you do not have sophisticated database monitoring software that captures information about DB2 database sizes, you can capture this yourself from the DB2 database and UNIX operating system by using this script. This valuable source of information can be used by a DBA as evidence to show database size changes over time and used to help justify new storage requests. If you develop a dash board for application teams, users and management that gives them visibility into this information then they can monitor database size history over time and gain a better understanding about the growth of information stored and used by the company.

If you have any questions related to this article, feel free to contact me, Mark Mulligan, at mark.mulligan.sr@sbcglobal.net and I will be glad to try to answer your questions.

About the author

Mark Mulligan is a DBA living in the Dallas-Fort Worth area of Texas. He has a Bachelor of Science Degree in Business Administration from Merrimack College in North Andover, Massachusetts, and 30 years of experience working with computer software design, development, maintenance, enhancement and support. He has 10 years of experience working with DB2 on servers running UNIX-like operating systems and 15 years of experience working with DB2 on Z/OS. Mark can be contacted at mark.mulligan.sr@sbcglobal.net.

DB2 Load and Index Build Information from DB2 Diagnostic Log File - STMM Information Tool

Mark Mulligan – (mark.mulligan.sr@sbcglobal.net) DB2 DBA

16 November 2008

Introduction

If you have application jobs or users that run the DB2 load utility and would like to keep track of this information over time there is valuable information stored in the DB2 diagnostic log file that can help you accomplish this. Detail information about each table load and index build is recorded in the DB2 diagnostic log file.

This article describes the table load and index build information available and a script that will extract this information and insert it into a table that will help you monitor table load and index build performance over time. This script also extracts DB2 memory management message information (STMM) and imports that into another table.

Pros and cons

The advantage of having table load and index build information over time is that it can help you monitor and manage performance and answer questions about the performance of loads run by applications and users. If you use the DB2 automatic memory manager (STMM) then you will have information showing how this behaves over time as well.

One disadvantage of this approach is that the script relies on the current availability and format of information from the DB2 diagnostic log file and if IBM changes the format or content of this file in a future release, as has been done in the past, then this script will have to be modified or may become completely obsolete if the information is no longer available.

Description

The DB2 diagnostic log file named db2diag.log for a DB2 instance contains messages with information about table loads and index builds that are run against a database. Listed below are examples of the start and completion of a table load and the start and completion of the index build phase of the load. The script I developed will capture the server, instance, database, authid, table load or index build type, starting timestamp, completed timestamp and table name involved with the load. It will also capture information about what the DB2 automatic memory manager is doing during the day if you use that feature.

Having this table load and index build information available in a table named OPERATIONS.DB2DIAG_LOAD_INFORMATION, will allow you to see all of the table loads and index builds that run during a day. You will also be able to compare the performance of loads into tables from one day to the next. Having automatic memory management information available in a table named OPERATIONS.DB2DIAG_STMM_INFORMATION will give you insight into what the automatic memory manager is doing in your environments as well.

One example of how I use the table load and index build information is to help me identify scheduling problems that result in poor performance for other users or jobs. If a load job is running, other applications might wait to select information from the table and eventually time out and roll back. Being able to tell application teams and users that some other application or user was running a load at the time can help them understand the importance of proper scheduling and coordination of work activities.

When a load runs longer than normal and about the same number of rows are involved with the load, this information may show that the index build phase of the load ran longer. If loads are usually run with the incremental index build option, there are times when the database will override this and do a complete index rebuild and the longer index build times may indicate this.

The great thing about having this information available in a table is that you can provide physical evidence about what happened when and not just have to ask other people to take your word for it. If you make this table information available in an online dash board for application support people, users and management, then they can see for themselves what is going on and trend graphs can be developed using this information to show how loads are performing over time.

2008-11-20-00.19.46.435158-300 I13347A492         LEVEL: Warning
PID     : 4976964              TID  : 1           PROC : db2lfrm0 0
INSTANCE: edwp02               NODE : 000
APPHDL  : 0-718                APPID: 10.145.33.23.39186.081120051946
AUTHID  : PP0342DL
FUNCTION: DB2 UDB, database utilities, sqlulPrintPhaseMsg, probe:311
DATA #1 : String, 131 bytes
LOADID: 6336514.2008-11-20-00.19.46.264479.0 (35;4)
Starting LOAD phase at 11/20/2008 00:19:46.427305. Table MDDB    .TRNCTN_ALRT

2008-11-20-00.19.46.522220-300 I14733A465         LEVEL: Warning
PID     : 106784               TID  : 1           PROC : db2lrid 0
INSTANCE: edwp02               NODE : 000
APPHDL  : 0-718                APPID: 10.145.33.23.39186.081120051946
AUTHID  : PP0342DL
FUNCTION: DB2 UDB, database utilities, sqlulPrintPhaseMsg, probe:311
DATA #1 : String, 105 bytes
LOADID: 6336514.2008-11-20-00.19.46.264479.0 (35;4)
Completed LOAD phase at 11/20/2008 00:19:46.517440.

2008-11-20-00.19.46.528250-300 I15199A465         LEVEL: Warning
PID     : 106784               TID  : 1           PROC : db2lrid 0
INSTANCE: edwp02               NODE : 000
APPHDL  : 0-718                APPID: 10.145.33.23.39186.081120051946
AUTHID  : PP0342DL
FUNCTION: DB2 UDB, database utilities, sqlulPrintPhaseMsg, probe:311
DATA #1 : String, 105 bytes
LOADID: 6336514.2008-11-20-00.19.46.264479.0 (35;4)
Starting BUILD phase at 11/20/2008 00:19:46.522519.

2008-11-20-00.19.46.943711-300 I15665A466         LEVEL: Warning
PID     : 106784               TID  : 1           PROC : db2lrid 0
INSTANCE: edwp02               NODE : 000
APPHDL  : 0-718                APPID: 10.145.33.23.39186.081120051946
AUTHID  : PP0342DL
FUNCTION: DB2 UDB, database utilities, sqlulPrintPhaseMsg, probe:311
DATA #1 : String, 106 bytes
LOADID: 6336514.2008-11-20-00.19.46.264479.0 (35;4)
Completed BUILD phase at 11/20/2008 00:19:46.940636.

Environment

The script in this article was tested on a server running the AIX operating system and DB2 versions 9.1 and 9.5. The KORN shell script should work on any server that has a UNIX-like operating system that supports these types of scripts. (Linux and Linux/390 come to mind.) This script is designed to run in a native UNIX environment and is not intended to run under the DB2 Script Center Client Window. This script may be run under any id that has access to a DB2 environment via the db2profile and that has the authority to run the db2 get dbm cfg command. This command is used to determine the default location of the db2diag.log file. The script requires that a DB2 instance id and database name be passed to it and has other parameters you may use to have it run against old copies of db2diag.log files if you move these off to another path daily like I do.

Before you get started, a couple of words of caution:

Practice running the script and verify the results.

Create the table before running the script. The DDL for this is provided below.

Create tablespaces for the table and it’s index.

Make sure the id you use to run the script has privileges to run the db2 get dbm cfg command and to be able to connect to the database and to insert and update rows in the OPERATIONS.DB2DIAG_LOAD_INFORMATION table and to import rows into the OPERATIONS.DB2DIAG_STMM_INFORMATION table.

Schedule this script to run in cron or some other scheduling system daily under a DB2 instance id and you will have a history of the table load and index build information for you to use over time.

WARNING: If you keep your db2diag.log file and never move it to another path and append a date timestamp to it then this script will run longer and longer every day and try to insert and update rows it processed the day before. I have a script that moves our db2diag.log files to a /dbawork/instance/db2dump path daily that renames the file to db2diag_CCYYMMDD*.log where CCYYMMDD* is a date timestamp. This script is designed to allow you to pass this path as the third parameter. If you just pass the path then it will process all db2diag*.log files. If you pass it the db2diag_CCYYMMDD*.log at the end of the path as shown in the crontab example below then the script will only process yesterday’s db2diag.log file.

NOTE: If you run the script without the third parameter, then the script will run db2 get dbm cfg to determine the location of the current db2diag.log file and then process that file.

WARNING: If IBM changes the format of the DB2 diagnostic log messages this script may need to be re-written or may become completely obsolete if the information is no longer available in the db2diag.log file.

CRONTAB schedule example

00 01 * * * /db2util/scripts/db2diag_extract_information.ksh edwp02 edwp02 
     /dbawork/edwp02/db2dump db2diag_CCYYMMDD*.log >/dev/null 2>&1

The script

#!/bin/ksh
#-----------------------------------------------------------------------
#  Program          : db2diag_extract_information.ksh
#  Description      : This script extracts information from db2diag
#                   : log files.
#  Author           : Mark Mulligan
#  Date             : 06/10/2008
#-----------------------------------------------------------------------
ScriptVer='06/10/2008'
ScriptName='db2diag_extract_information'

Server=$(hostname)

if [ "$1" = "" ]
then    echo ""
        echo "Parm 1 Instance                     (required)"
        echo "Parm 2 Database                     (required)"
        echo "Parm 3 DB2 Diagnostic Log Path      (optional) Defaults to DIAGPATH"
        echo '                                               May be a path where you put your db2diag_CCYYMMDDHHMMSS.log files'
        echo '                                               For example, "/dbawork/edwp02/db2dump"'
        echo "Parm 4 DB2 Diagnostic Log File      (optional) Defaults to db2diag*.log"
        echo '                                               May be db2diag_CCYYMMDD*.log'
        echo "Parm 5 Script Output Directory Path (optional) Defaults to /dbawork/reports"
        echo ""
        return 8
else    Instance=`echo $1 | tr '[A-Z]' '[a-z]'`
fi

if [ "$2" = "" ]
then    echo ""
        echo "Parm 2 Database                     (required)"
        echo "Parm 3 DB2 Diagnostic Log Path      (optional) Defaults to DIAGPATH"
        echo '                                               May be a path where you put your db2diag_CCYYMMDDHHMMSS.log files'
        echo '                                               For example, "/dbawork/edwp02/db2dump"'
        echo "Parm 4 DB2 Diagnostic Log File      (optional) Defaults to db2diag*.log"
        echo '                                               May be db2diag_CCYYMMDD*.log'
        echo "Parm 5 Script Output Directory Path (optional) Defaults to /dbawork/reports"
        echo ""
        return 8
else    Database=`echo $2 | tr '[a-z]' '[A-Z]'`
fi

if [ "$3" = "" ]
then    db2 get dbm cfg | grep DIAGPATH | read diagnostic data directory path diagpath equals DiagPath
        Path="${DiagPath}"
else    Path=${3}
fi

if [ "$4" = "" ]
then    File="db2diag*.log"
else    if [ "$4" = "db2diag_CCYYMMDD*.log" ]
        then    CCYYMMDD="`date +%Y%m%d`"
                File="db2diag_${CCYYMMDD}*.log"
        else    File=${4}
        fi
fi

if [ "$5" = "" ]
then    OutputDir="/dbawork/reports"
else    OutputDir="${5}"
fi

. $HOME/sqllib/db2profile
Datetime="`date +%Y%m%d%H%M%S`"
FileName=${OutputDir}/${ScriptName}_${Server}_${Instance}_${Database}_${Datetime}
OutputFile=${FileName}.txt

echo "$ScriptName Vers: $ScriptVer started on " `uname -n` on `date` | tee $OutputFile

echo "" | tee $OutputFile
echo "ls ${Path}/${File}" | tee -a $OutputFile
ls ${Path}/${File} > $FileName.ls
echo "" | tee $OutputFile
cat $FileName.ls | tee -a $OutputFile

cat $FileName.ls | while read filename
do
        echo "Processing db2diag information in $filename" | tee -a $OutputFile

        cat $filename | while read a b c d e f g h i j k l m n o p q r s t u v w x y z
        do

#               2008-06-19-06.27.59.170148-240 I1503054A494       LEVEL: Warning

                if [ "$c" = "LEVEL:" ]
                then    echo ${a} | sed 's/-/ /g' | read ccyy mo dd time extra
                        db2diagTimestamp="${ccyy}-${mo}-${dd}-${time}"
#                       echo $db2diagTimestamp
                fi

#               PID     : 6807552              TID  : 1           PROC : db2lfrm0 0

                if [ "$a" = "PID" ]
                then    PID=$c
                fi

#               INSTANCE: edwp02               NODE : 000

                if [ "$a" = "INSTANCE:" ]
                then    INSTANCE=$b
                        NODE=$e
                fi

#               APPHDL  : 0-1738               APPID: 10.145.33.23.46241.080619102739

                if [ "$a" = "APPHDL" ]
                then    APPHDL=$c
                        APPID=$e
                fi

#               AUTHID  : EDWP02

                if [ "$a" = "AUTHID" ]
                then    AUTHID=$c
                fi

#               LOADID: 3264648.2008-08-12-04.22.39.825964.0 (5;15)

                if [ "$a" = "LOADID:" ]
                then    LOADID=$b
                fi

#               Starting LOAD phase at 06/19/2008 06:27:58.562570. Table MDDB    .PIF_RPRT_DY_SM

                if [[ "$a" = "Starting" && "$b" = "LOAD" ]]
                then    Type=$b
                        StartingLOADTimestamp=$db2diagTimestamp
                        TableName=$h$i
                        echo $TableName | sed 's/\./ /g' | read TabSchema TabName
#                       echo "$TabSchema"
#                       echo "$TabName"
                        db2 -x "insert into operations.db2diag_load_information values \
                               ('${LOADID}','${Type}','${Server}','${Instance}','${Database}',${NODE},\
                               '${AUTHID}',${PID},'${APPHDL}','${APPID}',\
                               timestamp('${StartingLOADTimestamp}'), \
                               timestamp('${StartingLOADTimestamp}'), \
                               '${TableName}','${TabSchema}','${TabName}')" > /dev/null 2>&1
                fi

                if [[ "$a" = "Completed" && "$b" = "LOAD" ]]
                then    Type=$b
                        CompletedLOADTimestamp=$db2diagTimestamp
                        db2 -x "update operations.db2diag_load_information set completed_timestamp = \
                            timestamp('${CompletedLOADTimestamp}') where loadid = '${LOADID}' \
                            and type = '${Type}' and node = ${NODE}" > /dev/null 2>&1
                fi

#               Starting BUILD phase at 06/19/2008 06:28:02.441206.

                if [[ "$a" = "Starting" && "$b" = "BUILD" ]]
                then    Type=$b
                        StartingBUILDTimestamp=$db2diagTimestamp
                        db2 -x "select tablename,tabschema,tabname from operations.db2diag_load_information \
                            where loadid = '${LOADID}' and type = 'LOAD' and node = ${NODE}" | \
                            read TableName TabSchema TabName
                        db2 -x "insert into operations.db2diag_load_information values \
                            ('${LOADID}','${Type}','${Server}','${Instance}','${Database}',\
                            ${NODE},'${AUTHID}',${PID},'${APPHDL}','${APPID}',\
                            timestamp('${StartingBUILDTimestamp}'),\
                            timestamp('${StartingBUILDTimestamp}'),\
                            '${TableName}','${TabSchema}','${TabName}')"  > /dev/null 2>&1
                fi

#               Completed BUILD phase at 06/19/2008 06:28:05.446418.

                if [[ "$a" = "Completed" && "$b" = "BUILD" ]]
                then    Type=$b
                        CompletedBUILDTimestamp=$db2diagTimestamp
                        db2 -x "update operations.db2diag_load_information set completed_timestamp = \
                            timestamp('${CompletedBUILDTimestamp}') where loadid = '${LOADID}' \
                            and type = '${Type}' and node = ${NODE}" > /dev/null 2>&1
                fi

#               CHANGE  : STMM CFG DB EDWP02: "Pckcachesz" From: "4696"   To: "4664" 

                if [[ "$a" = "CHANGE" && "$c" = "STMM" ]]
                then    CHANGESTMMTimestamp=$db2diagTimestamp
                        DATABASE=`echo $f | sed 's/://g'`
                        Type=`echo $g | sed 's/"//g'`
                        From=`echo $i | sed 's/"//g'`
                        To=`echo $l | sed 's/"//g'`
                        echo '"'$CHANGESTMMTimestamp'","'$Server'","'$Instance'","'$DATABASE'",'$NODE', \
                             "'$AUTHID'",'$PID',"'$APPHDL'","'$APPID'","'$Type'","FROM",\
                             '$From >> $FileName.stmm
                        echo '"'$CHANGESTMMTimestamp'","'$Server'","'$Instance'","'$DATABASE'",'$NODE', \
                             "'$AUTHID'",'$PID',"'$APPHDL'","'$APPID'","'$Type'","TO",\
                             '$To >> $FileName.stmm
                fi

        done
done

db2 -v connect to $Database | tee -a $OutputFile
db2 -v "import from $FileName.stmm of del messages $FileName.stmm.msg \
     NP.insert_update into OPERATIONS.DB2DIAG_STMM_INFORMATION" | tee -a $OutputFile

echo "$ScriptName Vers: $ScriptVer ended   on " `uname -n` on `date` | tee -a $OutputFile

echo "Output located in" | tee -a $OutputFile
ls $FileName*

The Tables

------------------------------------------------
-- DDL Statements for table "OPERATIONS"."DB2DIAG_LOAD_INFORMATION"
------------------------------------------------


CREATE TABLE "OPERATIONS"."DB2DIAG_LOAD_INFORMATION"  (
                  "LOADID" CHAR(40) NOT NULL WITH DEFAULT ' ' ,
                  "TYPE" CHAR(5) NOT NULL WITH DEFAULT ' ' ,
                  "SERVER" CHAR(8) NOT NULL WITH DEFAULT ' ' ,
                  "INSTANCE" CHAR(8) NOT NULL WITH DEFAULT ' ' ,
                  "DATABASE" CHAR(8) NOT NULL WITH DEFAULT ' ' ,
                  "NODE" SMALLINT NOT NULL WITH DEFAULT 0 ,
                  "AUTHID" CHAR(8) NOT NULL WITH DEFAULT ' ' ,
                  "PID" BIGINT NOT NULL WITH DEFAULT 0 ,
                  "APPHDL" CHAR(8) NOT NULL WITH DEFAULT ' ' ,
                  "APPID" CHAR(31) NOT NULL WITH DEFAULT ' ' ,
                  "STARTING_TIMESTAMP" TIMESTAMP NOT NULL WITH DEFAULT CURRENT TIMESTAMP ,
                  "COMPLETED_TIMESTAMP" TIMESTAMP NOT NULL WITH DEFAULT CURRENT TIMESTAMP ,
                  "TABLENAME" VARCHAR(256) NOT NULL WITH DEFAULT '' ,
                  "TABSCHEMA" VARCHAR(128) NOT NULL WITH DEFAULT '' ,
                  "TABNAME" VARCHAR(128) NOT NULL WITH DEFAULT '' )
                 DISTRIBUTE BY HASH("LOADID")
                   IN "TSOPERATIONS01" INDEX IN IXOPERATIONS01 NOT LOGGED INITIALLY ;


-- DDL Statements for primary key on Table "OPERATIONS"."DB2DIAG_LOAD_INFORMATION"

ALTER TABLE "OPERATIONS"."DB2DIAG_LOAD_INFORMATION"
        ADD CONSTRAINT "DB2DIAG_LOAD_IX01" PRIMARY KEY
                ("LOADID",
                 "TYPE",
                 "NODE");

------------------------------------------------
-- DDL Statements for table "OPERATIONS"."DB2DIAG_STMM_INFORMATION"
------------------------------------------------


CREATE TABLE "OPERATIONS"."DB2DIAG_STMM_INFORMATION"  (
                  "CHANGE_TIMESTAMP" TIMESTAMP NOT NULL WITH DEFAULT  ,
                  "SERVER" CHAR(8) NOT NULL WITH DEFAULT  ,
                  "INSTANCE" CHAR(8) NOT NULL WITH DEFAULT  ,
                  "DATABASE" CHAR(8) NOT NULL WITH DEFAULT  ,
                  "NODE" SMALLINT NOT NULL WITH DEFAULT  ,
                  "AUTHID" CHAR(8) NOT NULL WITH DEFAULT  ,
                  "PID" BIGINT NOT NULL WITH DEFAULT  ,
                  "APPHDL" CHAR(8) NOT NULL WITH DEFAULT  ,
                  "APPID" CHAR(31) NOT NULL WITH DEFAULT  ,
                  "TYPE" CHAR(40) NOT NULL WITH DEFAULT  ,
                  "FROM_OR_TO" CHAR(4) NOT NULL WITH DEFAULT  ,
                  "MEMORY_SIZE" BIGINT NOT NULL WITH DEFAULT  )
                 DISTRIBUTE BY HASH("CHANGE_TIMESTAMP")
                   IN "TSOPERATIONS01" INDEX IN IXOPERATIONS01 NOT LOGGED INITIALLY ;


-- DDL Statements for primary key on Table "OPERATIONS"."DB2DIAG_STMM_INFORMATION"

ALTER TABLE "OPERATIONS"."DB2DIAG_STMM_INFORMATION"
        ADD CONSTRAINT "DB2DIAG_STMM_IX01" PRIMARY KEY
                ("CHANGE_TIMESTAMP",
                 "SERVER",
                 "INSTANCE",
                 "DATABASE",
                 "NODE",
                 "AUTHID",
                 "PID",
                 "APPHDL",
                 "APPID",
                 "TYPE",
                 "FROM_OR_TO");

Example

If you run the script in foreground without passing it any parameters, it will echo display the parameters that the script uses. The first two parameters for the script are required so that the common reusable DBA script will know what DB2 instance and database to use. The third parameter is optional. You may pass any path that indicates the location of the DB2 diagnostic log files the script should read or the script will default to the DIAGPATH value by running the db2 get dbm cfg command to obtain this. The fourth parameter is optional and defaults to the db2diag.log file name. If you move your db2diag.log file to another path daily and rename it to db2diag_CCYYMMDDHHMMSS.log where CCYYMMDDHHMMSS is a date timestamp like I do then you may pass this value as the parameter and the script will only run against yesterday’s diagnostic log file. Otherwise, it will run against all db2diag*.log files in the path.

Moving your db2diag.log files to another path daily and renaming it to have a date timestamp in the name will help this script only process new information. This db2diag.log save technique can also help you keep your db2diag.log file to a manageable size daily and when you have to open a PMR with IBM and run db2support to send the db2diag.log file and other information, the files you have to send IBM will be smaller and easier for them to analyze. You can keep your DB2 diagnostic log file history or remove these files over time to prevent file systems from filling up.

db2diag_extract_information.ksh

Parm 1 Instance                     (required)
Parm 2 Database                     (required)
Parm 3 DB2 Diagnostic Log Path      (optional) Defaults to DIAGPATH
                                               May be a path where you put your db2diag_CCYYMMDDHHMMSS.log files
                                               For example, "/dbawork/edwp02/db2dump"
Parm 4 DB2 Diagnostic Log File      (optional) Defaults to db2diag*.log
                                               May be db2diag_CCYYMMDD*.log
Parm 5 Script Output Directory Path (optional) Defaults to /dbawork/reports

When you run the script in foreground and pass it a DB2 instance id and database, you should see output displayed that is similar to the example shown below.

db2diag_extract_information.ksh edwp02 edwp02
db2diag_extract_information Vers: 06/10/2008 started on  edwasp on Thu Nov 20 09:45:12 EST 2008

ls /db2home/edwp02/sqllib/db2dump/db2diag*.log

/db2home/edwp02/sqllib/db2dump/db2diag.log
Processing db2diag information in /db2home/edwp02/sqllib/db2dump/db2diag.log
connect to EDWP02

   Database Connection Information

 Database server        = DB2/AIX64 9.1.4
 SQL authorization ID   = U0H005
 Local database alias   = EDWP02


import from /dbawork/reports/db2diag_extract_information_edwasp_edwp02_EDWP02_20081120094512.stmm 
of del messages /dbawork/reports/db2diag_extract_information_edwasp_edwp02_EDWP02_20081120094512.stmm.msg 
insert_update into OPERATIONS.DB2DIAG_STMM_INFORMATION

Number of rows read         = 1386
Number of rows skipped      = 0
Number of rows inserted     = 1386
Number of rows updated      = 0
Number of rows rejected     = 0
Number of rows committed    = 1386


db2diag_extract_information Vers: 06/10/2008 ended   on  edwasp on Thu Nov 20 09:48:25 EST 2008
Output located in
/dbawork/reports/db2diag_extract_information_edwasp_edwp02_EDWP02_20081120094512.ls        
    /dbawork/reports/db2diag_extract_information_edwasp_edwp02_EDWP02_20081120094512.stmm.msg
/dbawork/reports/db2diag_extract_information_edwasp_edwp02_EDWP02_20081120094512.stmm      
    /dbawork/reports/db2diag_extract_information_edwasp_edwp02_EDWP02_20081120094512.txt

Select Example

The output created by this script is stored in DB2 tables. You may develop SQL or a script that runs different types of SQL that will help you use this information to help you track table load and index build performance over time. This information can also be used to help identify what is running when and this may help you identify contention between different applications and users. You can also use this information to determine when DB2 is allowing a table load to incrementally add information to indexes and when it has switched over to completely rebuild indexes during a load. Here is one example of SQL that lists load information from yesterday.

select a.server,a.instance,a.database,a.node,a.authid,a.loadid,a.type,a.starting_timestamp,
a.completed_timestamp,char(a.tablename,40) as tablename,char(b.tbspace,20) as tablespace 
from operations.db2diag_load_information a,syscat.tables b 
where a.authid like '%%' 
and a.tabschema=b.tabschema 
and a.tabname=b.tabname 
and ( date(a.starting_timestamp)= current date - 1 day or 
      date(a.completed_timestamp)= current date - 1 day or 
      current date -1 day between date(a.starting_timestamp) 
      and date(a.completed_timestamp) 
     ) 
order by a.starting_timestamp

SERVER   INSTANCE DATABASE NODE   AUTHID   LOADID                                   TYPE  STARTING_TIMESTAMP         COMPLETED_TIMESTAMP        TABLENAME                                TABLESPACE
-------- -------- -------- ------ -------- ---------------------------------------- ----- -------------------------- -------------------------- ---------------------------------------- --------------------
edwasp   edwp02   EDWP02        3 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     LOAD  2008-11-19-00.02.19.825346 2008-11-19-00.02.20.496011 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        9 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     LOAD  2008-11-19-00.02.19.829924 2008-11-19-00.02.20.528872 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        1 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     LOAD  2008-11-19-00.02.19.830849 2008-11-19-00.02.20.449521 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        6 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     LOAD  2008-11-19-00.02.19.839059 2008-11-19-00.02.20.492698 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        4 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     LOAD  2008-11-19-00.02.19.843122 2008-11-19-00.02.20.493764 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02       10 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     LOAD  2008-11-19-00.02.19.844478 2008-11-19-00.02.20.499503 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        7 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     LOAD  2008-11-19-00.02.20.083580 2008-11-19-00.02.20.459874 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        2 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     LOAD  2008-11-19-00.02.20.195231 2008-11-19-00.02.20.454922 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        5 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     LOAD  2008-11-19-00.02.20.222706 2008-11-19-00.02.20.459483 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        8 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     LOAD  2008-11-19-00.02.20.222869 2008-11-19-00.02.20.868135 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        1 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     BUILD 2008-11-19-00.02.20.460441 2008-11-19-00.02.20.791962 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        2 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     BUILD 2008-11-19-00.02.20.489992 2008-11-19-00.02.20.661057 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        7 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     BUILD 2008-11-19-00.02.20.495294 2008-11-19-00.02.20.873008 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        5 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     BUILD 2008-11-19-00.02.20.496070 2008-11-19-00.02.20.742382 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        6 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     BUILD 2008-11-19-00.02.20.513766 2008-11-19-00.02.20.685750 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        4 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     BUILD 2008-11-19-00.02.20.526393 2008-11-19-00.02.20.675156 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02       10 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     BUILD 2008-11-19-00.02.20.531163 2008-11-19-00.02.20.829576 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        9 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     BUILD 2008-11-19-00.02.20.537341 2008-11-19-00.02.20.713257 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        3 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     BUILD 2008-11-19-00.02.20.839878 2008-11-19-00.02.21.084649 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        8 PP03A8DL 6090858.2008-11-19-00.02.18.570277.0     BUILD 2008-11-19-00.02.20.883066 2008-11-19-00.02.21.143827 PLCY_RPT.RV_VIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        1 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     LOAD  2008-11-19-00.03.00.229639 2008-11-19-00.03.00.979805 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02       10 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     LOAD  2008-11-19-00.03.00.238083 2008-11-19-00.03.00.983704 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        6 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     LOAD  2008-11-19-00.03.00.248511 2008-11-19-00.03.00.973224 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        4 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     LOAD  2008-11-19-00.03.00.250261 2008-11-19-00.03.00.971232 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        7 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     LOAD  2008-11-19-00.03.00.542755 2008-11-19-00.03.00.964926 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        3 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     LOAD  2008-11-19-00.03.00.549672 2008-11-19-00.03.00.967582 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        9 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     LOAD  2008-11-19-00.03.00.585876 2008-11-19-00.03.00.974125 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        2 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     LOAD  2008-11-19-00.03.00.660424 2008-11-19-00.03.00.981729 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        5 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     LOAD  2008-11-19-00.03.00.679167 2008-11-19-00.03.01.121790 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        8 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     LOAD  2008-11-19-00.03.00.680724 2008-11-19-00.03.00.966425 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        7 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     BUILD 2008-11-19-00.03.00.979796 2008-11-19-00.03.01.064096 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        8 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     BUILD 2008-11-19-00.03.00.980672 2008-11-19-00.03.01.091855 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        3 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     BUILD 2008-11-19-00.03.00.981723 2008-11-19-00.03.01.067041 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        4 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     BUILD 2008-11-19-00.03.00.983988 2008-11-19-00.03.01.064742 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        6 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     BUILD 2008-11-19-00.03.00.986718 2008-11-19-00.03.01.096628 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        9 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     BUILD 2008-11-19-00.03.00.989214 2008-11-19-00.03.01.085788 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02       10 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     BUILD 2008-11-19-00.03.00.999050 2008-11-19-00.03.01.102833 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        5 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     BUILD 2008-11-19-00.03.01.132103 2008-11-19-00.03.01.191272 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        1 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     BUILD 2008-11-19-00.03.01.362555 2008-11-19-00.03.01.427459 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        2 PP03A8DL 4923404.2008-11-19-00.02.58.885675.0     BUILD 2008-11-19-00.03.01.472588 2008-11-19-00.03.01.542501 PLCY_RPT.RV_CIF_SM                       TSPCY70
edwasp   edwp02   EDWP02        0 PP0342DL 5718288.2008-11-19-00.14.15.876530.0     LOAD  2008-11-19-00.14.16.169307 2008-11-19-00.14.16.305622 MDDB.TRNCTN_ALRT                         TSALR01
edwasp   edwp02   EDWP02        0 PP0342DL 5718288.2008-11-19-00.14.15.876530.0     BUILD 2008-11-19-00.14.16.312567 2008-11-19-00.14.16.569363 MDDB.TRNCTN_ALRT                         TSALR01
edwasp   edwp02   EDWP02        0 PP0342DL 2039862.2008-11-19-00.14.57.185249.0     LOAD  2008-11-19-00.14.57.360736 2008-11-19-00.14.57.417611 MDDB.PRCS_ALRT                           TSALR01
edwasp   edwp02   EDWP02        0 PP0342DL 2039862.2008-11-19-00.14.57.185249.0     BUILD 2008-11-19-00.14.57.424661 2008-11-19-00.14.57.442925 MDDB.PRCS_ALRT                           TSALR01
edwasp   edwp02   EDWP02        0 PP0341DL 4551084.2008-11-19-03.34.11.855921.0     LOAD  2008-11-19-03.34.12.694610 2008-11-19-03.34.13.120171 MDDB.TRNCTN_ALRT                         TSALR01
edwasp   edwp02   EDWP02        0 PP0341DL 4551084.2008-11-19-03.34.11.855921.0     BUILD 2008-11-19-03.34.13.138966 2008-11-19-03.34.13.538798 MDDB.TRNCTN_ALRT                         TSALR01
edwasp   edwp02   EDWP02        0 PP0341DL 6910400.2008-11-19-03.35.17.182007.0     LOAD  2008-11-19-03.35.17.456812 2008-11-19-03.35.17.551005 MDDB.PRCS_ALRT                           TSALR01
edwasp   edwp02   EDWP02        0 PP0341DL 6910400.2008-11-19-03.35.17.182007.0     BUILD 2008-11-19-03.35.17.575893 2008-11-19-03.35.17.605481 MDDB.PRCS_ALRT                           TSALR01

You may want to develop SQL select statements to run against the OPERATIONS.DB2DIAG_STMM_INFORMATION table to gain insight into what DB2 automatic memory management is doing as well.

Conclusion

If you do not have sophisticated database monitoring software that captures information about DB2 table loads and index builds, you can capture this yourself from the DB2 diagnostic log files by using this script. This valuable source of information can be used by a DBA as evidence to show when table loads and index builds have run and how long they take to process. If the load step is just one part of a job then you will know how much time the database is spending on the load and how much time the job is spending doing other things. If you develop a dash board for application teams, users and management that gives them visibility into this information then they can monitor load history and performance over time and gain a better understanding about what is running and when.

If you have any questions related to this article, feel free to contact me, Mark Mulligan, at mark.mulligan.sr@sbcglobal.net and I will be glad to try to answer your questions.

About the author

Mark Mulligan is a DBA living in the Dallas-Fort Worth area of Texas. He has a Bachelor of Science Degree in Business Administration from Merrimack College in North Andover, Massachusetts, and 30 years of experience working with computer software design, development, maintenance, enhancement and support. He has 10 years of experience working with DB2 on servers running UNIX-like operating systems and 15 years of experience working with DB2 on Z/OS. Mark can be contacted at mark.mulligan.sr@sbcglobal.net.

DB2 Check Pending Script

Thanks to Max Petrenko of DB2 Toronto Lab for sharing a very useful script to remove check pending status from the DB2 tables after LOAD or other operations. It is easy to generate a check pending script, but the importance of this script is that it builds the sequence in such a fashion that the dependencies are taken care automatically.

A simple approach to remove check pending

Generate script using a simple SELECT statement as shown below:

CONNECT TO TESTDB;
SET INTEGRITY FOR "VIKRAM"."DEBUG_TABLE" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."DESTINATION" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."CLASSES" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."CALL_STACKS" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."ERRORS" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."EXCEPTION_TABLE" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."LOG_TABLE" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."MAJOR_STATS" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."SOURCE" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."ERROR_STACKS" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."REGISTERED_STUDENTS" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."STUDENTS" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."RS_AUDIT" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."TABNUM" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."TEMP_TABLE" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."ROOMS" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."TAB1" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."TAB3" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."TMP" IMMEDIATE CHECKED;
SET INTEGRITY FOR "VIKRAM"."TAB2" IMMEDIATE CHECKED;
TERMINATE;

But, the problem with above approach is that the order of the tables is not as per the dependencies with a result that you may get this error:

DB21034E  The command was processed as an SQL statement because it was not a
valid Command Line Processor command.  During SQL processing it returned:
SQL3608N  Cannot check a dependent table "VIKRAM.REGISTERED_STUDENTS" using
the SET INTEGRITY statement while the parent table or underlying table
"VIKRAM.STUDENTS" is in the Set Integrity Pending state or if it will be put
into the Set Integrity Pending state by the SET INTEGRITY statement.
SQLSTATE=428A8

You have to run above script iteratively few times to remove tables from check pending status. This is definitely cumbersome.

A more elegant approach

db2 connect to sample
db2 -tx +w "with gen(tabname, seq) as( select rtrim(tabschema) || '.' || rtrim(tabname) 
as tabname, row_number() over (partition by status) as seq 
from  syscat.tables 
WHERE status='C' ),r(a, seq1) as (select CAST(tabname as VARCHAR(3900)), seq 
from  gen where seq=1 union all select r.a || ','|| rtrim(gen.tabname), gen.seq 
from gen , r where (r.seq1+1)=gen.seq ), r1 as (select a, seq1 from r) 
select 'SET INTEGRITY FOR ' || a || ' IMMEDIATE CHECKED;' from r1 
where seq1=(select max(seq1) from r1)" > db2FixCheckPending.sql
db2 -tvf db2FixCheckPending.sql

A sample output:

SET INTEGRITY FOR VIKRAM.ERROR_STACKS,VIKRAM.CLASSES,VIKRAM.CALL_STACKS,VIKRAM.ERRORS,VIKRAM.REGISTERED_STUDENTS,
VIKRAM.ROOMS,VIKRAM.STUDENTS IMMEDIATE CHECKED;

The order of the tables in above script is as per dependencies and the above single statement will run check pending command in the right order.

The only limitation is the size of the SET command - based on this script it cannot be larger that 3900 characters. You can increase the size up to 30,000 characters, but in this case you would need to have System Temporary Tablespace of 32K, which is not available by default.

You can download the script from here

DB2 Backup Environment Script as part of a Disaster Recovery Plan

Mark Mulligan – (mark.mulligan.sr@sbcglobal.net) DB2 DBA

01 November 2008

A critical part of any database disaster recovery plan is to make sure that the environment surrounding the database is the way it was before the disaster in order to provide the same level of performance and level of service to users after a recovery. This article explains a process to backup UNIX and DB2 environment information that contains important information that can be used during a disaster recovery or operational recovery. An example of a KORN shell script is provided to help you keep this information so it will be available when you need it.

Introduction

If you ever need to recover a DB2 environment from a disaster or serious operational system problem and want the database to perform like it did before the recovery, information about the environment is the fastest, most thorough way to accomplish your goal. This information is critical to your success in addition to restoring DB2 database backups that are a part of your disaster recovery plan.

This article describes the information you need to have available and a script that will help accomplish this and tells you step-by-step, how to use them.

Pros and cons

The advantage of having UNIX and DB2 environment information backed up and stored off site with your database backups is that you will have the information you need to verify that the environment and the database are completely restored after a disaster and that the database will perform and provide the same level of service after the recovery.

If you do not have UNIX and DB2 environment information backed up and stored off site with your database backups, then you risk having performance problems and unreliable service from a database after a recovery from a disaster.

Description

Here is a list of information that is helpful to have in addition to a DB2 database backup that can be used to verify an environment has been recovered or rebuilt in the same way before you restore your database backup after a disaster or severe operational problem.

oslevel
This information can be used to verify that the same operating system level is available in support of the recovery.

lslpp -h | grep -vp bos | grep -vp device | grep -vp X11

This above command backs up a list of all of the software installed on the server and can be used to verify that the operating system and programs installed on the server are as they were before the disaster. (This can be accomplished with different commands on different operating systems.)

db2level

This information can be used to verify that the same DB2 level is available in support of a recovery.

cat /etc/services

A backup of this can be used to ensure that the TCP/IP ports used by a DB2 instance are the same as before. Ports used to support partitioned database environments and tools like the high performance unload can be confirmed as well.

df –g

A backup of this UNIX command can be used to verify the file systems, space allocations and mount point paths restored or created after a disaster. This must be verified before you restore a database backup to ensure that the restore will be successful and that the database will fit in the file systems on the server.
mount A backup of this information can be used to verify that file systems use the same mount options that were used before a database is restored. You can verify that jfs, jfs2, cio, rbr, rbw and other mount options match so that you will experience similar operating system I/O performance after recovering from a disaster.
id instance Use information backed up by this command to verify that the instance id is set up with the correct operating system groups.

ulimit –a

Output from this command run for DB2 instance id’s can be used to verify that the id is set up the same way as before.

crontab –l

List output from the crontab command will save a UNIX crontab schedule if you use that to run jobs under a DB2 instance id. If you use a different scheduler to run jobs then you should have those schedules backed up as part of your disaster recovery plan.

cp .profile

A copy of the .profile file for a DB2 instance id’s can be copied for reuse after a disaster.
userprofile A copy of the DB2 userprofile for a DB2 instance id can be copied to reuse after a disaster if you customize your db2profile environment with environment variables in the sqllib userprofile file that is shipped with DB2.

cp db2.conf

A copy of the Veritas NETBACKUP db2.conf backup configuration file for a DB2 instance can be restored to reestablish your DB2 backup and restore environment. (Change this to commands used to backup TSM, HP Data Protector or other types of backup configuration information depending on the software you use for backups.)
db2licm -l This information can be used to verify that you have applied the correct license information for a DB2 instance in the event that you have to recreate an instance after a disaster.

db2 get admin cfg

The output from this DB2 command can be used to reestablish the DB2 administration configuration during a restore.

db2cfexp

This DB2 command will export configuration information that can be imported to completely restore DB2 environment information like a DB2 database directory, a DB2 node directory and other DB2 configuration information. The db2cfimp filename command can be used to import this information and help recover a large part of a DB2 environment. This should usually be run before restoring a database backup.
db2 list node directory The output from this DB2 command can be used to verify that a DB2 node directory is like it was before a problem requiring a restore or rebuild.

db2 list database directory

The output from this DB2 command can be used to verify the DB2 database directory.

db2set -all

Use this DB2 command information to verify the DB2 registry settings for a DB2 instance as part of your recovery steps.

db2 get dbm cfg

Use this output to verify that the DB2 instance configuration is what it was before the operational problem or disaster.

db2 get db cfg for DATABASE_NAME

A backup of the output from this command can be used to verify that DB2 database configurations are restored correctly. (db2_all “db2 get db cfg for DATABASE_NAME” may be used for partitioned databases.)

Environment

The script in this article was tested on a server running the AIX operating system and DB2 versions 8, 9.1 and 9.5. The KORN shell script should work on any server that has a UNIX-like operating system that supports these types of scripts. (Linux and Linux/390 come to mind.) This script is designed to run in a native UNIX environment and is not intended to run under the DB2 Script Center Client Window. This script should be run under a DB2 instance id or scheduled to run on a daily basis for each DB2 instance id. This script is designed to run against stand alone DB2 environments or DB2 partitioned database environments though it must be run on each server that is part of a DB2 partitioned environment if you want to have complete coverage.

Before you get started, a couple of words of caution:

  1. Practice running the script and verify the results.
  2. Make sure you have a DB2 database backup solution implemented in addition to this DB2 environment backup solution. The combination of the two will help provide what you need to recover from an operational problem or a disaster.
  3. Storing DB2 database backups and DB2 environment backup information off site can help ensure that the information you need will be available after a disaster.

CRONTAB schedule example

00 00 * * * /db2util/scripts/db2backup_environment.ksh db2inst1 >/dev/null 2>&1
The script

#!/bin/ksh
#-----------------------------------------------------------------------
#  Program          : db2backup_environment.ksh
#  Description      : This script backs up DB2 environment information
#                   : that is important to have for disaster recovery
#                   : purposes.
#  Author           : Mark Mulligan
#  Date             : 05/18/2001
#-----------------------------------------------------------------------
ScriptVer='05/18/2001'
ScriptName='db2backup_environment'

Server=$(hostname)

if [ "$1" = "" ]
then    echo ""
        echo "Parm 1 Instance                         (required)."
        echo "Parm 2 Script Output Directory Path     (optional)  Defaults to /dbawork/reports"
        echo "Parm 3 Disaster Recovery Directory Path (optional)  Defaults to /dbawork/reports/DISASTER_RECOVERY_INFO"
        echo ""
        return 8
else    Instance=`echo $1 | tr '[A-Z]' '[a-z]'`
fi

if [ "$2" = "" ]
then    OutputDir="/dbawork/reports"
else    OutputDir="${2}"
fi

if [ -d $OutputDir ]
then    :
else    echo "OutputDir $OutputDir must exist before you run this script."
        echo "Run mkdir commands to create this directory path."
        return 8
fi

if [ "$3" = "" ]
then    DisasterRecoveryPath="/dbawork/reports/DISASTER_RECOVERY_INFO"
else    OutputDir="${3}"
fi

if [ -d $DisasterRecoveryPath ]
then    :
else    echo "DisasterRecoveryPath $DisasterRecoveryPath must exist before you run this script."
        echo "Run mkdir commands to create this directory path."
        return 8
fi

. $HOME/sqllib/db2profile
Datetime="`date +%Y%m%d%H%M%S`"
FileName=${OutputDir}/${ScriptName}_${Server}_${Instance}_${Datetime}
OutputFile=${FileName}.txt
DisasterRecoveryFile=${DisasterRecoveryPath}/${ScriptName}_${Server}_${Instance}_${Datetime}

echo "$ScriptName Vers: $ScriptVer started on " `uname -n` on `date` | tee $OutputFile

# backup the oslevel

echo "oslevel > ${DisasterRecoveryFile}.oslevel.txt" | tee -a $OutputFile
oslevel > ${DisasterRecoveryFile}.oslevel.txt

# list software installed on the server

echo "lslpp -h | grep -vp bos | grep -vp device | grep -vp X11 > ${DisasterRecoveryFile}.lslpp.txt" | tee -a $OutputFile
lslpp -h | grep -vp bos | grep -vp device | grep -vp X11 > ${DisasterRecoveryFile}.lslpp.txt

# backup db2level

echo "db2level > ${DisasterRecoveryFile}.db2level.txt" | tee -a $OutputFile
db2level > ${DisasterRecoveryFile}.db2level.txt

# copy /etc/services file that contains DB2 TCP/IP information

if [ -f /etc/services ]
then    echo "cp /etc/services ${DisasterRecoveryFile}.services" | tee -a $OutputFile
        cp /etc/services ${DisasterRecoveryFile}.services
fi

# display file system information

echo "df -g > ${DisasterRecoveryFile}.df_g.txt" | tee -a $OutputFile
df -g > ${DisasterRecoveryFile}.df_g.txt

# backup mount information

echo "mount > ${DisasterRecoveryFile}.mount.txt" | tee -a $OutputFile
mount > ${DisasterRecoveryFile}.mount.txt

# backup id information for the instance id

echo "id $Instance > ${DisasterRecoveryFile}.id.txt" | tee -a $OutputFile
id $Instance > ${DisasterRecoveryFile}.id.txt

# backup ulimit settings for the instance id

echo "ulimit -a > ${DisasterRecoveryFile}.ulimit.txt" | tee -a $OutputFile
ulimit -a > ${DisasterRecoveryFile}.ulimit.txt

# backup crontab

echo "crontab -l > ${DisasterRecoveryFile}.crontab.txt" | tee -a $OutputFile
crontab -l > ${DisasterRecoveryFile}.crontab.txt

# copy instance profile

if [ -f $HOME/.profile ]
then    echo "cp $HOME/.profile ${DisasterRecoveryFile}.profile" | tee -a $OutputFile
        cp $HOME/.profile ${DisasterRecoveryFile}.profile
fi

# copy userprofile

if [ -f $HOME/sqllib/userprofile ]
then    echo "cp $HOME/sqllib/userprofile ${DisasterRecoveryFile}.userprofile" | tee -a $OutputFile
        cp $HOME/sqllib/userprofile ${DisasterRecoveryFile}.userprofile
fi

# copy Netbackup configuration

if [ -f $HOME/db2.conf ]
then    echo "cp $HOME/db2.conf ${DisasterRecoveryFile}.db2.conf" | tee -a $OutputFile
        cp $HOME/db2.conf ${DisasterRecoveryFile}.db2.conf
fi

# backup DB2 license information

echo "db2licm -l > ${DisasterRecoveryFile}.license.information" | tee -a $OutputFile
db2licm -l > ${DisasterRecoveryFile}.db2licm.license.information
cp $HOME/sqllib/adm/*.lic $DisasterRecoveryPath/. >/dev/null 2>&1

# backup DB2 administration server configuration.



echo "db2 get admin cfg > ${DisasterRecoveryFile}.admin.cfg" | tee -a $OutputFile
db2 get admin cfg > ${DisasterRecoveryFile}.admin.cfg

# export database directory, node directory, instance configuration and other DB2 environment information

echo "db2cfexp ${DisasterRecoveryFile}_db2cfexp.bak backup" | tee -a $OutputFile
db2cfexp ${DisasterRecoveryFile}_db2cfexp.bak backup

echo "db2 list node directory > ${DisasterRecoveryFile}.db2.list.node.directory" | tee -a $OutputFile
db2 list node directory > ${DisasterRecoveryFile}.db2.list.node.directory

echo "db2 list database directory > ${DisasterRecoveryFile}.db2.list.database.directory" | tee -a $OutputFile
db2 list database directory > ${DisasterRecoveryFile}.db2.list.database.directory

# list DB2 environment registry variable settings

echo "db2set -all > ${DisasterRecoveryFile}.db2set" | tee -a $OutputFile
db2set -all > ${DisasterRecoveryFile}.db2set

# save off instance configuration for comparison purposes even though this can be imported
# using db2cfimp with the db2cfexp.bak file created above

echo "db2 get dbm cfg > ${DisasterRecoveryFile}.dbm.cfg" | tee -a $OutputFile
db2 get dbm cfg > ${DisasterRecoveryFile}.dbm.cfg

# copy db2nodes.cfg if it exists

if [ -f $HOME/sqllib/db2nodes.cfg ]
then    echo "cp $HOME/sqllib/db2nodes.cfg ${DisasterRecoveryFile}.db2nodes.cfg" | tee -a $OutputFile
        cp $HOME/sqllib/db2nodes.cfg ${DisasterRecoveryFile}.db2nodes.cfg
fi

db2 list database directory > $FileName.dat

cat $FileName.dat | while read column1 column2 column3 column4 column5 column6
do
        if [[ "$column1" = "Database" && "$column2" = "alias" ]]
        then    export Database=$column4
        fi
        if [[ "$column1" = "Directory" && "$column5" = "Indirect" ]]
        then    if [ -f $HOME/sqllib/db2nodes.cfg ]
                then    cat $HOME/sqllib/db2nodes.cfg | while read NodeNumber HostName LogicalPort NetName
                        do
                                echo "PARTITION ${NodeNumber}: db2 -v get db cfg for ${Database} show detail 
>> ${DisasterRecoveryFile}_${Database}.db.cfg" | tee -a $OutputFile
                                echo "DB2NODE=$NodeNumber" >> ${DisasterRecoveryFile}_${Database}.db.cfg
                                export DB2NODE=$NodeNumber
                                db2 -v terminate >> ${DisasterRecoveryFile}_${Database}.db.cfg
                                db2 -v connect to $Database >> ${DisasterRecoveryFile}_${Database}.db.cfg
                                db2 -v get db cfg for ${Database} show detail >> ${DisasterRecoveryFile}_${Database}.db.cfg
                        done
                else    echo "db2 -v get db cfg for ${Database} show detail >> ${DisasterRecoveryFile}_${Database}.db.cfg" | tee -a $OutputFile
                        db2 -v terminate > ${DisasterRecoveryFile}_${Database}.db.cfg
                        db2 -v connect to $Database > ${DisasterRecoveryFile}_${Database}.db.cfg
                        db2 -v get db cfg for ${Database} show detail > ${DisasterRecoveryFile}_${Database}.db.cfg
                fi
        fi
done

echo "$ScriptName Vers: $ScriptVer ended   on " `uname -n` on `date` | tee -a $OutputFile

echo "Output located in $OutputFile" | tee -a $OutputFile

Example

If you run the script in foreground without passing it any parameters, it will echo display the parameters that the script uses. The script output directory path and the disaster recovery directory path must exist before the script will run the commands to create information about your DB2 environment that can be used during a disaster recovery or recovery from a severe operational problem.

db2backup_environment.ksh

Parm 1 Instance                         (required).
Parm 2 Script Output Directory Path     (optional)  Defaults to /dbawork/reports
Parm 3 Disaster Recovery Directory Path (optional)  Defaults to /dbawork/reports/DISASTER_RECOVERY_INFO

When you run the script under a DB2 instance id in foreground and pass it the DB2 instance id you should see output displayed that is similar to the example shown below.

/db2util/scripts/db2backup_environment.ksh db2inst1
db2backup_environment Vers: 05/18/2001 started on  edwasu on Fri Nov 14 08:57:39 EST 2008
oslevel > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.oslevel.txt
lslpp -h | grep -vp bos | grep -vp device | grep -vp X11 > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.lslpp.txt
db2level > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.db2level.txt
cp /etc/services /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.services
df -g > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.df_g.txt
mount > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.mount.txt
id db2inst1 > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.id.txt
ulimit -a > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.ulimit.txt
crontab -l > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.crontab.txt
cp /db2home/db2inst1/.profile /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.profile
cp /db2home/db2inst1/sqllib/userprofile /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.userprofile
cp /db2home/db2inst1/db2.conf /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.db2.conf
db2licm -l > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.license.information
db2 get admin cfg > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.admin.cfg
db2cfexp /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739_db2cfexp.bak backup
db2 list node directory > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.db2.list.node.directory
db2 list database directory > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.db2.list.database.directory
db2set -all > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.db2set
db2 get dbm cfg > /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.dbm.cfg
cp /db2home/db2inst1/sqllib/db2nodes.cfg /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.db2nodes.cfg
PARTITION 0: db2 -v get db cfg for PROD01 show detail >> /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739_PROD01.db.cfg
PARTITION 1: db2 -v get db cfg for PROD01 show detail >> /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739_PROD01.db.cfg
PARTITION 2: db2 -v get db cfg for PROD01 show detail >> /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739_PROD01.db.cfg
PARTITION 3: db2 -v get db cfg for PROD01 show detail >> /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739_PROD01.db.cfg
PARTITION 4: db2 -v get db cfg for PROD01 show detail >> /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739_PROD01.db.cfg
db2backup_environment Vers: 05/18/2001 ended   on  edwasu on Fri Nov 14 08:57:48 EST 2008
Output located in /dbawork/reports/db2backup_environment_edwasu_db2inst1_20081114085739.txt

List Example

The output created by this script can be listed for a particular day to see the DB2 backup environment information as of that day. Cut and paste the file to a cat command to see the contents.

ls  /dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739*
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.admin.cfg
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.crontab.txt
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.db2.conf
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.db2.list.database.directory
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.db2.list.node.directory
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.db2level.txt
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.db2licm.license.information
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.db2nodes.cfg
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.db2set
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.dbm.cfg
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.df_g.txt
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.id.txt
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.lslpp.txt
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.mount.txt
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.oslevel.txt
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.profile
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.services
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.ulimit.txt
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739.userprofile
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739_PROD01.db.cfg
/dbawork/reports/DISASTER_RECOVERY_INFO/db2backup_environment_edwasu_db2inst1_20081114085739_db2cfexp.bak

Conclusion

A DB2 Backup Environment Script can be an integral part of your disaster recover plan to protect the valuable information assets in the databases you support. It can help give you information about the operating system, software installed on that system and database environments that you can use to verify that the restored database and environment will operate in the same way that it did before the disaster.

If you have any questions related to this article, feel free to contact me, Mark Mulligan, at mark.mulligan.sr@sbcglobal.net and I will be glad to help in any way that I am able

About the author

Mark Mulligan is a DBA living in the Dallas-Fort Worth area of Texas. He has a Bachelor of Science Degree in Business Administration from Merrimack College in North Andover, Massachusetts, and 30 years of experience working with computer software design, development, maintenance, enhancement and support. He has 10 years of experience working with DB2 on servers running UNIX-like operating systems and 15 years of experience working with DB2 on Z/OS. Mark can be contacted at mark.mulligan.sr@sbcglobal.net.

How to concatenate rows together?

Say, you want to concatenate all rows of a column and show it as a single row in the same fashion aggregate function works.

$ db2 "create table t1(line# int not null, word varchar(20))"
$ db2 "insert into t1 values(1,'The'),(1,'horse'),(1,'is'),(1,'white')"
$ db2 "insert into t1 values(2,'The'),(2,'quick'),(2,'brown'),
       (2,'fox'),(2,'jumps'),(2,'over'),(2,'the'),(2,'dog')"
$ db2 "select * from t1"
LINE#       WORD                
----------- --------------------
          1 The                 
          1 horse               
          1 is                  
          1 white               
          2 The                 
          2 quick               
          2 brown               
          2 fox                 
          2 jumps               
          2 over                
          2 the                 
          2 dog                 

But, you want this to be in a single row. For example:

1 The horse is white
2 The quick brown fox jumps over the dog

SQL Server or Sybase allows you to concatenate string together. For example, you could use the following construct to get string concatenation.

declare @phrase varchar(1024)
set @phrase=''
select @phrase= word + @phrase from t1

People can debate this forever that why are you doing this processing at the server level against the wishes of RDBMS engine. Normally, the front end should have logic to do this processing. It is like destroying 1st normal form with a concatenated list.

Again, we do always compare things without rationalization. Read the book Predictably Irrational if you want to know that how humans beings are irrational by nature.

Yes, we can do same in DB2 in several different fashions. For example:

  1. Use XMLAGG Function
  2. Use a C UDF
  3. Use recursive query
  4. Use a SQL UDF by opening a cursor

Method - 1 : use XMLAGG function

SELECT line#, replace(replace(xml2clob(xmlagg(xmlelement(NAME a, word))),'<A>',''),'</A>',' ') 
FROM t1 
GROUP BY line#

Here, we use XML capability of DB2 to turn a data type to XML using XMLELEMENT. Use XMLAGG to aggregate the values and use XML2CLOB to view the data. We use REPLACE to trim XML tag. The output is as follows:

1 The horse is white
2 The quick brown fox jumps over the dog

Method - 2 : C UDF to process all rows (joinstr.c)

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <sqludf.h> 

#define STR_LEN 1024

struct SCRATCHDATA
{
    int changed;
    char str[STR_LEN];
};


void SQL_API_FN joinstr(SQLUDF_VARCHAR *inStr, SQLUDF_INTEGER *changed, SQLUDF_VARCHAR *outStr,
SQLUDF_SMALLINT *inStrIND, SQLUDF_INTEGER *changedIND, SQLUDF_SMALLINT *outStrIND, SQLUDF_TRAIL_ARGS_ALL)
{
  struct SCRATCHDATA *sp;
  sp = (struct SCRATCHDATA *) SQLUDF_SCRAT->data;

  switch (SQLUDF_CALLT)
  {
    case SQLUDF_FIRST_CALL:
      memset(sp,0,sizeof(struct SCRATCHDATA));
      if(*inStrIND >= 0)
      {
         strcpy(sp->str, inStr);
         if (*changedIND >= 0)
           sp->changed = *changed;
      }
      break;
    case SQLUDF_NORMAL_CALL:
      if(*inStrIND >= 0)
      {
         if (*changedIND >= 0)
         {
             if (*changed != sp->changed)
                memset(sp,0,sizeof(struct SCRATCHDATA));
             else
                strcat(sp->str, " ");
             strcat(sp->str, inStr);
             sp->changed = *changed;
         }
      }
      break;
    case SQLUDF_FINAL_CALL:
      break;
  }
  strcpy(outStr, sp->str);
  *outStrIND = 0;
}

joinstr.exp

joinstr

makefile

DB2PATH=${HOME}/sqllib

BLDRTN=./bldrtn

ALIAS=sample
UID=
PWD=

COPY=cp
ERASE=rm -f

all : \
        srv

srv : \
        rtn

rtn : \
        joinstr

clean : 
        $(ERASE) *.o

#****************************************************************************
#                  2h - make cleanall
#****************************************************************************

cleanall : \
        clean
        $(ERASE) joinstr
        $(ERASE) $(DB2PATH)/function/joinstr*

joinstr : joinstr.c
        $(BLDRTN) joinstr $(ALIAS)
        ./udfcat $(ALIAS) > udfcat.log

udfcat - Catalog the UDF

#! /bin/ksh
export ${FENCED:="NOT FENCED THREADSAFE"}
export ${SHLIBNAME:="joinstr"}
TOK=$(date +"%y%m%d%H%M%S")
export SHLIBNAME=joinstr$TOK
rm -f ~/sqllib/function/joinstr*
cp -f joinstr ~/sqllib/function/$SHLIBNAME

db2 -tv <<!EOF

CONNECT TO $1;

DROP FUNCTION JOIN_STR; 
CREATE FUNCTION JOIN_STR
(
  IN_STR       VARCHAR(1024),
  LINE_NO      INTEGER
)
RETURNS VARCHAR (1024)
EXTERNAL NAME '${SHLIBNAME}!joinstr'
SPECIFIC JOINSTR
LANGUAGE C
PARAMETER STYLE DB2SQL
${FENCED}
RETURNS NULL ON NULL INPUT
NOT DETERMINISTIC
NO SQL
NO EXTERNAL ACTION
SCRATCHPAD 2000
FINAL CALL
DISALLOW PARALLEL;

How to compile?

Unzip joinstr.tar.gz all files into a directory. Copy bldrtn from ~/sqllib/samples/c to your current directory and run make.

How to run?

db2 "select line#, max(join_str(word,line#))  from t1 group by line#"

We use MAX function to do the aggregation but the actual work is done by the C UDF to join words together.

Method - 3: SQL UDF to join words for a given line

CREATE FUNCTION get_str(v_line# INTEGER)
LANGUAGE SQL
RETURNS VARCHAR(1024)
BEGIN ATOMIC
  DECLARE str VARCHAR(1024);
  SET str = '';
  loop1: FOR row AS (SELECT word FROM t1 where line# = v_line#)
  DO
    IF row.word IS NOT NULL THEN
       SET str = str || row.word || ' ';
    END IF;
  END FOR loop1;
  RETURN str;
END
@

db2 "SELECT line#, get_str(line#) FROM t1 GROUP BY line#"

Method - 4 : Recursive SQL to join words for a line

WITH temp1(line#, word, all_words, cnt) as
(SELECT min(line#), min(word), VARCHAR(min(word),50),SMALLINT(1) FROM t1 a
group by a.line#
UNION ALL
SELECT a.line#, a.word, b.all_words||' '||a.word,SMALLINT(b.cnt+1)
FROM t1 a, temp1 b
WHERE a.line# = b.line#
AND a.word > b.word
AND a.word = (select min(c.word) from t1 c where c.line# = b.line# and c.word > b.word) 
)
SELECT d.line#, all_words FROM temp1 d
where cnt = (SELECT max(cnt) FROM temp1 e where e.line# = d.line#);

1 The horse is white                                
2 The brown dog fox jumps over quick the            

The question comes - which one of the above should I use?

If you are averse to C and hate to take burden of compiling C UDF as shown, use Method - 1 XMLAGG function. C UDF is light weight and uses scratchpad as you are going through the rows. It will give you the best performance. The recursive SQL is a good example but it could be slow.

Speed up MQT - Tips

Edin I. Aganagic shares his tips on how to increase incremental refresh/maintenance performance of MQTs in DB2.

  1. Create an index on the GROUP BY columns. It helps speed up MQT look-ups needed to decide whether existing row needs to be changed, new one inserted, or last one deleted
  2. If not aggregating, create the index on the set of columns that form a unique key usually by concatenating the unique key columns of the individual base tables.
  3. If possible, define the base table columns used in the GROUP BY clause of REFRESH IMMEDIATE MQTs as NOT NULL makes finding matching rows in the MQT easier
  4. Define RI relationships wherever applicable allows various loss-less join specific optimizations (even not enforced)
  5. Always keep the base table and MQT statistics up-to-date and it helps the optimizer choose an optimal access plan

DB2 ODBC Driver double quotes

If you are migrating your ODBC related code from one database to DB2, you might run into an issue of single quote vs. double quote issue.

For example: Your ODBC code might issue a call to a Stored Procedure using double quote as shown below:

CALL TESTSP("PARAM1","PARAM2")

Your previous database might allow you to pass parameters using double quotes but this will fail in DB2 since literals used in double quotes are treated as table name or column name in DB2 and hence above code will not work.

What if, if you have thousands of such statements and there will be some conversion effort for such statements. There is a workaround to this problem but you will be on your own to use this workaround as this is not documented and you are not likely to get any support for this from DB2.

It is strongly advised to be very careful if attempting to use either of these work around. They are not recommended for general implementation and may cause undesirable behavior in some cases.

You can add following entries in db2cli.ini file for this to take effect.

PATCH1 = 2048

This patch1 will replace all double-quotes with single-quotes. This patch is used to help port SQL that has been written using literals with double-quotes instead of the DB2 required single quotes.

PATCH2=62

This patch2 does a similar mapping as PATCH1=2048 but avoids some specific situations where DB2 thinks that the quotes are around an SQL object (which needs to remain double-quoted).

Again a no-nonsense disclosure: Use it at your own risk as this is not supported officially.

C Table UDF to join flat file with your SQL

Sometimes, it may be necessary to expose data in your flat file with the relational data. There might be some situations where there is no option to load that data in a table. But still, you need a way to use this flat file data in your SQL statements.

DB2 provides the concept of a table function that you can write. Here is one C table UDF example that reads a file and returns that data through your SQL statement.

Download sample code ufd.zip

udfreadfile.c

#include <stdio.h>
#include <stdarg.h>
#include <sqludf.h> 

#define  DEBUG 0

struct SCRATCHDATA
{
  long idx;
  FILE *p;
};


void hardw(char *msg, ...)
{
   FILE *p;
   char name[500];
   va_list argp;

   if (DEBUG) 
   {
      strcpy(name, getenv("HOME"));
      if (name == NULL)
      {
         strcpy(name, "/tmp/hard.txt");
      } else
      {
         strcat(name, "/sqllib/hard.txt");
      }
      p = fopen(name, "a");
      if (p == NULL)
      {
         p = fopen("/tmp/hard.txt", "a");
      }
      if (p != NULL)
      {
         va_start(argp, msg);
         vfprintf(p, msg, argp);
         va_end(argp);
         fprintf(p, "\n");
         fflush( p);
         fclose(p);
      } 
   }
} 

void SQL_API_FN  readFile(SQLUDF_VARCHAR *fileName, 
              SQLUDF_VARCHAR *cm15, 
              SQLUDF_NULLIND *fileNameInd, 
              SQLUDF_NULLIND *cm15IND, 
              SQLUDF_TRAIL_ARGS_ALL)
{
  char myData[100];
  long i = 0;
  char errMsg[1000];
  struct SCRATCHDATA *sp;
  sp = (struct SCRATCHDATA *) SQLUDF_SCRAT->data;

  switch (SQLUDF_CALLT)
  {
  case SQLUDF_TF_FIRST:
      hardw("in SQLUDF_TF_FIRST ******");
      if (*fileNameInd == -1)
      {
         strcpy( SQLUDF_STATE, "38777");
         sprintf (errMsg, "Null file name.");
         strcpy( SQLUDF_MSGTX, errMsg);
         break;
      }
      sp->p = fopen(fileName, "r");
      if (sp->p == NULL)
      {
         strcpy( SQLUDF_STATE, "38777");
         sprintf (errMsg, "Given file can not be opened", fileName);
         strcpy( SQLUDF_MSGTX, errMsg);
         hardw("File '%s'can not be opened", fileName);
         break;
      }
      sp->idx = 0;
    case SQLUDF_TF_OPEN:
      hardw("in SQLUDF_TF_OPEN ******");
      break;
    case SQLUDF_TF_FETCH:
      hardw("in SQLUDF_TF_FETCH %ld ******", sp->idx);
      if (fgets(myData, 100, sp->p) == NULL)
      {
         strcpy( SQLUDF_STATE, "02000");
         break;
      }
      myData[strlen(myData)-1] = '\0';
      strcpy(cm15, myData);
      *cm15IND = 0;
      sp->idx++;
      break;
    case SQLUDF_TF_CLOSE:
       hardw("in SQLUDF_TF_CLOSE ******");
       fclose(sp->p);
      break;
    case SQLUDF_TF_FINAL:
      /* close the file */
      hardw("in SQLUDF_TF_FINAL ******");
      sp->idx = 0;
      break;
  }
}

udfreadfile.exp

readFile

How to catalog Table UDF (udfcat)

The catalog definition to register C table UDF in DB2 catalog is given below:

#! /bin/ksh
export ${FENCED:="FENCED NOT THREADSAFE"}
export ${SHLIBNAME:="udfreadfile"}
TOK=$(date +"%y%m%d%H%M%S")
export SHLIBNAME=udfreadfile$TOK
rm -f ~/sqllib/function/udfreadfile*
cp -f udfreadfile ~/sqllib/function/$SHLIBNAME

db2 -tv <<!EOF

CONNECT TO $1;

DROP FUNCTION READ_FILE;

CREATE FUNCTION READ_FILE
(
  FILE_NAME       VARCHAR(128)
)
RETURNS TABLE (c1 varchar(15))
EXTERNAL NAME '${SHLIBNAME}!readFile'
SPECIFIC READFILE
LANGUAGE C
PARAMETER STYLE DB2SQL
${FENCED}
RETURNS NULL ON NULL INPUT
NOT DETERMINISTIC
NO SQL
NO EXTERNAL ACTION
SCRATCHPAD
FINAL CALL
DISALLOW PARALLEL;

The above is a script so make sure you set execute bit on it. i.e. chmod +x udfcat

How to compile?

On your platform, copy bldrtn from your ~/sqllib/sample/c dir to your local directory and use this makefile to compile this program.

DB2PATH=${HOME}/sqllib

BLDRTN=./bldrtn

ALIAS=sample
UID=
PWD=

COPY=cp
ERASE=rm -f

all : \
	srv

srv : \
	rtn

rtn : \
	udfreadfile

clean : 
	$(ERASE) *.o

#****************************************************************************
#		   2h - make cleanall
#****************************************************************************

cleanall : \
	clean
	$(ERASE) udfreadfile
	$(ERASE) $(DB2PATH)/function/udfreadfile

udfreadfile : udfreadfile.c
	$(BLDRTN) udfreadfile $(ALIAS)
	./udfcat $(ALIAS) > udfcat.log

How this UDF works?

The sample code given in this UDF reads a flat file having a column of data. The size of the data is 15 character and file can have as many rows as possible.

The UDF defines a scratchpad memory area where we store the file pointer so that it can be used for each row. When this UDF is executed for each row, the different segment of code are executed in certain order.

For example:

SQLUDF_TF_FIRST is used to initialize variables or open a file pointer. This will be executed only once.
SQLUDF_TF_FETCH is used for each row execution. Remember, there is no while loop to go through the contents of the file since different code segments are used for different events.
Use SQLUDF_TF_CLOSE or SQLUDF_TF_FINAL to free up file pointer or free up memory.

FINAL CALL is used in UDF definition so that DB2 can invoke calls for SQLUDF_TF_FIRST and SQLUDF_TF_FINAL code segments. SCRATCHPAD is used to store file pointer and make sure that the size of the SCRATCHPAD is sufficient enough to hold the structure. The default size is only 100 bytes.

How to invoke the table UDF?

SELECT * FROM TABLE (db2inst1.read_file('$HOME/tableudf/datafilename.txt'))

You can use this UDF in your query to join it with other tables etc or use it in a sub-query.

Julian day in DB2

DB2 provides a way to calculate date from a Julian day.

For example: 2008075 should match to the 75th day of 2008 and i.e. 2008-03-15.

If you try to do,

$ db2 values date(2008075)

You will get a date of 12/03/5498 but that is what you do not want. The argument to DATE was supplied as an INT. You must supply julian day as '2008075' and it must be CHAR(7).

$ db2 values date('2008075')

and you should get '03/15/2008'.

What if, you want to calculate reverse date from a Julian day. For example, you should get '03/15/2008' from '2008075'.

Use the following SQL to get reverse from the Julian day.

$ db2 values cast(trim(char(year('2008-03-15')))||REPEAT('0',(3-LENGTH(TRIM(CHAR(DAYOFYEAR('2008-03-15'))))))||TRIM(CHAR(DAYOFYEAR('2008-03-15'))) as char(7))

and you will get '2008075'.

By the way, DB2 has a function called Julian_day but it gives the number of days from Jan 1, 4713 BC.

$ db2 values julian_day('2008-03-15') and you will get number of days 2454541 since Jan 1, 4713 BC.

--#SET TERMINATOR @

DROP FUNCTION DATE_JULIAN
@

CREATE FUNCTION DATE_JULIAN(IN_DATE DATE)
RETURNS CHAR(7)
RETURN 
VALUES CAST(TRIM(CHAR(YEAR(IN_DATE)))||
REPEAT('0',(3-LENGTH(TRIM(CHAR(DAYOFYEAR(IN_DATE))))))||
TRIM(CHAR(DAYOFYEAR(IN_DATE))) AS CHAR(7))
@

values date_julian(date('03/15/2008'))@
values date_julian(date('2008-03-15'))@

Above 2 values will return 2008075.

How to create Explain Plan of DB2 Stored Procedures?

Step-1 : Create Explain tables

See this entry http://www.db2ude.com/?q=node/65 for how to create explain tables in DB2.

Please note: You need to create explain tables in the USER-ID of the logged-in user. If you created explain plans in the schema name of some userid, other logged-in users can not use the explain tables owned by other users. In other words, each user should have their own explain tables. This is true even when you are using SET CURRENT SCHEMA command to set schema name for the tables that you are using.

Step-2 : Set parameters so that query compiler can populate explain tables

You have 2 options here:

  1. Set it once at the instance level so that every procedure you create is explained
  2. Set it at DB2 session level so that you control which procedures need to be explained.

Personally, I like second option.

Option - 1 : Set at the instance level

$ db2set DB2_SQLROUTINE_PREPOPTS="EXPLAIN ALL EXPLSNAP ALL"
$ db2stop
$ db2start

Please note: Instance rebound is necessary for the DB2 registry variables to take effect.

Option - 2 : Set at the DB2 session level

In your DB2 Stored Procedure script, call SET_ROUTINE_OPTS parameter to let query compiler to create explain informations. For example, your stored procedure script may look like following:

testsp.sql
----------

--#SET TERMINATOR ;

CALL SYSPROC.SET_ROUTINE_OPTS('EXPLAIN ALL');

SET CURRENT SCHEMA = 'DB2INST1';

SET CURRENT FUNCTION PATH SYSIBM,SYSCAT,SYSFUN,DB2INST1;

DROP PROCEDURE testsp;

--#SET TERMINATOR @

CREATE PROCEDURE testsp
LANGUAGE SQL
BEGIN
  DECLARE c1 CURSOR WITH RETURN FOR VALUES CURRENT TIMESTAMP;
  OPEN c1;
END
@

Create SQL Procedure

$ db2 connect to sample
$ db2 -tf testsp.sql

Step-3 : Mapping procedure name to package Name

Each DB2 SQL routine has a procedure name (which is used in CALL statements), but is represented internally by a system-generated package name. The data in the explain tables will refer to the package name, not the procedure name, hence we must know the procedure-to-package mapping so that we can give the proper package name to exfmt when dumping plans.

select 	deps.bschema SCHEMA, 
	procs.routinename PROCEDURE, 
	deps.bname PACKAGE, 
	procs.valid VALID
  from 	sysibm.sysdependencies deps,
       	sysibm.sysroutines procs
 where 	deps.dtype = 'F' 
   and 	deps.btype = 'K'
   and 	procs.specificname  = deps.dname
   and 	procs.routineschema = deps.dschema
 order 	by 1,2;

Step -4 : Extract Explain Plan using db2exfmt or db2expln tool.

You can call db2exfmt or db2expln tool using the package name determined in the previous step.

db2exfmt -d <dbname> -e <schema> -s <schema> -w -1 -n <package name> -g -# 0 -o <output file>
db2expln -d <dbname> -c <schema> -p <package name> -s 0 -g -o <output file> 

Step -5 : Putting it all together to get the explain plan in a single step.

After you have have created procedures, you need minimum 2 steps to extract the explain plan for a SQL Procedure. First - get the package name used internally by the SQL Procedure and Second - use either db2exfmt or db2expln tool to get the explain plan.

For Linux/Unix Systems

#!/bin/ksh

if [ $# -eq 0 ] ; 
then
   echo Usage: `basename $0` schema.procedure
   exit -1
fi

schema=${1%%.*}
procedure=${1#*.}

if [ "$schema" = "" ] || [ "$schema" = "$procedure" ] ;
then
   schema=$USER
   procedure=$1
fi

dbname=`db2 -tx "select reg_var_value from sysibmadm.reg_variables \
                 where reg_var_name = 'DB2DBDFT' \
                 and dbpartitionnum = 0"`

if [ "$dbname" = "" ] ;
then
   echo DB2 registry variable DB2DBDFT is not set.
   exit -1
fi

pkgname=`db2 -tx "select VARCHAR(deps.bname,25) PACKAGE \
  from  sysibm.sysdependencies deps,\
        sysibm.sysroutines procs\
 where  deps.dtype = 'F' \
   and  deps.btype = 'K'\
   and  procs.specificname  = deps.dname\
   and  procs.routineschema = deps.dschema\
   and  procs.routineschema = upper('$schema')\
   and  procs.routinename = upper('$procedure')"`

echo Creating explain plan in database $dbname for $schema.$procedure using package $pkgname

#db2expln -d $dbname -c $schema -p $pkgname -s 0 -g -o $procedure.exp
db2exfmt -d $dbname -e $schema -s $schema -w -1 -n $pkgname -g -# 0 -o $procedure.exp

How to run in Linux/Unix

$ ./exp testsp

or

$ ./exp db2inst1.testsp

Note: If you do not specify schema name, logged-in user id is used as a schema name. The syntax is schema.procedure.

For Windows

You need 2 scripts to automate db2 explain plan for a SQL Procedure.

Script-1: pkg.cmd

@echo off
REM Package name for a stored procedure

SET SCHEMA=%1%
SET PROCEDURE=%2%

db2 connect to sample > NUL

db2 -x  select VARCHAR(deps.bname,25) PACKAGE ^
  from  sysibm.sysdependencies deps,^
        sysibm.sysroutines procs ^
 where  deps.dtype = 'F' ^
   and  deps.btype = 'K' ^
   and  procs.specificname  = deps.dname ^
   and  procs.routineschema = deps.dschema ^
   and  procs.routineschema = upper('%SCHEMA%') ^
   and  procs.routinename = upper('%PROCEDURE%')

db2 connect reset > NUL

Script-2: exp.cmd

@echo off
cls
@ECHO Executed by: %USERNAME% Machine: %COMPUTERNAME% On %OS% %DATE% 

IF "%1" == "" GOTO :Usage1

IF "%2" == "" GOTO :Usage2

SET SCHEMA=%1
SET PROCEDURE=%2

FOR /F "tokens=1" %%K in ('call pkg.cmd %SCHEMA% %PROCEDURE%') do SET PKGNAME=%%K
db2exfmt -d SAMPLE -e %USERNAME% -s %USERNAME% -w -1 -n %PKGNAME% -g -# 0 -o %PROCEDURE%.EXPLAIN

goto :end

:Usage1
ECHO You did not provide schema name for this script
ECHO.
ECHO Usage: exp schame_name procedure_name
ECHO.

goto :end

:Usage2
ECHO You did not provide procedure name for this script
ECHO.
ECHO Usage: exp schame_name procedure_name
ECHO.

:end

How to run in Windows

C:\exp schemaname procedurename

Please note: The database name was hard coded in the scripts. I did not had time to automate this. If you, please send me revised script at vikram@zinox.com.

All 3 scripts are also attached in a zip file at http://www.db2ude.com/files/explainscript.zip

What if Stored Procedures contain dynamic SQL?

If your stored procedure has dynamic SQL calls such as use of the EXECUTE command or use of the global temporary tables (GTT), you have dynamic SQL statements for which you will not get the explain plan during compile process. You need to execute the stored procedure once to get the explain plan for all dynamic SQL statements after you have completed the above step.

Follow this procedure to get the explain plan when you have used GTTs or dynamic SQL statements.

For example, you have the following stored procedure having dynamic as well as static calls.

dynsp.sql
------

CREATE PROCEDURE DYNSP
language SQL
BEGIN
   DECLARE GLOBAL TEMPORARY TABLE SESSION.tt_mytemp
   (
      c1 INT NOT NULL,
      c2 INT NOT NULL,
      c3 INT NOT NULL
   ) NOT LOGGED WITH REPLACE ON COMMIT PRESERVE ROWS;
   DECLARE GLOBAL TEMPORARY TABLE SESSION.tt_yrtemp
   (
      c1 INT NOT NULL,
      c2 INT NOT NULL,
      c3 INT NOT NULL
   ) NOT LOGGED WITH REPLACE ON COMMIT PRESERVE ROWS;


  INSERT INTO SESSION.tt_mytemp VALUES (1,2,3),(2,3,4),(3,4,5),(4,5,6),(5,6,7);
  INSERT INTO SESSION.tt_yrtemp VALUES (5,6,7),(4,5,6),(8,9,10),(9,10,11),(10,11,12);

  BEGIN
     DECLARE c1 CURSOR WITH RETURN FOR SELECT t1.c1,t1.c2,t1.c3 
                                       FROM SESSION.tt_mytemp t1, SESSION.tt_yrtemp t2
                                       WHERE t1.c1 = t2.c2;
     OPEN c1;
  END;   
END
@

db2 connect to sample
db2 "CALL SYSPROC.SET_ROUTINE_OPTS('EXPLAIN ALL')"
db2 -tf dynsp.sql

The above step gives you explain plan for all static SQL calls in the SP. Since you have used GTT, you need to do one extra step of getting the explain plan

db2 connect to sample
db2 set current explain mode yes
db2 "call dynsp()"
db2 set current explain mode no
./exp dynsp

Now, look at the dynsp.exp file and it will have explain plan for all dynamic SQL statements as well as static SQLs contained in your stored procedure.

Acknowledgment: Thanks to John Hornibrook and Sunil Kamath of DB2 Toronto Lab to provide help on this.

Optimistic Locking in DB2 9.5 LUW

Improve concurrency with DB2 9.5 optimistic locking

New optimistic locking feature to avoid maintaining long-lived locks

IBM® DB2®, Version 9.5 for Linux®, UNIX®, and Windows® provides enhanced optimistic locking support, a technique for SQL database applications that does not hold row locks between selecting and updating, or deleting rows. Gain an understanding of this enhancement, and learn how applications using this programming model benefit from this enhanced optimistic locking feature and gain improved concurrency.

Pessimistic vs. optimistic locking

Pessimistic locking

A pessimistic locking strategy assumes that the probability is high that another user will try to modify the same row in a table that you are changing. A lock is held between the time a row is selected and the time that a searched update or delete operation is attempted on that row (for example, by using the repeatable read isolation level or lock the table in exclusive mode). The advantage of pessimistic locking is that it is guaranteed that changes are made consistently and safely. The major disadvantage is that this locking strategy might not be very scalable. On a system with many users or with long-living transactions, or when transactions involve a greater number of entities, the probability of having to wait for a lock to be released increases.

Figure 1 illustrates the functioning of pessimistic locking. Transaction 1 reads a specific record and places a lock on that row. It takes some time to decide whether the row will be updated. In the meantime, transaction 2 wants access to that same row, but it has to wait until the lock is released by Transaction 1. Until then, transaction 2 will receive results from its SELECT and can continue with its business logic.

Figure 1. Pessimistic locking concept

Pessimistic locking concept

Optimistic locking
The main problem with a pessimistic locking approach is that transactions have to wait for each other. A way to avoid this is to follow an optimistic locking strategy and assume that it is very unlikely that another user will try to change the same row that you are changing. If the row does change, the update or delete will fail, and the application logic handles such failures by, for example, retrying the select. With this approach, no locks are held between selecting and updating, or deleting a row. But, consequently, this method requires a way to ensure that the data has not changed between the time of being read and being altered. Although more retry logic in the application is needed, the primary advantage of an optimistic locking strategy is that it minimizes the time for which a given resource is unavailable for use by other transactions and thus will be a more scalable locking alternative than pessimistic locking.

Figure 2 illustrates the idea behind optimistic locking. Similar to Figure 1, transaction 1 reads a specific record but then releases its lock. Transaction 2 is now not prevented from retrieving that same row. Before committing the transaction, both transaction 1 and transaction 2 must check whether the row has changed after the previous SELECT. If a change has occurred, the transaction must restart with a new SELECT in order to retrieve the current data. However, if that row has not been changed after the previous SELECT, the data can be successfully updated.

Figure 2. Optimistic locking concept

Optimistic locking concept

Enhanced optimistic locking with DB2 9.5

Optimistic locking in DB2 9.5 improves scalability by minimizing the time for which a given resource is unavailable for use by other transactions. Because the database manager can determine when a row is changed, it can ensure data integrity while limiting the time that locks are held. With optimistic concurrency control, the database manager releases the row or page locks immediately after a read operation.

DB2 9.5 for Linux, Unix, and Windows adds support for easier and faster optimistic locking with no false positives. This support is added using the following new SQL functions, expressions, and features:

  • Row identifier (RID_BIT or RID) built-in function: This built-in function can be used in the SELECT list or predicates statement. In a predicate, for example, WHERE RID_BIT(tab)=?, the RID_BIT equals predicate is implemented as a new direct access method in order to efficiently locate the row. Previously, thus called values optimistic locking with values was done by adding all the selected column values to the predicates and relying on some unique column combinations to qualify only a single row, with a less efficient access method.

  • ROW CHANGE TOKEN expression: This new expression returns a token as BIGINT. The token represents a relative point in the modification sequence of a row. An application can compare the current row change token value of a row with the row change token value that was stored when the row was last fetched to determine whether the row has changed.

  • Time-based update detection: This feature is added to SQL using the ROW CHANGE TIMESTAMP expression. To support this feature, the table needs to have a new generated row change timestamp column defined to store the timestamp values. This can be added to existing tables using the ALTER TABLE statement, or the row change timestamp column can defined when creating a new table. The row change timestamp column's existence also affects the behavior of optimistic locking in that the column is used to improve the granularity of the row change token from page level to row level, which could greatly benefit optimistic locking applications.

  • Implicitly hidden columns: For compatibility, this feature eases the adoption of the row change timestamp columns to existing tables and applications. Implicitly hidden columns are not externalized when implicit column lists are used. For example a SELECT * against the table does not return a implicitly hidden columns in the result table and an INSERT statement without a column list does not expect a value for implicitly hidden columns, but the column should be defined to allow nulls or have another default value.

Applications using this programming model will benefit from the enhanced optimistic locking feature. Note that applications that do not use this programming model are not considered optimistic locking applications, and they will continue to work as before.

Figure 3 illustrates the functioning of DB2 9.5 optimistic locking. Both transaction 1 and transaction 2 read the same row, including the RID_BIT and the ROW CHANGE TOKEN value. Then transaction 1 updates the row after ensuring that the row has not changed after the previous SELECT by adding a RID_BIT and ROW CHANGE TOKEN predicate to the UPDATE statement. When transaction 2 now tries to update that same row using the same predicate as transaction 1, the row will not be found because the value of the ROW CHANGE TOKEN has changed regarding to the UPDATE of transaction 1. Transaction 2 has to start a retry in order to retrieve the current data.

Figure 3. Enhanced optimistic locking with DB2 9.5
DB2 optimistic locking concept

Enabling optimistic locking

Since the new SQL expressions and attributes for optimistic locking can be used with no DDL changes to the tables involved, you can easily try optimistic locking in your test applications.

Note that without DDL changes, optimistic locking applications may get more false negatives than with DDL changes. An application that does get false negatives may not scale well in a production environment because the false negatives may cause too many retries. Therefore, to avoid false negatives, optimistic locking target tables should be either be created with a ROW CHANGE TIMESTAMP column or altered to contain the ROW CHANGE TIMESTAMP column.

     CREATE TABLE EMPLOYEE (EMPNO CHAR(6) NOT NULL,
              ......
              ROWCHGTS TIMESTAMP NOT NULL
              GENERATED ALWAYS
              FOR EACH ROW ON UPDATE AS
              ROW CHANGE TIMESTAMP)

     ALTER TABLE EMPLOYEE ADD COLUMN
              ROWCHGTS TIMESTAMP NOT NULL
              GENERATED ALWAYS
              FOR EACH ROW ON UPDATE AS
              ROW CHANGE TIMESTAMP

These are a basic steps to be performed in order to enable optimistic locking support in your applications:

  • In the initial query, SELECT the row identifier using the RID_BIT() and RID() built-in function) and row change token for each row that you need to process.
  • Release the row locks so that other applications can SELECT, INSERT, UPDATE, and DELETE from the table (for example, use isolation level cursor stability or uncommitted read).
  • Perform a searched UPDATE or DELETE on the target rows, using the row identifier and row change token in the search condition, optimistically assuming that the unlocked row has not changed since the original SELECT statement.
  • If the row has changed, the UPDATE operation will fail and the application logic must handle the failure. For instance, the application retries the SELECT and UPDATE operations.

Usage scenario

An employee got a new job responsibility and is now working for another department. Two managers (the manager of the old department called Manager1 and Manager2 from the new department) are using a personnel administration application to update employee records in the EMPLOYEE table in the SAMPLE database. There is a possibility that both managers try to update the same employee record at the same time.

The EMPLOYEE table contains a implicitly hidden ROW CHANGE TIMESTAMP column and is accessed by Manager1 and Manager2 simultaneously. Manager1 selects the data from the EMPLOYEE table and later tries to update the same data. However, between his select and his update, Manager2 updates the same data. Manager2's update is successful, but Manager1's update fails.

Result from the SELECT (Manager1 and Manager2)

RID_BIT ROW CHANGE TOKEN EMPNO FIRSTNME LASTNAME PHONENO ROW CHANGE TIMESTAMP
x'0400400100000000
0000000000FA9023'
74904229642240 000010 CHRISTINE HAAS 3978 0001-01-01
00:00:00.000000
x'0500400100000000
0000000000FA9023'
74904229642240 000020 MICHAEL THOMPSON 3476 0001-01-01
00:00:00.000000
x'0600400100000000
0000000000FA9023'
74904229642240 000030 SALLY KWAN 4738 0001-01-01
00:00:00.000000

UPDATE statement (Manager2)


      UPDATE EMPLOYEE SET
            (FIRSTNME,LASTNAME,PHONENO) = ('CHRISTINE','HAAS','1092')
            WHERE RID_BIT(EMPLOYEE)=x'04004001000000000000000000FA9023' AND
            ROW CHANGE TOKEN FOR EMPLOYEE=74904229642240

Result from the UPDATE (Manager2)

RID_BIT ROW CHANGE TOKEN EMPNO FIRSTNME LASTNAME PHONENO ROW CHANGE TIMESTAMP
x'0400400100000000
0000000000FA9023'
141285645885181032 000010 CHRISTINE HAAS 1092 2007-12-2011:55:45.593000
x'0500400100000000
0000000000FA9023'
74904229642240 000020 MICHAEL THOMPSON 3476 0001-01-01
00:00:00.000000
x'0600400100000000
0000000000FA9023'
74904229642240 000030 SALLY KWAN 4738 0001-01-01
00:00:00.000000

UPDATE statement (Manager1)

      UPDATE EMPLOYEE SET
            (FIRSTNME,LASTNAME,PHONENO) = ('CHRISTINE','HAAS','1092')
            WHERE RID_BIT(EMPLOYEE)=x'04004001000000000000000000FA9023' AND
            ROW CHANGE TOKEN FOR EMPLOYEE=74904229642240

Result from the UPDATE (Manager1)

The update from Manager1 is unsuccessful. Since the ROW CHANGE TOKEN has changed by the UPDATE of Manager2, the ROW CHANGE TOKEN predicate of Manager1's UPDATE statement fails while comparing the token retrieved at the point of the SELECT and the current value after having been updated by Manager2's application. Thus the UPDATE fails to find the specified row. A message "SQL0100W No row was found for FETCH, UPDATE or DELETE; or the result of a query is an empty table. SQLSTATE=02000" is returned.

More detailed information and usage scenarios

More details can and usage scenarios be found in an IBM DeveloperWorks article "Improve concurrency with DB2 9.5 optimistic locking" at http://www.ibm.com/developerworks/db2/library/techarticle/dm-0801schuetz

How to setup the Self Tuning Memory Manager (STMM)

Self-tuning memory was first introduced in IBM ® DB2 ® 9.1 and simplifies the task of memory configuration by automatically setting optimal values for most memory configuration parameters, including buffer pools, package cache, locking memory, sort heap, and total database shared memory. When the self tuning memory manager (STMM) is enabled, the memory tuner dynamically distributes the available memory among the various memory consumers.

STMM modes of operation

STMM works in four different modes

  • Mode 1: DATABASE_MEMORY= AUTOMATIC
    In this case, the required memory is taken from and returned to the OS when required. The total amount of memory used by DB2 can grow over time and is limited only by the operating system's memory availability.
  • Mode 2: DATABASE_MEMORY= <NUMERIC VALUE> AUTOMATIC
    In this case, memory tuning still occurs, but the total memory used by the database would start from the NUMERIC value and can grow over time. It is only limited by the operating system's available memory.

  • Mode 3: DATABASE_MEMORY=<NUMERIC VALUE>
    DB2 will allocate this amount of memory during startup and this memory setting is static. It can not take or give memory to the operating system on demand.

  • Mode 4: DATABASE_MEMORY=COMPUTED
    The database memory is computed based on the sum of initial values of the database memory heaps during the database startup. In this case DATABASE_MEMORY is not enabled for self tuning.

Activating self-tuning memory

      UPDATE DATABASE CONFIGURATION USING DATABASE_MEMORY AUTOMATIC
      UPDATE DATABASE CONFIGURATION USING SELF_TUNING_MEM ON IMMEDIATE
      UPDATE DATABASE CONFIGURATION USING SORTHEAP AUTOMATIC
      UPDATE DATABASE CONFIGURATION USING SHEAPTHRES_SHR AUTOMATIC
      UPDATE DATABASE CONFIGURATION USING LOCKLIST AUTOMATIC
      UPDATE DATABASE CONFIGURATION USING MAXLOCKS AUTOMATIC
      
      UPDATE DATABASE CONFIGURATION USING PCKCACHESZ 8196 AUTOMATIC IMMEDIATE
      ALTER BUFFERPOOL BPname1 IMMEDIATE SIZE 1000 AUTOMATIC
      ALTER BUFFERPOOL BPname2 IMMEDIATE SIZE 1000 AUTOMATIC

Determining which memory consumers are enabled for self tuning

 GET DB CFG SHOW DETAIL

 Description                                    Parameter    Current Value      Delayed Value
 -----------------------------------------------------------------------------------------------
 Self tuning memory                      (SELF_TUNING_MEM) = ON (Active)        ON   
 Size of database shared memory (4KB)    (DATABASE_MEMORY) = AUTOMATIC(37200)   AUTOMATIC(37200)
 Max storage for lock  list (4KB)               (LOCKLIST) = AUTOMATIC(7456)    AUTOMATIC(7456)
 Percent. of lock lists  per application        (MAXLOCKS) = AUTOMATIC(98)      AUTOMATIC(98)
 Package cache size (4KB)                     (PCKCACHESZ) = AUTOMATIC(8196)    AUTOMATIC(5600)
 Sort heap thres for shared  sorts (4KB)  (SHEAPTHRES_SHR) = AUTOMATIC(5000)    AUTOMATIC(5000)
 Sort list heap (4KB)                           (SORTHEAP) = AUTOMATIC(256)     AUTOMATIC(256)

Verifying which bufferpools are enabled for self tuning

 SELECT BPNAME, NPAGES, PAGESIZE FROM SYSCAT.BUFFERPOOLS

BPNAM                      NPAGES      PAGESIZE   
-----------------------------------------------
IBMDEFAULTBP                   -2        4096 
BPname1                        -2        8192
BPname2                        -2        32768

A bufferpool is enabled for self tuning, when NPAGES has a value of -2

Controlling DB2 Memory consumption for an Instance

The instance_memory configuration parameter specifies the maximum amount of memory that can be allocated for a database partition.

When instance_memory is set to AUTOMATIC, a fixed upper bound on total memory consumption for the instance is set at instance startup (db2start). Actual memory consumption by DB2 varies depending on the workload. When STMM is enabled to perform database_memory tuning (by default for new databases), during run-time, STMM dynamically updates the size of performance-critical heaps within the database shared memory set
according to the free physical memory on the system, while ensuring that there is sufficient free instance_memory available for functional memory requirements.

Depending on workload, DB2's default memory configuration adapts to the memory requirements of the instance without requiring explicit self-tuning of overall instance memory. For instance:
  • For heavily-used instances, STMM increases the size of performance-critical heaps as needed. More functional memory is consumed, as there are more database agents servicing applications and consuming functional memory. If there is enough free instance_memory but very little free physical memory on the system, STMM starts decreasing the size of performance-critical heaps ensure that the system does not start paging. As functional memory requirements drop, free physical memory on the system should increase, and STMM will start increasing the performance-critical heaps again.
  • For less heavily-used instances, there is less functional memory consumed by the instance, and if there is insufficient free physical memory left on the system, STMM shrinks performance-critical heaps.

If instance_memory is set to a specific value, and at least one active database has an AUTOMATIC value for database_memory, and STMM is enabled for that database, then STMM increases the database_memory size such that DB2 uses almost the entire amount of memory specified by instance_memory, ensuring only that enough free instance_memory is available
for functional memory requests. In this scenario, STMM does not monitor free physical memory on the machine, therefore, instance_memory must be configured properly to ensure that paging will not occur.

Disabling self tuning memory

Self tuning can be disabled for the entire database by setting self_tuning_mem to OFF. When self_tuning_mem is set to OFF, the memory configuration parameters and buffer pools that are set to AUTOMATIC remain AUTOMATIC and the memory areas remain at their current size.

      UPDATE DATABASE CONFIGURATION USING SELF_TUNING_MEM OFF IMMEDIATE

Self tuning can also be effectively deactivated for the entire database if only a single memory consumer is enabled for self tuning. This is because memory cannot be redistributed when only one memory area is enabled. For example, to disable self tuning of the sortheap configuration parameter, you could enter the following:

      UPDATE DATABASE CONFIGURATION USING SORTHEAP MANUAL

To disable self tuning of the sortheap configuration parameter and change the current value of sortheap to 2000 at the same time, enter the following:

      UPDATE DATABASE CONFIGURATION USING SORTHEAP 2000

More detailed information

More details can be found on these very helpful article:

"DB2 9 self-tuning memory management" at
http://www.ibm.com/developerworks/edu/dm-dw-dm-0611read2-i.html

"Understanding the advantages of DB2 9 autonomic computing features" at
http://www.ibm.com/developerworks/db2/library/techarticle/dm-0709saraswatipura/

"DB2 self-tuning memory manager log parser" at
http://www.ibm.com/developerworks/db2/library/techarticle/dm-0708naqvi/

DB2 LUW Deep Compression

Introduction

This article discusses DB2 LUW Deep Compression from the perspective of someone who planned, organized, communicated and coordinated the implementation of this in development, test, regression test and production environments for multi-terabyte data warehouse databases.

Pros and cons

DB2 LUW Deep Compression reduces the amount of disk space needed to store information in databases and improves performance through faster I/O and more efficient use of memory. Here is a list of our production databases showing the size before and after compression along with the compression ratios and space savings we accrued.

We measured performance improvements in the 5% to 30% range. We determined that more rows were stored in buffer pools and that application memory high water marks were lower. We reduced our application memory configuration and increased our buffer pool memory configuration to better utilize the memory we had available on servers. Overall performance of online and batch SQL has improved significantly and better memory management by DB2 has helped us avoid having to purchase more memory to handle increased work loads. The combined improvement in I/O and memory utilization of DB2 LUW Deep Compression has made this a good investment for our large databases.

DB2 LUW Deep Compression uses a bit more CPU, requires more of a conversion effort if system managed tablespaces (SMS) are being used and costs more to license than DB2 LUW without compression. We measured CPU utilization increases of 2-5% after implementing compression and suspect this cost is due to the decompression that is done before information is returned to applications. In our environments the improved I/O and memory management performance far outweigh this small CPU cost.

Environment

AIX 5.3.0.0 Technology Level 6

DB2 V9.1 FIX PAK 2
DB2 Database Partitioning: "Licensed"
DB2 Storage Optimization: "Licensed"

Detail Steps

  • Alter database managed tablespace (DMS) from REGULAR to LARGE.
  • Alter DMS tablespace to AUTORESIZE YES.
  • Alter tables in DMS tablespace to COMPRESS YES and VALUE COMPRESSION.
  • Run reorg using the temporary tablespace and RESETDICTIONARY options.
  • Run DB2 list tablespaces show detail command.
  • Recycle the DB2 instance.
  • Run reorg in place to free up space inside DMS tablespaces.
  • Run DB2 list tablespaces show detail command.
  • Recycle the DB2 instance.
  • Alter tablespaces to reduce the size of containers to recover disk space.
  • Use the copy/rename table approach for large tables in DMS tablespaces.
  • Create new DMS LARGE tablespaces to replace SMS tablespaces.
  • Use the copy/rename table approach for tables in SMS tablespaces.

Recommendations and Information

  • Start with the smallest DMS tablespace first and work toward the largest.
  • Start with the smallest table in a tablespace first and work toward the largest table in that tablespace. DB2 will not implement compression for a table if it does not have enough rows to warrant this. We found it faster and easier to alter all tables to be compressed and to simply let DB2 decide whether to build the compression dictionary and compress the data during the reorg.
  • Using the smallest to largest approach helps in several ways. First, experience is gained while working with smaller units of work. Second, if you have multiple tables in a tablespace, DB2 will recover more space for reuse after compression is implemented due to the way DB2 stores data internally in tablespaces. Third, using the smallest table to the largest table will allow you to free up disk space as you work through the project. This space can be reused to support the additional space you may need when using the copy/rename table approach for large tables or for tables you are converting from SMS to DMS tablespaces.
  • Complete the alter tablespace to LARGE, alter table and reorgs with the temporary tablespace and resetdictionary options for all tables in a tablespace before altering the next tablespace to LARGE.
  • Plan tablespace alters, table alters and reorgs during times when tables are not in great demand. We found that we could run alters and reorgs for small to medium size tables (less than a billion rows) during the day when we have a lot of SQL selects running against our data warehouses. We did not run alters or reorgs during our nightly batch and replication cycle windows. We had a few -911 time out roll back errors on alters and reorgs and simply had to rerun these. We had a few times when people using our data warehouses got a -911 time out roll back error. We avoided most of this by using the copy/rename table approach for large tables (more than a billion rows).
  • The alter DMS tablespace AUTORESIZE YES option makes DMS tablespaces behave like SMS tablespaces because they will automatically increase in size when more space is needed.
  • The reorg with the resetdictionary option builds the compression dictionary and compresses the data in the table if DB2 thinks there is enough data to make this worthwhile. Pages of data are converted from regular record identifiers (RIDS) to large RIDS whether compressed or not. Large RIDS result in more data being stored per page and this combined with compression reduces the amount of disk space needed to store the information and it reduces the number of I/O’s required to return the information.
  • The db2 list tablespaces show detail command resets some internal pointers in tablespaces after they have been converted to large RIDS with compressed tables. This is required in order to be able to recover space for reuse.
  • Recycling the DB2 instance flushes the bufferpools and DB2 will use these more efficiently with compressed data when the DB2 instance is started. Tablespace information in memory is also refreshed after the instance is recycled.
  • The second table reorg will lower the high watermark in the table and tablespace and allow you to reduce tablespace containers to recover space for reuse. Be sure to reorg the smallest tables first and work your way toward the largest. This approach will recover the most space due to the way information is stored internally in tablespaces that contain many tables.
  • Alter the DMS tablespace to reduce the size of container files to recover disk space for reuse when you have completed implementing compression for all tables in the tablespace. We found that doing this with a script that loops and attempts to reduce container sizes using smaller values works best. Containers are reduced in size until there is no longer free space to allow this and then the script will get warning messages as it works it’s way down in size. DB2 version 9.5 will automatically reduce the size of DMS tablespaces defined with the AUTORESIZE YES feature. You have to run DB2 alter commands to reduce tablespace container sizes for tablespaces with volatile information in releases before DB2 v9.5.
  • Space recovered after compressing small and medium tables can be used to increase file systems that have temporary tablespace container files. This can ensure enough temporary work space is available to rebuild multi-billion row indexes of the largest tables in a database.
  • Additional space recovered after compressing small and medium tables can be used to support the copy/rename approach on large tables and on tables converted from SMS to DMS tablespaces.
  • The copy/rename approach simply means to create a new DMS LARGE tablespace, a new table and new indexes, to copy the data from the current table to the new table and to implement compression on the new table. Then the current table can be renamed to an old name and the new table renamed to the current name. Since the rename takes seconds, this avoids long outages when you need to implement compression against large tables. The old table and tablespace can be dropped.
  • Reorgs should usually be run serially and not in parallel against multiple tables at the same time if you want to reduce the impact the compression project has on other users of the database.
  • Recycling instances can be coordinated based on production service level agreements.

Summary

It took us about a week to implement compression in each of our development and test environments. It took about two weeks to implement compression in each of our user regression test environments. It took from four to six weeks to implement compression in each of our production environments. We only ran alters and reorgs during the day and not at night during our nightly batch and replication cycles. Coordinating instance outages increased the elapsed time.

The amount of space we recovered for reuse and the performance improvement we have seen through reduced I/O’s and better database memory utilization make DB2 LUW Deep Compression well worth the investment of time and money.

Script Examples

The script examples below can help provide information that can be used to plan and manage projects to implement DB2 LUW Deep Compression. DBA’s can combine and enhance these scripts to generate the commands needed to alter tablespaces, alter tables and reorganize tables in order to convert regular record identifiers to large record identifiers in tablespaces and to build compression dictionaries and compress data in tables. Ordering the generated command output so that tablespaces and tables are converted from smallest to largest will help you recover more space for reuse during a compression project. Small and medium tablepsaces and tables can be converted using the generated commands and large tables can be skipped in this process and converted using the copy/rename table approach mentioned earlier in this article.

(Replace the word Database in the script with your database name.)

db2list_candidate_tablespaces_for_conversion.ksh

#  Description      : This script lists tablespaces that are candidates for
#                   : conversion from SMS to DMS and from DMS regular to
#                   : DMS large in support of a project to implement large
#                   : record identifiers so that DB2 will store more rows
#                   : per page.   This effort could be part of a project
#                   : to implement compression because table compression
#                   : requires large record identifiers. (RIDS)
db2 connect to Database
echo "Listing candidate tablespaces for conversion."
db2 -x "select TBSP_TYPE,sum(TBSP_USED_SIZE_KB) as TBSP_USED_SIZE_KB,
char(TBSP_NAME,20) as TBSP_NAME,TBSP_CONTENT_TYPE,TBSP_PAGE_SIZE,TBSP_AUTO_RESIZE_ENABLED 
from sysibmadm.tbsp_utilization 
where TBSP_CONTENT_TYPE not like '%TEMP%' 
group by TBSP_TYPE,TBSP_NAME,TBSP_CONTENT_TYPE,
TBSP_PAGE_SIZE,TBSP_AUTO_RESIZE_ENABLED" > FileName.dat
sort FileName.dat > FileName.txt
cat FileName.txt
rm FileName.dat
echo "Candidate tablespaces for conversion located in FileName.txt"

db2list_candidate_tables_for_compression.ksh

#  Description      : This script lists candidate tables for compression.
#                   : A text file is generated with information about the
#                   : server, instance, database, # tablespaces, tablespace
#                   : types (D=DMS, S=SMS), schema, table name and row
#                   : count (cardinality) from DB2 SYSCAT catalog views.
#                   : Output can be imported into a spreadsheet
#                   : and used to help plan a compression project.
#
db2 connect to Database
echo "Identifying tables that are candidates for compression."
db2 -x "select a.tbspace,b.tbspacetype,digits(a.card),a.tabschema,a.tabname 
from syscat.tables a,syscat.tablespaces b 
where a.tabschema not like 'SYS%' 
and  a.tbspace=b.tbspace and a.card > 0 
and a.type in ('T','S') 
order by a.card desc,a.tbspace,a.tabschema,a.tabname" > FileName.dat
cat FileName.dat | while read Tbspace TbspaceType Card TabSchema TabName
do
        echo '"'$(hostname)'","'${DB2INSTANCE}'","'Database'","'${Tbspace}'","
'${TbspaceType}'","'${TabSchema}'","'${TabName}'","'${Card}'"' | tee -a FileName_excel.txt
done
rm FileName.dat
echo "Candidate tables for compression located in FileName_excel.txt"

Space savings


DB2 LUW Terabytes Terabytes Percent Partitions
Database Before After compressed  
prod001 20 9 55% 16
prod002 8 4 50% 11
prod003 12 6 50% 11
prod004 22 10 55% 7
prod005 18 7 61% 16
Total 80 36 55%  
Space savings 44 Terabytes

How to setup PHP thin client on your application server?

You have installed DB2 on your database server and now you need to setup your IBM PHP client to connect to DB2. Do you require a full ~500 MB client install? Mostly, system administrator or DBAs grind their teeth when they find out that for a simple connect from PHP, they need to install a big fat client. But, things have changed and you can install a thin ODBC-CLI client instead of installing a fat client.

Pre-requisites

Steps to get IBM thin ODBC-CLI Driver

  1. Go to IBM DB2 Application Web Site at http://ibm.com/software/data/db2/ad
  2. Click on download now link
  3. Click on IBM Data Server Driver for ODBC and CLI link

    ODBC CLI Driver link for PHP
  4. After you click above link, you will be asked to login. If you do not have an IBM User ID, create one and save that for your future use as you will require that user ID for any download etc. Choose the right driver type and download it. You will see a download page similar to shown below:
    Different ODBC CLI Driver types link for PHP
  5. After accepting license agreement, download the driver (approx 14MB) and you will see a screen something similar to one shown below:
    ODBC CLI Driver link for PHP on Linux
  6. After you download the ODBC-CLI driver, you need to install it manually since there is no GUI install program. Installation is nothing but uncompressing the file to a directory somewhere on your application server.
    [vikram@gopal clidriver]$ pwd
    /home/vikram/clidriver
    [vikram@gopal clidriver]$ ls -l
    total 56
    drwxr-xr-x 2 vikram vikram 4096 Oct  4 00:12 adm
    drwxr-xr-x 2 vikram vikram 4096 Oct  4 00:12 bin
    drwxr-xr-x 2 vikram vikram 4096 Oct  4 00:12 bnd
    drwxr-xr-x 2 vikram vikram 4096 Oct  4 00:12 cfg
    drwxr-xr-x 4 vikram vikram 4096 Oct  4 00:12 conv
    drwxr-xr-x 2 vikram vikram 4096 Oct  4 00:12 db2dump
    drwxr-xr-x 3 vikram vikram 4096 Mar 28 22:31 lib
    drwxr-xr-x 3 vikram vikram 4096 Oct  4 00:12 license
    drwxr-xr-x 3 vikram vikram 4096 Oct  4 00:12 msg
    drwxr-xr-x 2 vikram vikram 4096 Oct  2 11:51 php32
    drwxr-xr-x 2 vikram vikram 4096 Oct  4 00:12 php64
    drwxr-xr-x 2 vikram vikram 4096 Oct  2 11:51 ruby32
    drwxr-xr-x 2 vikram vikram 4096 Oct  4 00:12 ruby64
    drwxr-xr-x 3 vikram vikram 4096 Oct  4 00:12 security64
    
  7. After you have extracted files, configure it as per this link. You might not need to do any thing but this link is here for reference purpose.
  8. After you have copied or installed your PHP driver, make sure that you made correct entries in your php.ini file. You can check modules directory of php to make sure that you have ibm_db2.so shared library. The modules or ext or extension directory location will be different based upon your distribution.
    /usr/lib64/php/modules
    [vikram@gopal modules]$ ls -l
    total 2896
    -rwxr-xr-x 1 root root  337448 May  9  2007 gd.so
    -rwxr-xr-x 1 root root  167620 Mar 28 23:17 ibm_db2.so
    -rwxr-xr-x 1 root root   46152 May  9  2007 ldap.so
    -rwxr-xr-x 1 root root 1863856 May  9  2007 mbstring.so
    -rwxr-xr-x 1 root root  118952 May  9  2007 mysqli.so
    -rwxr-xr-x 1 root root   54088 May  9  2007 mysql.so
    -rwxr-xr-x 1 root root   28296 May  9  2007 pdo_mysql.so
    -rwxr-xr-x 1 root root   92616 May  9  2007 pdo.so
    -rwxr-xr-x 1 root root   24520 May  9  2007 pdo_sqlite.so
    -rwxr-xr-x 1 root root   15416 Nov  6  2006 phpcups.so
    

    Now, you should see the dependencies involved for successful execution of your ibm_db2.so shared library.

    [vikram@gopal modules]$ ldd ibm_db2.so
            libdb2.so.1 => /home/db2/sqllib/lib64/libdb2.so.1 (0x00002aaaaabbe000)
            libc.so.6 => /lib64/libc.so.6 (0x00002aaaacbda000)
            libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00002aaaace23000)
            libdl.so.2 => /lib64/libdl.so.2 (0x00002aaaacf57000)
            libpthread.so.0 => /lib64/libpthread.so.0 (0x00002aaaad05c000)
            libm.so.6 => /lib64/libm.so.6 (0x00002aaaad174000)
            libdb2dascmn.so.1 => /opt/ibm/db2/V9.5/lib64/libdb2dascmn.so.1 (0x00002aaaad2f5000)
            libdb2g11n.so.1 => /opt/ibm/db2/V9.5/lib64/libdb2g11n.so.1 (0x00002aaaad522000)
            libdb2genreg.so.1 => /opt/ibm/db2/V9.5/lib64/libdb2genreg.so.1 (0x00002aaaadda6000)
            libdb2install.so.1 => /opt/ibm/db2/V9.5/lib64/libdb2install.so.1 (0x00002aaaadfeb000)
            libdb2locale.so.1 => /opt/ibm/db2/V9.5/lib64/libdb2locale.so.1 (0x00002aaaae1f6000)
            libdb2osse.so.1 => /opt/ibm/db2/V9.5/lib64/libdb2osse.so.1 (0x00002aaaae419000)
            libdb2osse_db2.so.1 => /opt/ibm/db2/V9.5/lib64/libdb2osse_db2.so.1 (0x00002aaaaea2d000)
            libdb2trcapi.so.1 => /opt/ibm/db2/V9.5/lib64/libdb2trcapi.so.1 (0x00002aaaaec73000)
            libstdc++.so.5 => /usr/lib64/libstdc++.so.5 (0x00002aaaaee87000)
            libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00002aaaaf062000)
            /lib64/ld-linux-x86-64.so.2 (0x0000555555554000)
            librt.so.1 => /lib64/librt.so.1 (0x00002aaaaf170000)
    

    In above listing, you see the location of all libdb* shared libraries pointing to the location of db2 installed on my system. On a machine, where there is no DB2 client, the location of libdb2* points to unknown. After you uncompressed ODBC-CLI driver, you will need to update LD_LIBRARY_PATH in your profile to the location of lib directory of clidriver.

    export LD_LIBRARY_PATH=/home/vikram/clidriver/lib
    

    Make changes to your php.ini file for entires as shown below:

    ibm_db2.instance_name=db2inst1 or your DB2 instance name
    [PHP_IBM_DB2]
    extension=php_ibm_db2.dll or ibm_db2.so or any other name that you chose to compile the driver.
    

    Make sure that you have made above changes in correct php.ini file. For example, the following image shows the location of my php.ini file and I should be making changes to this php.ini file. I write this because my system had many php.ini and I was making changes to the wrong php.ini and struggling to find why phpinfo() is not showing those changes.

    Location of php.ini file

  9. If PHP is able to recognize ibm_db2 driver, you should be able to verify it by running phpinfo().
    Make a simple PHP page with phpinfo() and assuming that you have already set apache web server and run it.

    phpinfp.php file
    ----------------
    <?
    phpinfo();
    ?>
    
  10. Copy following PHP script in a file and test connection to DB2 by running it either through command line or through your web page.
    db2conn.php
    -----------
    <?
      $database = 'SAMPLE';
      $user = 'db2admin';
      $password = 'password';
      $hostname = 'localhost';
      $port = 50000;
      $conn_string = "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=$database;" .
      "HOSTNAME=$hostname;PORT=$port;PROTOCOL=TCPIP;UID=$user;PWD=$password;";
      $conn_resource = db2_connect($conn_string, '', '');
      if ($conn_resource) {
        echo 'Connection to database succeeded.';
        db2_close($conn_resource);
      } else {
        echo 'Connection to database failed.';
        echo 'SQLSTATE value: ' . db2_conn_error();
        echo 'with Message: ' . db2_conn_errormsg();
      }
    ?>
    
    $ php db2conn.php
    Connection to database succeeded.
    

    If you notice above, we are making a direct connection to the DB2 database using server name, port number and by using the name of the database at the server. If we had a full DB2 client, we would have cataloged DB2 databases locally but we could not do that here since thin client does not come with DB2 CLP.

    If you in fact had DB2 CLP (or fat client), you could have used following script that uses the database directory to resolve the local alias name of DB2. Please note that the following DB2 connection script will not work with thin ODBC-CLI driver as we are only specifying name of the database and no host name or port number. This is due to the fact that we catalog a remote DB2 database locally through a regular DB2 client and PHP driver will resolve host name or port number through DB2 database directory but that is not the case with the thin ODBC-CLI client. However, it is a best practice to use DB2 connection as shown in the above script since it will work for both thin and thick db2 clients. This may sound simple for the people who know about DB2 but I find people struggling on this topic very much.

    <?php
       $db_name = 'SAMPLE';
       $usr_name = 'db2inst1';
       $password = 'password';
       // For persistent connection change db2_connect to db2_pconnect
       $conn_resource = db2_connect($db_name, $usr_name, $password);
       if ($conn_resource) {
         echo 'Connection to database succeeded.';
         db2_close($conn_resource);
       } else {
         echo 'Connection to database failed.';
         echo 'SQLSTATE value: ' . db2_conn_error();
         echo 'with Message: ' . db2_conn_errormsg();
       }
    ?>
    
  11. If you do not succeed in connecting to DB2, make sure that you have set following on your DB2 server.

    $ db2 get dbm cfg | grep -i svce
     TCP/IP Service name                          (SVCENAME) = 
    

    If SVCENAME is not set, update this parameter to a TCPIP port number

    $ db2 update dbm cfg using svcename 50000
    $ db2set -all
    

    If db2set -all does not show any variable as DB2COMM, set this to TCPIP

    $ db2set DB2COMM=TCPIP
    $ db2stop force
    $ db2start
    

    After making above changes, try again.

Case insensitive string comparison in DB2

You are trying to figure out the best approach to do the case in-sensitive search in DB2. You must first read these 3 excellent articles written on this topic by 3 IBMers.

  1. Blair Adamache
  2. Doug Doole
  3. Knut Stolze

In a nutshell, you can take one of the approach as outlined below:

  • Create a generated column in DB2 using LOWER or UPPER function
    CREATE TABLE testtable (name VARCHAR(60), name_lower GENERATED ALWAYS AS (LOWER(name)))
    CREATE INDEX ix1_name ON testtable (name_lower)
    SELECT name FROM testtable WHERE LOWER(name) = 'babbu';
    

    DB2 optimizer will actually use index ix1_name when you use LOWER function on name in your query. Please do not consider this to be an all out alternative of Oracle functional index in DB2. DB2 is smart enough for most of the functions where a functional index is not required and I will explain that in detail in some other article with actual examples. The Oracle DBAs get blind sided on this to create generated columns for an alternative of Oracle's functional indexes.
    Explain plan for query using generated column.

  • Use index extensions
    Please read Knut's article on how to use index extension capability. This approach requires using Kunt's user defined functions to use index extension capability with the use of DISTINCT TYPE data type. The use of GENERATED COLUMN requires additional storage in table but index extension approach takes that storage in index rather than in the actual table. This will be very useful when adding a GENERATED COLUMN is not a possibility due to page size.
  • Use COLLATION_KEY_BIT function
    Please refer to Doug's article on a user defined function and what he described was implemented in DB2 and the DB2 function name is COLLATION_KEY_BIT.
    SELECT name FROM testtable
    WHERE collation_key_bit(name,'UCA400R1_S1') = COLLATION_KEY_BIT('babbu', 'UCA400R1_S1');
    

    Explain plan for query using generated column.
    If there is an index on a generated column collation_key_bit(name,'UCA400R1_S1'), DB2 will use the index as shown above otherwise it will use full table scan as shown below. You also get same behavior with the use of LOWER or UPPER function but using COLLATION_KEY_BIT function gives you much greater flexibility in comparing the strings particularly if you want to eliminate accent characters in comparison or to do culturally correct comparisons. For example, Nuernberg should match with Nürnberg. You will not get that with lower or upper function.
    Explain plan for query using collation_key_bit.

  • Create a case insensitive database
    You can create a case insensitive database similar to the lines of collation_key_bit function to compare strings properly but let database do that comparison instead of using COLLATION_KEY_BIT function.
    $ db2 CREATE DB SAMPLE COLLATE USING UCA500R1_S2
    
    $ db2 create database mydb2 collate using UCA500R1_E0_S1
    This is case sensitive but accent insensitive and will collate "role" = "rôle" < "Role" 
    
    $ db2 create database mydb2 collate using UCA500R1_S1
    
    This is both case and accent insensitive and will collate "role" = "Role" = "rôle"
    

    If you are on DB2 9.5 or later, you can use this collation for case in-sensitive search.

    create database mydb2C automatic storage yes on /db2fs USING CODESET UTF-8 TERRITORY US 
    COLLATE USING UCA500R1_LEN_S1_NX pagesize 16384 autoconfigure apply none ; 
    

    You will notice some performance impact due to above since SYSTEM or IDENTITY collation gives the best performance.

  • Which approach you should take - It really depends upon pros and cons of different approaches as outlined above. If you can define all of your STRING data in all tables with a common DISTINCT TYPE and performance is the criteria and you can not use generated columns, go with the index extension approach as per Knut's UDFs. For simplicity, go with case in-sensitive database but be prepared to sacrifice some performance due to complex UCA algorithm of string compare.

    The choice is yours.

    How to compile DB2 PHP driver

  • You are new to ibm_db2 or db2 and PHP driver available with thin ODBC-CLI client do not match with your version of PHP. The following steps take drudgery out of complex compilation process for those DBAs or system administrator who are averse to compilation process.
  • Check if your have PHP > 4.3 and php-devl installed on your system.
    # rpm -qa | grep php
    php-mbstring-5.1.6-1.2
    php-5.1.6-1.2
    php-mysql-5.1.6-1.6
    php-pdo-5.1.6-1.2
    php-pdo-5.1.6-1.6
    php-devel-5.1.6-1.6
    php-pear-1.4.9-1.2
    php-5.1.6-1.6
    php-mbstring-5.1.6-1.6
    php-gd-5.1.6-1.6
    php-mysql-5.1.6-1.2
    php-devel-5.1.6-1.2
    php-ldap-5.1.6-1.6
    
  • Download latest PHP driver source from http://pecl.php.net/get/ibm_db2
  • If you want previous versions of driver, go to http://pecl.php.net/package/ibm_db2 and select driver of your choice
  • After you unzip files, you will see something similar to
    -rw-r--r-- 1 vikram vikram   4687 Aug 31  2005 ibm_db2.dsp
    -rwxr-xr-x 1 vikram vikram    508 Jul 11  2006 config.w32
    -rw-r--r-- 1 vikram vikram   7993 Nov 20 19:06 php_ibm_db2.h
    -rw-r--r-- 1 vikram vikram    256 Nov 20 19:06 TODO
    -rw-r--r-- 1 vikram vikram   3297 Nov 20 19:14 config.m4
    -rw-r--r-- 1 vikram vikram 182086 Nov 21 11:56 ibm_db2.c
    drwxrwxr-x 2 vikram vikram   4096 Mar 29 12:47 tests
    
  • You do not see ./configure command to build the driver. To get configure command, you need to run phpize command to build configure command from config.m4

    [vikram@gopal ibm_db2-1.6.5]$ phpize
    Configuring for:
    PHP Api Version:         20041225
    Zend Module Api No:      20050922
    Zend Extension Api No:   220051025
    [vikram@gopal ibm_db2-1.6.5]$ ls -l
    total 1596
    -rw-r--r-- 1 vikram vikram  66540 Mar 29 12:49 acinclude.m4
    -rw-rw-r-- 1 vikram vikram 297593 Mar 29 12:49 aclocal.m4
    drwxr-xr-x 2 vikram vikram   4096 Mar 29 12:49 autom4te.cache
    drwxrwxr-x 2 vikram vikram   4096 Mar 29 12:49 build
    -rwxr-xr-x 1 vikram vikram  42037 Mar 29 12:49 config.guess
    -rw-rw-r-- 1 vikram vikram   1565 Mar 29 12:49 config.h.in
    -rw-r--r-- 1 vikram vikram   3297 Nov 20 19:14 config.m4
    -rwxr-xr-x 1 vikram vikram  30253 Mar 29 12:49 config.sub
    -rwxrwxr-x 1 vikram vikram 666346 Mar 29 12:49 configure
    -rw-rw-r-- 1 vikram vikram   2691 Mar 29 12:49 configure.in
    -rwxr-xr-x 1 vikram vikram    508 Jul 11  2006 config.w32
    -rw-r--r-- 1 vikram vikram 182086 Nov 21 11:56 ibm_db2.c
    -rw-r--r-- 1 vikram vikram   4687 Aug 31  2005 ibm_db2.dsp
    -rw-rw-r-- 1 vikram vikram      0 Mar 29 12:49 install-sh
    -rw-r--r-- 1 vikram vikram 196440 Mar 29 12:49 ltmain.sh
    -rw-r--r-- 1 vikram vikram   4308 Mar 29 12:49 Makefile.global
    -rw-rw-r-- 1 vikram vikram      0 Mar 29 12:49 missing
    -rw-rw-r-- 1 vikram vikram      0 Mar 29 12:49 mkinstalldirs
    -rw-r--r-- 1 vikram vikram   7993 Nov 20 19:06 php_ibm_db2.h
    -rw-r--r-- 1 vikram vikram  54642 Mar 29 12:49 run-tests.php
    drwxrwxr-x 2 vikram vikram   4096 Mar 29 12:47 tests
    -rw-r--r-- 1 vikram vikram    256 Nov 20 19:06 TODO
    
  • Run configure command to build the driver

    $ ./configure --with-IBM_DB2=<path to DB2 install dir>
    $ make
    # make install
    

    Specify path to DB2 e.g. /home/db2inst1/sqllib or /opt/IBM/db2/V9.5 or your version of DB2

  • Top like utility for DB2

    Yes, db2 has a top like utility called db2top written by 2 IBMers Subho Chatterjee and Jacques Milman. It uses DB2 snapshot APIs to extract information from DB2 engine and shows in a format that you are familiar with top utility on Unix systems.

    Click at this link at get more information on this utility. This utility has been included in regular DB2 product since DB2 8.1 FixPak 16.

    You can look at this IBM Alphawork (for emerging technologies) site for details on this tool.

    Generally people ask question as how can they run db2top on windows and answer is No. But you can catalog your Windows DB2 database in your Unix system and then run db2top against your windows database.

    Show Package ISOLATION Level

    Use following SQL to find out the ISOLATION LEVEL used by DB2 packages.

    SELECT 'SHOW_RSETS' "STORED PROCEDURE",
            SUBSTR(PKGNAME,1,18) PACKAGE, 
           "ISOLATION" 
    FROM   syscat.packages 
    WHERE  pkgname = (SELECT deps.bname PACKAGE
                      FROM   sysibm.sysdependencies deps,
           	                 sysibm.sysroutines procs
                      WHERE  deps.dtype = 'F' 
                      AND 	 deps.btype = 'K'
                      AND 	 procs.specificname  = deps.dname
                      AND 	 procs.routineschema = deps.dschema
                      AND    procs.routinename   = 'Stored_Procedure_name')
    @
    

    Equivalent of NEWGUID in DB2

    If you have been using UNIQUE_IDENTIFIER as a data type and use newguid as a default in your SQL Server table definition, you can use a UDF called from a trigger to simulate same behavior in DB2. This work around is due to the fact that DB2 does not yet support EXPRESSIONS in default clause in table definition.

    In Microsoft world, a GUID is nothing but a unique identifier which looks like if presented in hex form e.g.
    3F2504E0-4F89-11D3-9A0C-0305E82C3301. What we need is a unique identifier that is guaranteed for each row and the UDF given below will generate a unique data for each row.

    CREATE FUNCTION newguid()
    RETURNS CHAR(32)
    NOT DETERMINSTIC
    RETURN hex(generate_unique()) ||
     hex(CHR(CAST(RAND()*255 AS SMALLINT))) ||
     hex(CHR(CAST(RAND()*255 AS SMALLINT))) ||
     hex(CHR(CAST(RAND()*255 AS SMALLINT)))
    ;
    

    After above UDF is created, you can create a trigger in DB2 to simulate NEWGUID.

    For DB2 on LUW (Linux, Unix and Windows)

    CREATE TRIGGER TRIG_TAB1 NO CASCADE BEFORE INSERT
    ON SCH1.TAB1 REFERENCING NEW AS NEW
    FOR EACH ROW MODE DB2SQL
      SET col1 = DB2.NEWGUID();
    

    For DB2 on Z/OS

    CREATE TRIGGER TRIG_TAB1 NO CASCADE BEFORE INSERT
    ON SCH1.TAB1 REFERENCING NEW AS NEW
    FOR EACH ROW MODE DB2SQL
      VALUES DB2.NEWGUID() INTO col1
    ;