DB2/400 中作为 SProc 一部分的动态 CTE

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

我正在尝试在 V7R2 环境中 db2/400 中编写一个 SProc,它会根据传递的参数创建一个 CTE。然后我需要在 CTE 上执行递归查询。

我 运行 正在创建和执行动态 CTE。

根据http://www-01.ibm.com/support/knowledgecenter/ssw_ibm_i_72/db2/rbafzpreph2.htm prepare 语句不能直接与 WITHSELECT 语句一起使用。

我尝试将动态 CTE 和动态 SELECT 包装在 VALUES INTO 中,并成功准备了语句。当我尝试执行该语句时,问题就来了。

我得到一个错误代码SQL0518,定义在这里(CTRL+F for 'SQL0518'跳转):http://publib.boulder.ibm.com/iseries/v5r2/ic2924/index.htm?info/rzala/rzalamsg.html(注意*:这个link适用于 V5R2,但我的错误的错误代码和文本部分与此处列出的错误完全相同,代码相同。所以我确定错误代码在不同版本之间保持相同)

从列出的 3 条恢复建议来看,第二条似乎不太可能,因为我的 execute 是我的 prepare 之后的下一行。建议 3 似乎也不太可能,因为没有使用 commitrollback。所以我倾向于相信建议 1 适用于我的具体情况。但是,我不明白如何执行建议的步骤。

If &1 identifies a prepared SELECT or DECLARE PROCEDURE statement, a different prepared statement must be named in the EXECUTE statement.

我是否应该为同一个执行准备两个准备语句?这在句法上看起来如何?

这是我的 SProc 的代码供参考:

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  ; 

感谢任何帮助。

编辑 1:我正在尝试创建一个具有 5 个参数的 SProc(我将使用它来创建 UDF)。我正在尝试对跨越多个记录的单个字段进行透视,以便将值作为逗号分隔的字符串返回。不过我想让它动态化,这样我就可以 re-use 它适用于很多情况。一个示例调用是:CALL DLLIB.G_DPIVOT@(TABLE, PIVOT, PIVOTFLD, VALUE, LIST); 其中 TABLE 是我要转换的 table 的名称,PIVOT 是记录之间的共性 (FK),PIVOTFLD 是我想压缩成单个字符串的字段,VALUE 是我想用来转换的 FK 值,LIST 是包含结果字符串的 OUT 参数。您可以在此处阅读有关 non-dynamic 实施的更多信息:http://www.mcpressonline.com/sql/techtip-combining-multiple-row-values-into-a-single-row-with-sql-in-db2-for-i.html

当我有一个 header table 与另一个 table 有 one-to-many 关系时使用。然后,我将能够根据 PK/FK 关系汇总 "many" table 中特定字段的所有值。

编辑 2:

这是我最近的一次尝试,我认为我设法使用 EXECUTE IMMEDIATE 成功创建了 CTE,现在我正在尝试对其执行简单的 select。我正在尝试使用 DB2 游标,但在 DECLARE C2 CURSOR FOR S2; 行的 "C2" 处出错。我对 DB2 游标没有太多经验,但相信我正在以正确的方式使用它们。

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;

有人发现这些更改有什么问题吗?

这是准确的错误消息(不包括建议文本):

SQL State: 42601
Vendor Code: -104
Message: [SQL0104] Token C2 was not valid. Valid tokens: GLOBAL. 

编辑 3(最终存储过程): @user2338816 提供所有帮助。参见他的 post 解决方案的解释,但这里是最终的 SProc 以供参考:

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  ; 

基本问题在 EXECUTE 中。你不能"execute"准备好的SELECT。相反,您需要为 S1 声明 CURSOR 并从 CURSOR 获取行。请注意,如果允许,'executing' 和 SELECT 语句实际上不会执行任何操作;它只是 "SELECT",所以 EXECUTE 没有多大意义。 (SELECT INTO 语句可以不同,但​​不清楚它在这里是否合适。)

打开一个 CURSOR 和 return 一个结果集而不是 FETCHing 行是可能的。随着对您实际想要如何使用它的更多定义,应该可以进行一些详细说明。

编辑:

第二题:

我已经为您的原始 CTE 和您编辑的问题中的 CTE 创建了更具可读性的版本。原文:

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 ? ;

您在 CTE 之后有一个 VALUE INTO 语句。据我所知,那是无效的。

以及您编辑的示例:

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 引用它的裸 CTE。您稍后会尝试 PREPARE a SELECT 语句,但两者需要一起进行。您无法单独执行 CTE。

尝试将它们放在一起作为一个语句,看看是否有 CURSOR 在结果上创建。变量 STMT1 看起来像这样:

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 ;

请注意,语句末尾包含 SELECT。 WITH ... 子句后跟 单个语句 中的 SELECT ...,这是 PREPAREd。然后将在该语句上打开 CURSOR。

编辑 2:

我已经修改了我已经使用了一段时间的示例 CTE,以适应存储过程和 return 一个值。它是在我的 i 6.1 系统上编译和 运行 的。 CTE 是根据放入 VARCHAR 的字符串准备的,然后在其上打开 CURSOR。在 WHILE 循环中提取行。

CTE 生成汇总行,然后将这些行与来自 QIWS/QCUSTCDT 的详细信息行联合。摘要由 STATE 提供 BALDUE 的小计。 WHILE 循环有点毫无意义;它仅显示对行的提取和处理。唯一的操作是计算 CTE 中 不是 摘要行的行数。这与基本 table 中的行数基本相同。行数在 rowCnt OUT 参数中 returned。

源代码是 copy/pasted,但有两个来源。首先,在从已编译的存储过程生成 SQL 之后,CREATE PROCEDURE 语句取自 iNavigator 的 'Run SQL scripts' 实用程序。其次,BEGIN ... END 复合语句主体来自我输入到 iNavigator New-> Procedure 函数中的原始语句。虽然两者在逻辑上是等价的,但我想保留输入的实际行。您可以 copy/paste 将整个源代码放入 'Run SQL Scripts' 或通过实用程序创建过程,并且仅 copy/paste BEGIN ... END 复合语句在前两个选项卡中输入值后新建->程序函数。

我有一个名为 SQLEXAMPLE 的模式,我在其中构建了类似的东西。您需要调整架构和过程名称以适应您的环境。 QIWS/QCUSTCDT table 应该存在于几乎所有 AS/400-series 系统上。

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

当 CTE 在 STRSQL 中单独为 运行 时,输出的前几行如下所示:

....+....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 

摘要行应该很容易识别。当从 'Run SQL Scripts' 调用存储过程时,结果输出为:

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)

该系统上的 QIWS/QCUSTCDT table 有 12 行,与值 returned 匹配。

它与您想要的 CTE 不完全相同,但它应该证明可以使用动态 CTE。它还展示了 FETCH 如何出于任何需要的目的从 CTE 中提取行。