Dynamic CTE's as part of a SProc in DB2/400 - sql

I'm trying to write a SProc in db2/400 in a V7R2 environment which creates a CTE based on the parameters passed. I then need to perform a recursive query on the CTE.
I'm running issues into creating and executing the dynamic CTE.
According to http://www-01.ibm.com/support/knowledgecenter/ssw_ibm_i_72/db2/rbafzpreph2.htm
the prepare statement does not work with the WITH or SELECT statements directly.
I tried to wrap both the dynamic CTE and dynamic SELECT in a VALUES INTO and manage to successfully prepare the statement. The issue then comes when I try to execute the statement.
I get an error code of SQL0518 which is defined here (CTRL+F for 'SQL0518' to jump down): http://publib.boulder.ibm.com/iseries/v5r2/ic2924/index.htm?info/rzala/rzalamsg.html (NOTE*: This link is for V5R2 but the error code and text portion of my error is exact to the error listed here with the same code. So I'm sure the error code remained the same between versions)
From the 3 recovery suggestions listed, the second seems unlikely to be the case since my execute is the very next line after my prepare. Suggestion 3 also seems unlikely because there is no use of commit or rollback. So I am inclined to believe suggestion 1 applies to my particular case. However, I do not understand how to take the suggested steps.
If &1 identifies a prepared SELECT or DECLARE PROCEDURE statement, a different prepared statement must be named in the EXECUTE statement.
Am I supposed to have two prepare statements for the same execute? Syntactically how would this look?
Here is the code for my SProc for reference:
CREATE OR REPLACE PROCEDURE DLLIB/G_DPIVOT# (
IN TABLE_NAME CHAR(12) CCSID 37 DEFAULT '' ,
IN PIVOT CHAR(12) CCSID 37 DEFAULT '' ,
IN PIVOTFLD CHAR(12) CCSID 37 DEFAULT '' ,
IN "VALUE" DECIMAL(10, 0) DEFAULT 0 ,
INOUT LIST CHAR(5000) CCSID 37 )
LANGUAGE SQL
SPECIFIC DLLIB/G_DPIVOT#
NOT DETERMINISTIC
READS SQL DATA
CALLED ON NULL INPUT
CONCURRENT ACCESS RESOLUTION DEFAULT
SET OPTION ALWBLK = *ALLREAD ,
ALWCPYDTA = *OPTIMIZE ,
COMMIT = *NONE ,
DECRESULT = (31, 31, 00) ,
DFTRDBCOL = *NONE ,
DYNDFTCOL = *NO ,
DYNUSRPRF = *USER ,
SRTSEQ = *HEX
BEGIN
DECLARE STMT1 VARCHAR ( 1000 ) ;
SET STMT1 = 'WITH DETAILS ( ' || TRIM ( PIVOT ) || ' , ' || TRIM ( PIVOTFLD ) || ' , CURR , PREV ) AS ( ' ||
'SELECT ' || TRIM ( PIVOT ) || ' ,' || TRIM ( PIVOTFLD ) || ',' ||
' ROW_NUMBER ( ) OVER ( PARTITION BY ' || TRIM ( PIVOT ) || ' ORDER BY ' || TRIM ( PIVOTFLD ) || ' ) AS CURR ,' ||
' ROW_NUMBER ( ) OVER ( PARTITION BY ' || TRIM ( PIVOT ) || ' ORDER BY ' || TRIM ( PIVOTFLD ) || ' ) - 1 AS PREV' ||
' FROM ' || TRIM ( TABLE_NAME ) ||
' WHERE ' || TRIM ( PIVOT ) || ' = ' || TRIM ( VALUE ) ||
' GROUP BY ' || TRIM ( PIVOT ) || ' , ' || TRIM ( PIVOTFLD ) || ' )' ||
' VALUES( SELECT MAX ( TRIM ( L '','' FROM CAST ( SYS_CONNECT_BY_PATH ( ' || TRIM ( PIVOTFLD ) || ' , '','' ) AS CHAR ( 5000 ) ) ) )' ||
' FROM DETAILS ' ||
' START WITH CURR = 1 ' ||
' CONNECT BY NOCYCLE ' || TRIM ( PIVOT ) || ' = PRIOR ' || TRIM ( PIVOT ) || ' AND PREV = PRIOR CURR) INTO ?' ;
--SET LIST = STMT1; -- If I execute the value of LIST in interactive SQL everything is as expected (minus the VALUES INTO ofcourse)
PREPARE S1 FROM STMT1 ;
EXECUTE S1 USING LIST; -- If I comment this I don't get an error, but I also don't get a return value in LIST)
END ;
Any assistance is appreciated.
EDIT 1: I am trying to create a SProc (which I will use to create a UDF) which has 5 parameters. I am trying to pivot a single field spanning across multiple records so the values are returned as a comma delimited string. I want to make this dynamic though so I can re-use it for many situations. An example call would be: CALL DLLIB.G_DPIVOT#(TABLE, PIVOT, PIVOTFLD, VALUE, LIST); Where TABLE is the name of the table I want to pivot, PIVOT is the commonality between records (FK), PIVOTFLD is the field I want to condense to a single string, VALUE is the FK value I want to use to pivot on, and LIST is the OUT parameter which would contain the resulting string. You can read more about a non-dynamic implementation here: http://www.mcpressonline.com/sql/techtip-combining-multiple-row-values-into-a-single-row-with-sql-in-db2-for-i.html
The use is for when I have a header table which has a one-to-many relationship with another table. I'll then be able to summarize all the values of a particular field in the "many" table based on the PK/FK relationship.
EDIT 2:
Here is a recent attempt which I think I manage to successfully create the CTE using EXECUTE IMMEDIATE and am now trying to just perform a simple select on it. I'm trying to make use of DB2 cursors but get at error at the "C2" on the line DECLARE C2 CURSOR FOR S2;. I don't have much experience with DB2 cursors but believe I am using them in the correct way.
DECLARE STMT1 VARCHAR ( 1000 ) ;
DECLARE STMT2 VARCHAR ( 1000 ) ;
SET STMT1 = 'WITH DETAILS ( ' || TRIM ( PIVOT ) || ' , ' || TRIM ( PIVOTFLD ) || ' , CURR , PREV ) AS ( ' ||
'SELECT ' || TRIM ( PIVOT ) || ' ,' || TRIM ( PIVOTFLD ) || ',' ||
' ROW_NUMBER ( ) OVER ( PARTITION BY ' || TRIM ( PIVOT ) || ' ORDER BY ' || TRIM ( PIVOTFLD ) || ' ) AS CURR ,' ||
' ROW_NUMBER ( ) OVER ( PARTITION BY ' || TRIM ( PIVOT ) || ' ORDER BY ' || TRIM ( PIVOTFLD ) || ' ) - 1 AS PREV' ||
' FROM ' || TRIM ( TABLE_NAME ) ||
' WHERE ' || TRIM ( PIVOT ) || ' = ' || TRIM ( VALUE ) ||
' GROUP BY ' || TRIM ( PIVOT ) || ' , ' || TRIM ( PIVOTFLD ) || ' )';
EXECUTE IMMEDIATE STMT1;
SET STMT2 = "SELECT * FROM DETAILS";
PREPARE S2 FROM STMT2;
DECLARE C2 CURSOR FOR S2;
OPEN C2;
FETCH C2 INTO LIST;
CLOSE C2;
Does anyone see anything wrong with these changes?
Here is the exact error message (excluding suggestion text):
SQL State: 42601
Vendor Code: -104
Message: [SQL0104] Token C2 was not valid. Valid tokens: GLOBAL.
EDIT 3 (Final SProc):
#user2338816 for all of the help. See his post for an explanation of the solution, but here is the final SProc for reference:
CREATE PROCEDURE DLLIB/G_DPIVOT# (
IN TABLE_NAME CHAR(12) CCSID 37 DEFAULT '' ,
IN PIVOT CHAR(12) CCSID 37 DEFAULT '' ,
IN PIVOTFLD CHAR(12) CCSID 37 DEFAULT '' ,
IN "VALUE" DECIMAL(10, 0) DEFAULT 0 ,
INOUT LIST CHAR(5000) CCSID 37 )
LANGUAGE SQL
SPECIFIC DLLIB/G_DPIVOT#
NOT DETERMINISTIC
READS SQL DATA
CALLED ON NULL INPUT
CONCURRENT ACCESS RESOLUTION DEFAULT
SET OPTION ALWBLK = *ALLREAD ,
ALWCPYDTA = *OPTIMIZE ,
COMMIT = *NONE ,
DECRESULT = (31, 31, 00) ,
DFTRDBCOL = *NONE ,
DYNDFTCOL = *NO ,
DYNUSRPRF = *USER ,
SRTSEQ = *HEX
BEGIN
DECLARE STMT1 VARCHAR ( 1000 ) ;
DECLARE C1 CURSOR FOR S1 ;
SET STMT1 = 'WITH DETAILS ( ' || TRIM ( PIVOT ) || ' , ' || TRIM ( PIVOTFLD ) || ' , CURR , PREV ) AS ( ' ||
'SELECT ' || TRIM ( PIVOT ) || ' ,' || TRIM ( PIVOTFLD ) || ',' ||
' ROW_NUMBER ( ) OVER ( PARTITION BY ' || TRIM ( PIVOT ) || ' ORDER BY ' || TRIM ( PIVOTFLD ) || ' ) AS CURR ,' ||
' ROW_NUMBER ( ) OVER ( PARTITION BY ' || TRIM ( PIVOT ) || ' ORDER BY ' || TRIM ( PIVOTFLD ) || ' ) - 1 AS PREV' ||
' FROM ' || TRIM ( TABLE_NAME ) ||
' WHERE ' || TRIM ( PIVOT ) || ' = ' || TRIM ( VALUE ) ||
' GROUP BY ' || TRIM ( PIVOT ) || ' , ' || TRIM ( PIVOTFLD ) || ' )' ||
' SELECT MAX ( TRIM ( L '','' FROM CAST ( SYS_CONNECT_BY_PATH ( ' || TRIM ( PIVOTFLD ) || ' , '','' ) AS CHAR ( 5000 ) ) ) ) ' ||
' FROM DETAILS ' ||
' START WITH CURR = 1 ' ||
' CONNECT BY NOCYCLE ' || TRIM ( PIVOT ) || ' = PRIOR ' || TRIM ( PIVOT ) || ' AND PREV = PRIOR CURR' ;
PREPARE S1 FROM STMT1 ;
OPEN C1 ;
FETCH C1 INTO LIST ;
CLOSE C1 ;
END ;

The basic problem is in the EXECUTE. You can't "execute" the prepared SELECT. Instead, you'll need to DECLARE CURSOR for S1 and FETCH rows from the CURSOR. Note that 'executing' a SELECT statement wouldn't actually do anything if it was allowed; it would just "SELECT", so EXECUTE doesn't make much sense. (A SELECT INTO statement can be different, but it's not clear if that's appropriate here.)
It might be possible to OPEN a CURSOR and return a result set rather than FETCHing rows. With more definition of how you actually want to use this, some elaboration should be possible.
Edit:
Second problem:
I've created more readable versions of your original CTE and the CTE in your edited question. The original:
WITH DETAILS ( PIVOT , PIVOTFLD , CURR , PREV ) AS (
SELECT PIVOT , PIVOTFLD ,
ROW_NUMBER ( ) OVER ( PARTITION BY PIVOT ORDER BY PIVOTFLD ) AS CURR ,
ROW_NUMBER ( ) OVER ( PARTITION BY PIVOT ORDER BY PIVOTFLD ) - 1 AS PREV
FROM TABLE_NAME
WHERE PIVOT = VALUE
GROUP BY PIVOT , PIVOTFLD )
VALUES( SELECT MAX ( CAST ( SYS_CONNECT_BY_PATH ( PIVOTFLD , ',' ) AS CHAR ( 5000 ) ) ) )
FROM DETAILS
START WITH CURR = 1
CONNECT BY NOCYCLE PIVOT = PRIOR PIVOT AND PREV = PRIOR CURR) INTO ? ;
You have a VALUE INTO statement after the CTE. AFAIK, that's not valid.
And your edited example:
WITH DETAILS ( PIVOT , PIVOTFLD , CURR , PREV ) AS (
SELECT PIVOT ,
PIVOTFLD ,
ROW_NUMBER ( ) OVER ( PARTITION BY PIVOT ORDER BY PIVOTFLD ) AS CURR ,
ROW_NUMBER ( ) OVER ( PARTITION BY PIVOT ORDER BY PIVOTFLD ) - 1 AS PREV
FROM TABLE_NAME
WHERE PIVOT = VALUE
GROUP BY PIVOT , PIVOTFLD );
Well, it's just a bare CTE that has no associated SELECT referencing it. You do try to PREPARE a SELECT statement later, but the two need to go together. You can't EXECUTE the CTE by itself.
Try putting them together as a single statement and see if a CURSOR creates over the result. Variable STMT1 would then look something like this:
WITH DETAILS ( PIVOT , PIVOTFLD , CURR , PREV ) AS (
SELECT PIVOT ,
PIVOTFLD ,
ROW_NUMBER ( ) OVER ( PARTITION BY PIVOT ORDER BY PIVOTFLD ) AS CURR ,
ROW_NUMBER ( ) OVER ( PARTITION BY PIVOT ORDER BY PIVOTFLD ) - 1 AS PREV
FROM TABLE_NAME
WHERE PIVOT = VALUE
GROUP BY PIVOT , PIVOTFLD )
SELECT * FROM DETAILS ;
Note that the statement includes the SELECT at the end. The WITH ... clause is followed by the SELECT ... in a single statement that is PREPAREd. The CURSOR would then be OPENed over that statement.
Edit 2:
I have modified a sample CTE that I've had for a while to fit into a stored proc and to return a value. It was compiled and run on my i 6.1 system. The CTE is PREPAREd from a string placed into a VARCHAR, then a CURSOR is opened over it. Rows are FETCHed in a WHILE-loop.
The CTE generates summary rows that are then UNIONed with detail rows from QIWS/QCUSTCDT. The summary is by STATE to provided a sub-total of BALDUE. The WHILE-loop is kind of meaningless; it only shows FETCHing and processing of rows. The only action is to count the number of rows that are not summary rows out of the CTE. This is essentially the same as the number of rows in the base table. The row count is returned in the rowCnt OUT parameter.
The source code is copy/pasted, but comes from two sources. First, the CREATE PROCEDURE statement is taken from iNavigator's 'Run SQL scripts' utility after generating the SQL from the compiled stored procedure. And second, the BEGIN ... END compound statement body is from the original I typed into the iNavigator New-> Procedure function. Although the two would have logical equivalence, I wanted to preserve the actual lines that were input. You can copy/paste the entire source into 'Run SQL Scripts' or go through the utility to create the procedure and only copy/paste the BEGIN ... END compound statement after entering values into the first two tabs of the New-> Procedure function.
I have a schema named SQLEXAMPLE that I build things like this into. You'll need to adjust the schema and procedure names to fit your environment. The QIWS/QCUSTCDT table should exist on nearly all AS/400-series systems.
CREATE PROCEDURE SQLEXAMPLE.CTE_CustCDT (
OUT rowCnt INTEGER )
LANGUAGE SQL
SPECIFIC SQLEXAMPLE.CTECUSTCDT
NOT DETERMINISTIC
READS SQL DATA
CALLED ON NULL INPUT
SET OPTION ALWBLK = *ALLREAD ,
ALWCPYDTA = *OPTIMIZE ,
COMMIT = *NONE ,
CLOSQLCSR = *ENDMOD ,
DECRESULT = (31, 31, 00) ,
DFTRDBCOL = *NONE ,
DYNDFTCOL = *NO ,
DYNUSRPRF = *USER ,
SRTSEQ = *HEX
BEGIN
DECLARE sumRows INTEGER DEFAULT 0 ;
DECLARE cusNum INTEGER ;
DECLARE lstNam CHAR(10) ;
DECLARE state CHAR(2) ;
DECLARE balDue DECIMAL(7, 2) ;
DECLARE stmt1 VARCHAR(512) ;
DECLARE at_end INT DEFAULT 0 ;
DECLARE not_found
CONDITION FOR '02000';
DECLARE c1 CURSOR FOR c1Stmt ;
DECLARE CONTINUE HANDLER FOR not_found
SET at_end = 1 ;
SET stmt1 = 'with t1 As(
SELECT 0 ,''Tot'' , state , sum( balDue )
FROM qiws.qcustcdt
GROUP BY state
ORDER BY state
)
select cusNum , lstNam , state, balDue
from qiws.qcustcdt
union
select *
from t1
order by state FOR FETCH ONLY' ;
PREPARE c1Stmt FROM stmt1 ;
OPEN c1 ;
FETCH C1 INTO cusNum , lstNam , state , balDue ;
WHILE at_end = 0 DO
IF cusNum <> 0 THEN SET sumRows = sumRows + 1 END IF ;
FETCH C1 INTO cusNum , lstNam , state , balDue ;
END WHILE ;
SET rowCnt = sumRows ;
CLOSE c1 ;
END
When the CTE is run by itself in STRSQL, the first few lines of output look like this:
....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+.
CUSNUM LSTNAM STATE BALDUE
475,938 Doe CA 250.00
0 Tot CA 250.00
389,572 Stevens CO 58.75
0 Tot CO 58.75
938,485 Johnson GA 3,987.50
0 Tot GA 3,987.50
846,283 Alison MN 10.00
583,990 Abraham MN 500.00
0 Tot MN 510.00
The summary rows should easily be recognized. And when the stored proc is CALLed from 'Run SQL Scripts', the resulting output is:
Connected to relational database TISI on Tisi as Toml - 090829/Quser/Qzdasoinit
> call SQLEXAMPLE.CTE_CustCDT( 0 )
Return Code = 0
Output Parameter #1 = 12
Statement ran successfully (570 ms)
The QIWS/QCUSTCDT table on that system has 12 rows, and that matches the value returned.
It's not exactly the same as your desired CTE, but it should demonstrate that a dynamic CTE can be used. It also shows how FETCHes might pull rows from the CTE for whatever purpose is needed.

Related

Creating BigQuery Stored Procedure by passing column and table name as parameters

I am trying to create BigQuery stored Procedure that will accept column and Table name as parameters.
This is the running Query:
EXECUTE IMMEDIATE (
''' SELECT
''' || (SELECT STRING_AGG(DISTINCT "MAX(IF(PROPERTY_KEY = '" || PROPERTY_KEY || "', a.RANK, NULL)) AS " || PROPERTY_KEY)
FROM `project.dataset.table` x
) || '''
FROM (
select "1" AS GroupbyCol,
PROPERTY_KEY ,
rank() over (order by PROPERTY_KEY ) AS RANK
from (
select distinct PROPERTY_KEY
from `project.dataset.table`
)
) a GROUP BY GroupbyCol
''')
I tried creating stored procedure as below:
CREATE OR REPLACE PROCEDURE `project.dataset.test_stored_procedure_ne`
(table_name STRING,
destination_table STRING,
row_ids STRING,
pivot_col_name STRING,
pivot_col_value STRING,
aggregation STRING)
BEGIN
DECLARE header STRING;
EXECUTE IMMEDIATE (
"SELECT
''' || (SELECT STRING_AGG(DISTINCT "MAX(IF(#pivot_col_name = '" || #pivot_col_name || "', a.RANK, NULL)) AS " || #pivot_col_name)
FROM `table_name` x
) || '''
FROM (
select "1" AS GroupbyCol,
#pivot_col_name ,
rank() over (order by #pivot_col_name ) AS RANK
from (
select distinct #pivot_col_name
from `table_name`
)
) a GROUP BY GroupbyCol
") header
USING pivot_col_name AS pivot_col_name
END;
Calling SP as :
CALL `project.dataset.test_stored_procedure_ne`(
'project.dataset.InputTbl' #table_name
, 'project.dataset.outputTbl' #destination_table
, 'Id' #row_id
, 'PROPERTY_KEY' # column name
, 'PROPERTY_VALUE' #column value
, 'MAX' #aggregation if any
);
Getting Error as :
Error validating procedure body (add OPTIONS(strict_mode=false) to suppress): Query error: Invalid value: Table name "table_name" missing dataset while no default dataset is set in the request.
Please explain how and when to put quotes which I believe am misplacing
Please explain how and when to put quotes which I believe am misplacing
You have below in few places
FROM `table_name`
Just replace those with below
FROM `"||table_name||"`

Pivoting a table in iSeries DB2 dynamically

So what I'm trying to do is convert rows into columns. This has been covered before on this site in a verity of ways, the way I like the best is as follows:
SELECT
FRNPCM032.WFP.WFORD#,
MAX(CASE WHEN FRNPCM032.WFP.WFSEGN = 'COLOR1' THEN WFVAL END) AS COLOR,
MAX(CASE WHEN FRNPCM032.WFP.WFSEGN = 'OSKVA' THEN WFVAL END) AS KVA,
MAX(CASE WHEN FRNPCM032.WFP.WFSEGN = 'OSSWITCH' THEN WFVAL END) AS LBSWITCH
FROM FRNPCM032.WFP
GROUP BY FRNPCM032.WFP.WFORD#;
This is a very simple way, but I need something less manual than this because new rows might be added later and I dont want to have to go back to keep maintaining a query or view.
Is there a way I can dynamically do this? I can think of a way to do it with loops, but i cant do that in a query or view.
Unfortunately, DB2 for IBM i doesn't have a PIVOT() function...
However, you can build a procedure that dynamically looks at the current contents of the table and builds an SQL statement that pivots the data.
The following code was taken from the article, An SQL Pivot Procedure
If you wanted you could modify the code to build a view, rather than returning a result set. But you'd have to rebuild the view if the pivot values changed.
CREATE PROCEDURE DO_PIVOT
(IN FOR_SCHEMA CHARACTER (10) ,
IN FOR_TABLE CHARACTER (10) ,
IN PIVOT_COLUMN VARCHAR (250) ,
IN VALUE_COLUMN VARCHAR (250) ,
IN AGG_FUNCTION VARCHAR (5) DEFAULT 'SUM' ,
IN GROUP_COLUMN VARCHAR (250) DEFAULT NULL )
LANGUAGE SQL
MODIFIES SQL DATA
PROGRAM TYPE SUB
CONCURRENT ACCESS RESOLUTION DEFAULT
DYNAMIC RESULT SETS 1
OLD SAVEPOINT LEVEL COMMIT ON RETURN NO
BEGIN
DECLARE SQLCODE INTEGER DEFAULT 0 ;
DECLARE SQL_STATEMENT VARCHAR ( 5000 ) ;
DECLARE PIVOT_VALUE VARCHAR ( 20 ) ;
DECLARE PAD CHAR ( 2 ) DEFAULT ' ' ;
DECLARE C1 CURSOR FOR D1 ;
DECLARE C2 CURSOR WITH RETURN FOR D2 ;
SET SCHEMA = FOR_SCHEMA ;
-- Get the list of values available for the pivot column
-- Each value will be a column in the return set
SET SQL_STATEMENT = 'select distinct '
|| PIVOT_COLUMN
|| ' from '
|| FOR_TABLE
|| ' order by 1' ;
PREPARE D1 FROM SQL_STATEMENT ;
OPEN C1 ;
-- Construct a dynamic select statement for the pivot
SET SQL_STATEMENT = 'select ' ;
-- If requested, add the Group By Column
-- to the select clause
IF GROUP_COLUMN IS NOT NULL THEN
SET SQL_STATEMENT = SQL_STATEMENT || GROUP_COLUMN ;
SET PAD = ', ' ;
END IF ;
-- For each possible value for the Pivot Column,
-- add a case statement to perform the requested
-- aggregate function on the Value Column
FETCH NEXT FROM C1 INTO PIVOT_VALUE ;
WHILE ( SQLCODE >= 0 AND SQLCODE <> 100 ) DO
SET SQL_STATEMENT = SQL_STATEMENT
|| PAD
|| AGG_FUNCTION
|| '(CASE WHEN '
|| PIVOT_COLUMN
|| ' = '''
|| PIVOT_VALUE
|| ''' THEN '
|| VALUE_COLUMN
|| ' END) AS '
|| PIVOT_VALUE ;
SET PAD = ', ' ;
FETCH NEXT FROM C1 INTO PIVOT_VALUE ;
END WHILE ;
CLOSE C1 ;
-- Specify the table to select from
SET SQL_STATEMENT = SQL_STATEMENT
|| ' from '
|| FOR_TABLE ;
-- If requested, add the Group By Column
-- to the select clause
IF GROUP_COLUMN IS NOT NULL THEN
SET SQL_STATEMENT = SQL_STATEMENT
|| ' group by '
|| GROUP_COLUMN
|| ' order by '
|| GROUP_COLUMN;
END IF ;
PREPARE D2 FROM SQL_STATEMENT ;
OPEN C2 ;
END ;
LABEL ON ROUTINE DO_PIVOT
( CHAR(), CHAR(), VARCHAR(), VARCHAR(), VARCHAR(), VARCHAR() )
IS 'Perform a General Purpose Pivot';
COMMENT ON PARAMETER ROUTINE DO_PIVOT
( CHAR(), CHAR(), VARCHAR(), VARCHAR(), VARCHAR(), VARCHAR() )
(FOR_SCHEMA IS 'Schema for Table' ,
FOR_TABLE IS 'For Table' ,
PIVOT_COLUMN IS 'Name of Column to be Pivoted' ,
VALUE_COLUMN IS 'Column to be Aggregated for Pivot' ,
AGG_FUNCTION IS 'Use Aggregate Function' ,
GROUP_COLUMN IS 'Group on Column' ) ;

Pivoting DB2 Data - where number of columns and and rows are not fixed

I've seen a few similar Q/A's on here, but the cases I've seen were variations on the "sales by year across quarter" variety, so DECODE is used, with 4 categories.
In my case, I don't know in advance how many rows or columns the pivot will have.
| Pay | Age | Value |
|-----|-----|-------|
| 1 | 1 | 10 |
| 1 | 2 | 20 |
| 1 | 3 | 30 |
| 2 | 1 | 90 |
| 2 | 2 | 80 |
| 2 | 3 | 70 |
and we want the result set as
PAYGROUP Millennials GenX Boomers
1 10 20 30
2 90 80 70
This would be easy with a PIVOT statement, i.e.
Transform Max(VALUE) AS V
SELECT PAYGROUP
FROM table
GROUP BY PAYGROUP
PIVOT AGEGROUP;
but my DB2 has no PIVOT function.
The number of pay groups and age groups may vary from case to case, e.g., the data can have different numbers of pay and age groupings for different cases.
It can be done, but only within an SQL procedure or other HLL.
You used one SQL statement to find out what/how many distinct values then build a dynamic SQL statement using those values.
Can probably point you to an example if you add your DB2 platform and version to the question.
Here's a pure SQL procedure (originally for DB2 for IBM i but should work for LUW ) found here: https://www.itjungle.com/2015/04/21/fhg042115-story01/
SET SCHEMA = WHER_YOU_WANT_IT;
CREATE PROCEDURE DO_PIVOT
(IN FOR_SCHEMA CHARACTER (10) ,
IN FOR_TABLE CHARACTER (10) ,
IN PIVOT_COLUMN VARCHAR (250) ,
IN VALUE_COLUMN VARCHAR (250) ,
IN AGG_FUNCTION VARCHAR (5) DEFAULT 'SUM' ,
IN GROUP_COLUMN VARCHAR (250) DEFAULT NULL )
LANGUAGE SQL
MODIFIES SQL DATA
PROGRAM TYPE SUB
CONCURRENT ACCESS RESOLUTION DEFAULT
DYNAMIC RESULT SETS 1
OLD SAVEPOINT LEVEL COMMIT ON RETURN NO
BEGIN
DECLARE SQLCODE INTEGER DEFAULT 0 ;
DECLARE SQL_STATEMENT VARCHAR ( 5000 ) ;
DECLARE PIVOT_VALUE VARCHAR ( 20 ) ;
DECLARE PAD CHAR ( 2 ) DEFAULT ' ' ;
DECLARE C1 CURSOR FOR D1 ;
DECLARE C2 CURSOR WITH RETURN FOR D2 ;
SET SCHEMA = FOR_SCHEMA ;
-- Get the list of values available for the pivot column
-- Each value will be a column in the return set
SET SQL_STATEMENT = 'select distinct '
|| PIVOT_COLUMN
|| ' from '
|| FOR_TABLE
|| ' order by 1' ;
PREPARE D1 FROM SQL_STATEMENT ;
OPEN C1 ;
-- Construct a dynamic select statement for the pivot
SET SQL_STATEMENT = 'select ' ;
-- If requested, add the Group By Column
-- to the select clause
IF GROUP_COLUMN IS NOT NULL THEN
SET SQL_STATEMENT = SQL_STATEMENT || GROUP_COLUMN ;
SET PAD = ', ' ;
END IF ;
-- For each possible value for the Pivot Column,
-- add a case statement to perform the requested
-- aggregate function on the Value Column
FETCH NEXT FROM C1 INTO PIVOT_VALUE ;
WHILE ( SQLCODE >= 0 AND SQLCODE <> 100 ) DO
SET SQL_STATEMENT = SQL_STATEMENT
|| PAD
|| AGG_FUNCTION
|| '(CASE WHEN '
|| PIVOT_COLUMN
|| ' = '''
|| PIVOT_VALUE
|| ''' THEN '
|| VALUE_COLUMN
|| ' END) AS '
|| PIVOT_VALUE ;
SET PAD = ', ' ;
FETCH NEXT FROM C1 INTO PIVOT_VALUE ;
END WHILE ;
CLOSE C1 ;
-- Specify the table to select from
SET SQL_STATEMENT = SQL_STATEMENT
|| ' from '
|| FOR_TABLE ;
-- If requested, add the Group By Column
-- to the select clause
IF GROUP_COLUMN IS NOT NULL THEN
SET SQL_STATEMENT = SQL_STATEMENT
|| ' group by '
|| GROUP_COLUMN
|| ' order by '
|| GROUP_COLUMN;
END IF ;
PREPARE D2 FROM SQL_STATEMENT ;
OPEN C2 ;
END ;
LABEL ON ROUTINE DO_PIVOT
( CHAR(), CHAR(), VARCHAR(), VARCHAR(), VARCHAR(), VARCHAR() )
IS 'Perform a General Purpose Pivot';
COMMENT ON PARAMETER ROUTINE DO_PIVOT
( CHAR(), CHAR(), VARCHAR(), VARCHAR(), VARCHAR(), VARCHAR() )
(FOR_SCHEMA IS 'Schema for Table' ,
FOR_TABLE IS 'For Table' ,
PIVOT_COLUMN IS 'Name of Column to be Pivoted' ,
VALUE_COLUMN IS 'Column to be Aggregated for Pivot' ,
AGG_FUNCTION IS 'Use Aggregate Function' ,
GROUP_COLUMN IS 'Group on Column' ) ;
I think you could also use the XML functions to pivot the data...but I've yet to find a good example of that

oracle translate function is giving error to convert in number

I have a query where I need to remove the first and last quote from a string to use it in clause. When I run the following query ::
with t as (
select '1,2,3' x from dual)
select translate(x, ' '||chr(39)||chr(34), ' ' ) from t
it gives the result > 1,2,3
But when I run the following query ::
select * from care_topic_templates where care_topic_id in (
with t as (
select '1,2,3' x from dual)
select translate(x, ' '||chr(39)||chr(34), ' ' ) from t
);
it gives this error > ORA-01722: invalid number.
Because you are comparing an integer id to a string, which looks like '1,2,3' -- and this string cannot be converted to an integer, even after the strange substitutions using translate(). Strings are not lists.
You can do what you want using like and a correlated subquery:
select *
from care_topic_templates
where exists (select 1
from (select '1,2,3' as x from dual) x
where ',' || x || ',' like '%,' || care_topic_id || ',%'
);
Or, in your case:
select *
from care_topic_templates
where exists (select 1
from (select '1,2,3' as x from dual) x
where ',' || translate(x, ' '||chr(39)||chr(34), ' ') || ',' like '%,' || care_topic_id || ',%'
);
This is following the logic of your query. There are other ways to express this logic.

Order by in subquery (for jQuery jTable) doesn't work?

Some background:
My framework jQuery jTable, allows me to do pagination and sort columns, in my select query I need to retrieve n rows (from nth, to nth) and previously order the data by the selected column.
I have a table with n columns where would not exist some rows (this is an example):
To achieve the first requirement I wrote the follow procedure:
create or replace
PROCEDURE PR_SHOWVALUESOLD
(
PRMROWMIN IN NUMBER
, PRMROWMAX IN NUMBER
, CURSORRESULT OUT SYS_REFCURSOR
) AS
BEGIN
open CURSORRESULT for
select * from
(select v.*, rownum r,
(
select count(*) TOTALITEMS from TABLE1 v
) TOTALITEMS
from TABLE1 v
) d
where d.r >= PRMROWMIN and d.r <= PRMROWMAX;
END PR_SHOWVALUESOLD;
This work successfully, I execute the procedure with the follows parameters (PRMROWMIN = 6, PRMROWMAX = 9), the result of the procedure are in Output Varibles window.
Now comes the next step, I need to order the data before take from n to x row.
I rewrite the procedure to do this, but doesn't work:
CREATE OR REPLACE PROCEDURE PR_SHOWVALUES
(
PRMROWMIN IN NUMBER
, PRMROWMAX IN NUMBER
, PRMORDERCOL IN VARCHAR2
, PRMORDERDIR IN VARCHAR2
, CURSORRESULT OUT SYS_REFCURSOR
) AS
BEGIN
open CURSORRESULT for
select * from
(select v.*, rownum r,
(
select count(*) TOTALITEMS from TABLE1 v
) TOTALITEMS
from TABLE1 v
order by 'LOWER(' || PRMORDERCOL || ')' || ' ' || PRMORDERDIR
) d
where d.r >= PRMROWMIN and d.r <= PRMROWMAX;
END PR_SHOWVALUES;
I executed the modified procedure with the follows parameters:
PRMROWMIN := 6;
PRMROWMAX := 9;
PRMORDERCOL := 'COLUMNA';
PRMORDERDIR := 'DESC';
I expected the highlighted rows Query Result 2 window (but this new procedure retrieve the same data as old but disordered Output Variables Window):
How to achieve my requirements?
Thanks in advance.
This is your order by:
order by 'LOWER(' || PRMORDERCOL || ')' || ' ' || PRMORDERDIR
It is not applying the function lower(). Instead, it is concatenating the strings. You may mean:
order by LOWER(PRMORDERCOL) ' ' || PRMORDERDIR
I found a solution to my requirement.
I need to use DECODE to match every column to sort.
I can order in subquery, in this case I do two order by in two subqueries.
The documentation of PL/SQL DECODE function are in:
PL/SQL DECODE FUNCTION
The final Procedure are:
CREATE OR REPLACE PROCEDURE PR_SHOWVALUES
(
PRMROWMIN IN NUMBER
, PRMROWMAX IN NUMBER
, PRMORDERCOL IN VARCHAR2
, PRMORDERDIR IN VARCHAR2
, CURSORRESULT OUT SYS_REFCURSOR
) AS
BEGIN
open CURSORRESULT for
select * from (
select rownum r, v.* from
(
select * from
(
select * from table1 tbl
order by decode
(
UPPER(PRMORDERCOL),
'COLUMNA', LOWER(tbl.COLUMNA),
'COLUMNB', LOWER(tbl.COLUMNB),
LOWER(tbl.TABLE1_ID)
)
)
ORDER BY
CASE
WHEN UPPER(PRMORDERDIR) = 'DESC' THEN
ROWNUM * -1
ELSE
ROWNUM
END
) v
)
where r >= PRMROWMIN and r <= PRMROWMAX;
END PR_SHOWVALUES;
Acknowledgment to Jack David Baucum where I found the solution.