Category Archives: DB2

DB2 Ownership Transfer

If you ever get an error in DB2 along the lines of…

SQLCODE=-727, SQLSTATE=56098, SQLERRMC=1;-551;42501;MARKJ|EXECUTE|XX.PROC_P

…but in your code are executing the proc as another user (say “USR1”) instead of MARKJ, it may be that MARKJ created the proc and until recently still had the necessary access for executing everything in XX.PROC_P. Subsequently permissions were “fixed” and now stuff starts blowing up all over because MARKJ no longer has access. How to fix? Well, like all things I’m sure there are a million ways to do it, but the easiest in this situation was to have MARKJ transfer the ownership on everything he owned. To determine that, the following SQL was run to generate the commands, then the commands were run…


SELECT 
CASE ROUTINETYPE WHEN 'F' THEN
    'TRANSFER OWNERSHIP OF FUNCTION  '||
    RTRIM(ROUTINESCHEMA)||'.'|| ROUTINENAME ||
    ' TO USER DB2DBA PRESERVE PRIVILEGES; '
ELSE
    'TRANSFER OWNERSHIP OF PROCEDURE  '||
    RTRIM(ROUTINESCHEMA)||'.'|| ROUTINENAME ||
    ' TO USER DB2DBA PRESERVE PRIVILEGES; '
END
FROM SYSCAT.ROUTINES 
WHERE OWNER = 'MARKJ' 
WITH UR;

DB2 Stats vs Actual

Ever have a DB2 query that all of a sudden went from performing just fine to being dog slow? If so, there may be an issue with the stats on one or more of the tables being used in the query. To check, run the SQL below replacing XX and TABLE_NM with your appropriate schema and table in 2 places…

-- What DB2 thinks the table count is
SELECT  'Stats' AS TYPE_X,
        CARD AS COUNT_NB,
        STATS_TIME AS UPDATED_TS
FROM    SYSCAT.TABLES 
WHERE   TABSCHEMA = 'XX' 
        AND TABNAME ='TABLE_NM'
    UNION
-- Actual table count
SELECT  'Actual' AS TYPE_X,
        COUNT(*) AS COUNT_NB,
        CURRENT_TIMESTAMP AS UPDATED_TS
FROM    XX.TABLE_NM
FOR READ ONLY WITH UR

… if the record counts are way off, have your DBA do a runstats on the table in question followed by a rebind of any stored procs that use the table.

DB2 Recursive Query

If you’ve ever had to do a recursive query in DB2 to get a hierarchy of records, feel free to use this as a starting point…

WITH LINKS (PARENT_ID, CHILD_ID, UPDATED_TS) AS 
    ( 
    SELECT  ROOT.PARENT_ID, ROOT.CHILD_ID, ROOT.UPDATED_TS 
    FROM    XX.EVENTS ROOT 
    WHERE   ROOT.CHILD_ID = '234ASDFASDF' 
        UNION ALL 
    SELECT  CHILD.PARENT_ID, CHILD.CHILD_ID, CHILD.UPDATED_TS 
    FROM    LINKS PARENT, XX.EVENTS CHILD 
    WHERE   PARENT.PARENT_ID = CHILD.CHILD_ID 
    ) 
SELECT  PARENT_ID, CHILD_ID, UPDATED_TS 
FROM    LINKS 
ORDER BY UPDATED_TS 
FOR READ ONLY WITH UR

… yes, newer versions of DB2 have this ability built in but you’re not always using the most recent version are you??

Thanks to this page for pointing me in the right direction.

How Much Space Is Being Used by 1 LOB / CLOB / BLOB in DB2

Building off of yesterdays lesson on how to determine how much space a table is using (including LOBs and Indexes), there may come a time when you need to determine how much space 1 or more specific records are taking up. To do that, use the LENGTH() function which will return you how many bytes the field is using. Simply do some math to get a more useful value (like KB).

SELECT length(LOB_DATA_X) BYTE_A, length(LOB_DATA_X) / 1024 KB_A
FROM XX.MY_TABLE
FETCH FIRST 10 ROWS ONLY WITH UR FOR READ ONLY;

Get Space Used by LOB / CLOB / BLOB in DB2

If you have a DB2 table and want to get information as to how much space a particular table is using, you could try and deduce it from things like the row length, or you could just query the view via a table function and make your life a heck of a lot easier.

For instance, start with something like this…

SELECT * FROM TABLE(SYSPROC.ADMIN_GET_TAB_INFO('XX', 'MY_TABLE'));

Previous versions of this function include ADMIN_GET_TAB_INFO_V97 and ADMIN_GET_TAB_INFO_V95. The nice thing is that it appears you don’t have to be a DBA to query the info which is nice for us app developers out there!

Keep in mind that sizes returned are in KB, so you may want to convert the data to something more useful like so…

SELECT  TRIM(TABSCHEMA) || '.' || TRIM(TABNAME) AS TABLE_NM,
        DATA_OBJECT_P_SIZE / 1024 AS DATA_P_SIZE_NB, 
        LOB_OBJECT_P_SIZE / 1024 AS LOB_P_SIZE_MB, 
        XML_OBJECT_P_SIZE / 1024 AS XML_P_SIZE_MB, 
        LONG_OBJECT_P_SIZE / 1024 AS LONG_P_SIZE_MB, 
        (DATA_OBJECT_P_SIZE + LOB_OBJECT_P_SIZE + XML_OBJECT_P_SIZE + LONG_OBJECT_P_SIZE) / 1024 AS TOTAL_P_SIZE_MB,
        INDEX_OBJECT_P_SIZE / 1024 AS INDEX_OBJECT_P_SIZE_MB
FROM TABLE(SYSPROC.ADMIN_GET_TAB_INFO('XX', 'MY_TABLE'));

See also: this link

Connect to DB2 via UNIX Command Line

To access DB2 via the command line on a UNIX/Linux box you need…

  • The DB2 client installed on the box
  • The DB you’re trying to connect to cataloged in the client

Once you know you have that, simply source your db2profile and connect…

. /path/to/db2profile
db2 connect to DBCATNAME user yourid using yourpw

… where DBCATNAME is the name the DB is cataloged as on that box, yourid is your user ID for connecting to the DB, and yourpw is your password for connecting to the DB. Note, that if the actual database resides on the same box, and you want to login as the user your logged into the UNIX box as, you can simplify the connect string and not have to include your user ID or password…

. /path/to/db2profile
db2 connect to DBNAME

Determine DB2 Client Version (aka: level) installed on UNIX box

Sometimes you need to know what version of the DB2 client is installed on a particular unix/linux box. To do so, simple source the client profile and run db2level like so…

. /path/to/db2profile
db2level

Generate test data to help debug DB2 SQL

You can easily create test data to help debug SQL statements:

with data (key, value) as
(   values ('key1', 'a')
         , ('key2', null)
         , ('key3', 'z')
)
select 'min', min(coalesce(value,'')) from data
union
select 'max', max(coalesce(value,'')) from data

How to Tell What Objects Reference a DB2 Table

This “little” 🙂 query will do it for you. Just change the where clause at the bottom…

SELECT Type_X AS "Type" 
, RefObj_X AS "Referencing Object" 
FROM ( 
SELECT RTRIM(SYSPROC.PROCSCHEMA)||'.'||RTRIM(SYSPROC.PROCNAME) AS RefObj_X 
, RTRIM(SYSPROC.PROCSCHEMA) AS Schema_X 
, RTRIM(SYSPROC.PROCNAME) AS Obj_X 
, RTRIM(PDEP.BSCHEMA)||'.'||RTRIM(PDEP.BNAME) AS BaseObj_X 
, 'Proc' AS Type_X 
FROM SYSCAT.PACKAGEDEP PDEP 
INNER JOIN SYSIBM.sysdependencies SYSDEP 
ON SYSDEP.BSCHEMA = PDEP.BSCHEMA 
AND SYSDEP.BNAME = PDEP.PKGNAME 
INNER JOIN SYSIBM.sysprocedures SYSPROC 
ON SYSDEP.DNAME = SYSPROC.SPECIFICNAME 

UNION ALL 

SELECT RTRIM(TRIGSCHEMA)||'.'||RTRIM(TRIGNAME) AS RefObj_X 
, RTRIM(TRIGSCHEMA) AS Schema_X 
, RTRIM(TRIGNAME) AS Obj_X 
, RTRIM(BSCHEMA)||'.'||RTRIM(BNAME) AS BaseObj_X 
, 'Trigger' AS Type_X 
FROM SYSCAT.TRIGDEP 

UNION ALL 

SELECT RTRIM(VIEWSCHEMA)||'.'||RTRIM(CAST((VIEWNAME) AS CHAR(126))) AS RefObj_X 
, RTRIM(VIEWSCHEMA) AS Schema_X 
, RTRIM(CAST((VIEWNAME) AS CHAR(126))) AS Obj_X 
, RTRIM(BSCHEMA)||'.'||RTRIM(BNAME) AS BaseObj_X 
, CASE DTYPE WHEN 'S' 
THEN 'SUMMARY TABLE' 
ELSE 'View' 
END AS Type_X 
FROM SYSCAT.VIEWDEP 

UNION ALL 

SELECT RTRIM(INDSCHEMA)||'.'||RTRIM(INDNAME) AS RefObj_X 
, RTRIM(INDSCHEMA) AS Schema_X 
, RTRIM(INDNAME) AS Obj_X 
, RTRIM(TABSCHEMA)||'.'||RTRIM(TABNAME) AS BaseObj_X 
, 'Index' AS Type_X 
FROM SYSCAT.INDEXES 

UNION ALL 

SELECT RTRIM(TABSCHEMA)||'.'||RTRIM(TABNAME) AS RefObj_X 
, RTRIM(TABSCHEMA) AS Schema_X 
, RTRIM(TABNAME) AS Obj_X 
, RTRIM(BASE_TABSCHEMA)||'.'||RTRIM(BASE_TABNAME) AS BaseObj_X 
, 'Alias' AS Type_X 
FROM SYSCAT.TABLES 
WHERE TYPE = 'A' 

UNION ALL 

SELECT DISTINCT RTRIM(CAST(RTRIM(TABSCHEMA) AS VARCHAR(126))) || '.' || RTRIM(TABNAME) AS RefObj_X 
, RTRIM(CAST(RTRIM(TABSCHEMA) AS VARCHAR(126))) AS Schema_X 
, RTRIM(TABNAME) AS Obj_X 
, RTRIM(REFTABSCHEMA)||'.'||RTRIM(REFTABNAME) AS BaseObj_X 
, 'Table' AS Type_X 
FROM SYSCAT.REFERENCES 
) AS RefTbl 
WHERE BaseObj_X = 'XX.MY_TABLE' 
ORDER BY 
Type_X 
, RefObj_X;

Force values into DB2 GENERATED ALWAYS fields

If doing a conversion of data where you need to force a value into a field that is GENERATED ALWAYS after the conversion, you can still load values into the generating and generated fields.

In your conversion load process, use the “generatedoverride” modifier (ex: LOAD FROM C012 OF CURSOR MODIFIED BY generatedoverride MESSAGES $l_sLog). This will allow you to force values into GENERATED ALWAYS fields.

Once your load completes, issue one of the following statements to take your table out of the “set integrity pending” state:

SET INTEGRITY FOR table-name GENERATED COLUMN IMMEDIATE UNCHECKED; 
-- will not verify the values you supplied in the load

SET INTEGRITY FOR table-name IMMEDIATE CHECKED; 
-- will verify the values you supplied in the load

Ref: http://publib.boulder.ibm.com/infocenter/db2luw/v9/index.jsp?topic=/com.ibm.db2.udb.admin.doc/doc/c0004592.htm