Someone brought to my attention a Db2 for i table function that parses the names of the tables and columns used in string passed to it. I have to admit I had not heard of this, therefore, I decided to test out what kinds of information it could return.
The PARSE_STATEMENT appears to have been introduced as part of the IBM i release 7.2 . It takes a string that contains a SQL statement and returns the columns, objects, etc. contained within. The basic syntax is:
SELECT * FROM TABLE(<sql string>)) AS A |
For example:
SELECT * FROM
TABLE(QSYS2.PARSE_STATEMENT('SELECT ORDNO,ORDSTS FROM PRODLIB.ORDHDR'))
AS A
|
This returns:
| NAME_ TYPE |
NAME | SCHEMA | RDB | COLUMN _NAME |
USAGE _TYPE |
NAME_ START_ POSITION |
SQL_ STATEMENT _TYPE |
| COLUMN | - | - | - | ORDNO | QUERY | 8 | QUERY |
| COLUMN | - | - | - | ORDSTS | QUERY | 14 | QUERY |
| TABLE | ORDHDR | PRODLIB | - | - | QUERY | 26 | QUERY |
Note: The dash ( - ) indicates that the value in the column is null.
Passing string like this to the table function is not exactly "real world". What I am more likely to do is take SQL statements from another view and use them with the PARSE_STATEMENT table function. Fortunately there is the view, SYSPROGRAMSTMTSTAT, that contains many of the SQL statements in the programs in this IBM i partition. There are just two columns I need from this view I would want to use with the PARSE_STATEMENT:
- PROGRAM_NAME Name of the program containing the SQL statement(s)
- STATEMENT_TEST The SQL statement
If there is more than one SQL statement in the program then there will be more than one column.
I will be using the same SQL statement for all of the example programs I have created which is:
01 SELECT B.NAME_TYPE,B.NAME,B.SCHEMA,B.COLUMN_NAME,B.USAGE_TYPE, 02 B.SQL_STATEMENT_TYPE,A.STATEMENT_TEXT 03 FROM QSYS2.SYSPROGRAMSTMTSTAT AS A, 04 TABLE(QSYS2.PARSE_STATEMENT(A.STATEMENT_TEXT)) AS B 05 WHERE A.PROGRAM_SCHEMA = 'MYLIB' 06 AND A.PROGRAM_NAME = '<program name>' |
Lines 1 and 2: I am selecting some of the available columns:
| Column name | Description |
| B.NAME_TYPE | Type of object name: COLUMN,FUNCTION,SEQUENCE,TABLE,TYPE |
| B.NAME | Object name |
| B.SCHEMA | Library/schema name |
| B.COLUMN_NAME | Column name. If null then NAME_TYPE is not COLUMN |
| B.USAGE_TYPE | How the name is used in this statement |
| B.SQL_STATEMENT_TYPE | Type of SQL statement |
| A.STATEMENT_TEXT | SQL statement contained in the program (this column is from SYSPROGRAMSTMTSTAT) |
Line 3: Defining the SYSPROGRAMSTMTSTAT view.
Line 4: I am passing the SQL statement from the column in SYSPROGRAMSTMTSTAT to the PARSE_STATEMENT table function.
Lines 5 and 6: I am selecting only those rows from tt>SYSPROGRAMSTMTSTAT view when the schema (library) and program names match.
For the examples I am not going to show complete programs, just the SQL statements they contain. The first example contains a multiple row insert:
exec sql INSERT INTO QTEMP.PERSON VALUES(DEFAULT,'ROBERT','ALLEN'),
(DEFAULT,'John','Austin'),
(DEFAULT,'don','bennett'),
(DEFAULT,'DIETMAR','BRUCK'),
(DEFAULT,'Brian','Caine'),
(DEFAULT,'george','curtis'),
(DEFAULT,'ALAN','DALEY'),
(DEFAULT,'Ron','Framer'),
(DEFAULT,'lol','harvey'),
(DEFAULT,'RON','HEWITT'),
(DEFAULT,'Brian','Hill'),
(DEFAULT,'peter','hill'),
(DEFAULT,'COLIN','HOLDER'),
(DEFAULT,'Stuart','Imlach'),
(DEFAULT,'eric','jones'),
(DEFAULT,'MICK','KEARNS'),
(DEFAULT,'Frank','Kletzenbauer'),
(DEFAULT,'arthur','lightening'),
(DEFAULT,'BILLY','MYERSCOUGH'),
(DEFAULT,'Brian','Nicholas'),
(DEFAULT,'reg','ryan'),
(DEFAULT,'KEN','SATCHWELL'),
(DEFAULT,'Nelson','Stiffle'),
(DEFAULT,'ray','straw'),
(DEFAULT,'BOB','WESSON') ;
|
This returns the following results, I am not going to include the STATEMENT_TEXT below as it contains the same as the above SQL statement:
| NAME_ TYPE |
NAME | SCHEMA | COLUMN _NAME |
USAGE _TYPE |
SQL_ STATEMENT _TYPE |
| TABLE | PERSON | QTEMP | - | TARGET TYPE | INSERT |
TARGET TABLE in the USAGE_TYPE indicates that the table will be affected by an insert, delete, update or merge statement.
The next program just contains a delete statement, where any row equal where the last name is equal to "Hill" will be deleted.
exec sql DELETE FROM QTEMP.PERSON WHERE LASTNAME = 'Hill' ; |
This time two rows are returned, one for the table and the other for the column name used.
| NAME_ TYPE |
NAME | SCHEMA | COLUMN _NAME |
USAGE _TYPE |
SQL_ STATEMENT _TYPE |
| TABLE | PERSON | QTEMP | - | TARGET TYPE | DELETE |
| COLUMN | - | - | LASTNAME | QUERY | DELETE |
The next program is a bit more complicated as it contains a multiple row Fetch.
01 exec sql SET OPTION COMMIT = *NONE ;
02 exec sql DECLARE C0 CURSOR FOR
SELECT SYSTEM_VALUE_NAME,
CURRENT_NUMERIC_VALUE,
CAST(CURCHARVAL AS CHAR(1280) CCSID 37)
FROM QSYS2/SYSTEM_VALUE_INFO
ORDER BY SYSTEM_VALUE_NAME ;
03 exec sql OPEN C0 ;
04 exec sql FETCH C0 FOR :Rows ROWS INTO :InDs :InNulls ;
05 exec sql GET DIAGNOSTICS :Rows = ROW_COUNT ;
06 exec sql CLOSE C0 ;
|
The only rows returned are for the DECLARE CURSOR:
| NAME_ TYPE |
NAME | SCHEMA | COLUMN _NAME |
USAGE _TYPE |
SQL_ STATEMENT _TYPE |
| COLUMN | - | - | SYSTEM_ VALUE_NAME |
QUERY | DECLARE CURSOR |
| COLUMN | - | - | CURRENT_ NUMERIC _VALUE |
QUERY | DECLARE CURSOR |
| COLUMN | - | - | CURCHARVAL | QUERY | DECLARE CURSOR |
| TABLE | SYSTEM _VALUE _INFO |
QSYS2 | - | QUERY | DECLARE CURSOR |
| COLUMN | - | - | SYSTEM_ VALUE_NAME |
QUERY | DECLARE CURSOR |
The column SYSTEM_VALUE_NAME is there a second type as that is the column that is used for the ORDER BY.
The next program is yet more complex. This program does:
01 exec sql DROP TABLE QTEMP.TESTFILE ;
02 exec sql CREATE TABLE QTEMP.TESTFILE
(KEY_COLUMN FOR "KEYFLD",
FIRST,SECOND,THIRD,FOURTH,FIFTH,SIXTH)
AS (SELECT A.F1KEY1,A.F1F1,A.F1F2,
B.F2F1,B.F2F2,
C.F3F1,C.F3F2
FROM FILE1 A CROSS JOIN
FILE2 B CROSS JOIN
FILE3 C
WHERE A.F1KEY1 = B.F2KEY1
AND A.F1KEY1 = C.F3KEY1)
DEFINITION ONLY
INCLUDING COLUMN DEFAULTS ;
03 exec sql INSERT INTO QTEMP.TESTFILE
(KEY_COLUMN,FIRST,SECOND)
SELECT * FROM FILE1 ;
04 exec sql MERGE INTO QTEMP.TESTFILE A USING FILE2 B
ON A.KEY_COLUMN = B.F2KEY1
WHEN MATCHED THEN
UPDATE SET A.THIRD = B.F2F1,
A.FOURTH = B.F2F2
WHEN NOT MATCHED THEN
INSERT (KEY_COLUMN,THIRD,FOURTH)
VALUES (B.F2KEY1,B.F2F1,B.F2F2) ;
|
This program returns 24 rows, therefore, I am going to separate each set of rows by the SQL statement that generated them.
I find it interesting that the DROP TABLE did not generate any rows.
The CREATE TABLE did:
| NAME_ TYPE |
NAME | SCHEMA | COLUMN _NAME |
USAGE _TYPE |
SQL_ STATEMENT _TYPE |
| COLUMN | FILE1 | - | F1KEY | QUERY | CREATE TABLE |
| COLUMN | FILE1 | - | F1F1 | QUERY | CREATE TABLE |
| COLUMN | FILE1 | - | F1F1 | QUERY | CREATE TABLE |
| COLUMN | FILE1 | - | F1F2 | QUERY | CREATE TABLE |
| COLUMN | FILE2 | - | F2F1 | QUERY | CREATE TABLE |
| COLUMN | FILE3 | - | F3F1 | QUERY | CREATE TABLE |
| COLUMN | FILE3 | - | F3F1 | QUERY | CREATE TABLE |
| TABLE | FILE1 | - | - | QUERY | CREATE TABLE |
| TABLE | FILE2 | - | - | QUERY | CREATE TABLE |
| TABLE | FILE3 | - | - | QUERY | CREATE TABLE |
| COLUMN | FILE1 | - | F1KEY | QUERY | CREATE TABLE |
| COLUMN | FILE2 | - | F2KEY | QUERY | CREATE TABLE |
| COLUMN | FILE1 | - | F1KEY | QUERY | CREATE TABLE |
| COLUMN | FILE3 | - | F3KEY | QUERY | CREATE TABLE |
The last four rows are to do with the join criteria.
The next SQL statement in this program is the INSERT. Which generates the following rows:
| NAME_ TYPE |
NAME | SCHEMA | COLUMN _NAME |
USAGE _TYPE |
SQL_ STATEMENT _TYPE |
| TABLE | TESTFILE | QTEMP | - | TARGET TABLE | INSERT |
| COLUMN | TESTFILE | QTEMP | KEY_COLUMN | TARGET TABLE | INSERT |
| COLUMN | TESTFILE | QTEMP | FIRST | TARGET TABLE | INSERT |
| COLUMN | TESTFILE | QTEMP | KEY_COLUMN | SECOND | INSERT |
| TABLE | FILE1 | - | - | QUERY | INSERT |
The first table row is for the table that the data is being inserted into. The second table, and last row, is for the table that the data is taken from.
The final statement in this program was a MERGE.
| NAME_ TYPE |
NAME | SCHEMA | COLUMN _NAME |
USAGE _TYPE |
SQL_ STATEMENT _TYPE |
| TABLE | TESTFILE | QTEMP | - | TARGET TABLE | MERGE |
| COLUMN | TESTFILE | QTEMP | KEY_COLUMN | QUERY | MERGE |
| COLUMN | FILE2 | - | F2KEY1 | QUERY | MERGE |
| COLUMN | TESTFILE | QTEMP | THIRD | TARGET TABLE | MERGE |
| COLUMN | FILE2 | - | F2F1 | QUERY | MERGE |
The examples I have shown above are only part of what I experimented with the PARSE_STATEMENT table function. I have to admit I cannot think of a scenario I would use it. I am left asking myself: "What purpose do these results serve?"
I remain disappointed with the SYSPROGRAMSTMTSTAT view as it does not contain all of the SQL statements from my example programs. I might want to retrieve the SQL statement for a program from this view, but without statements, like the FETCH, I cannot say I feel confident I could recreate all of the SQL statements used by a particular program.
You can learn more about the PARSE_STATEMENT table function from the IBM website here.
This article was written for IBM i 7.3 and 7.2 .
The usefulness comes in hanging this off an ODBC exit or similar where you can quickly judge what verb the user is using and the tables acted upon. However it really is hampered by not being remote verb complete and if it does not like a statement for any reason; be it an unsupported verb or bad statement; you have to check the SQL state to see that it faiedl
ReplyDelete