<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES--> DB2 提供的强大功能可以让开发人员创建出非常高效稳定的存储过程。但对于初学者来说,开发出这样的程序并不容易。本文主要讨论开发高效稳定的 DB2 存储过程的一些常用技巧和方法。
读者定位为具有一定开发经验的 DB2 开发经验的开发人员。
读者可以从本文学习到如何编写稳定、高效的存储过程。并可以直接使用文章中提供的 DB2 代码,从而节省他们的开发和调试时间,提高效率。
本文以 DB2 开发人员的角度介绍了在 DB2 存储过程开发中需要注意的事项和技巧。新手如果能够按照本文介绍的最佳实践来开发存储过程,可以避免一些常见的错误,从而编写出高效的程序。本文从初始化参数、游标、异常处理、临时表的使用以及如何寻找并 rebind 非法存储过程等常见问题进行了着重讨论,并且给出了示例代码。
在存储过程中,开发人员能够声明和设置 SQL 变量、实现流程控制、处理异常、能够对数据进行插入、更新或者删除。同时,客户应用(这里指调用存储过程的应用程序,它可以是 JDBC 的调用,也可以是 ODBC 和 CLI 等)和存储过程之间可以传递参数,并且从存储过程中返回结果集。其中,使用 SQL 编写的 DB2 存储过程是在开发中常见的一种存储过程。本文主要讨论此类存储过程。
最佳实践 1:在创建存储过程语句中提供必要的参数
创建存储过程语句(CREATE PROCEDURE)可以包含很多参数,虽然从语法角度讲它们不是必须的,但是在创建存储过程时提供它们可以提高执行效率。下面是一些常用的参数 容许 SQL (allowed-SQL)
容许 SQL (allowed-SQL)子句的值指定了存储过程是否会使用 SQL 语句,如果使用,其类型如何。它的可能值如下所示:
SET poGenStatus = 0;
SET piName = RTRIM(COALESCE(piName, ''));
SET piRank = COALESCE(piRank, 0);
-- make sure all required input parameters are not null
IF ( piNum IS NULL
OR piName = ''
OR piAge IS NULL )
THEN
SET poGenStatus = 34100;
RETURN poGenStatus;
END IF;
CREATE PROCEDURE getPeople(IN piAge INTEGER)
DYNAMIC RESULT SETS 2
READS SQL DATA
LANGUAGE SQL
BEGIN
DECLARE rs1 CURSOR WITH RETURN TO CLIENT FOR
SELECT name, age FROM person
WHERE age<piAge;
DECLARE rs2 CURSOR WITH RETURN TO CALLER FOR
SELECT NAME, age FROM person
WHERE age>piAge;
OPEN rs1;
OPEN rs2;
END
代码中rs1游标的DECLAER语句中包含WITH RETURN TO CLIENT子句,表示结果集返回给客户应用(CLIENT)。rs2游标的DECLARE语句中包含WITH RETURN TO CALLER子句,表示结果集返回给调用者(CALLER)。
游标返回给调用者(CALLER)表示由存储过程的调用者接收结果集,而不考虑调用者是否是另一个存储过程,还是客户应用。图(1)中存储过程PROZ如果声明为WITH RETURN TO CALLER,那么结果集会返回给存储过程PROY,Client Application是不会得到PROZ返回的结果集的。
图1:存储过程递归调用
游标返回给客户应用(CLIENT)表示由发出最初 CALL 语句的客户应用接收结果集,即使结果集由嵌套层次中的 15 层深的嵌套存储过程发出也是如此。图1中存储过程 PROZ 如果声明为 WITH RETURN TO CLIENT,那么结果集会返回给 Client Application。返回给客户应用(CLIENT)的游标声明是我们经常使用的,也是默认的结果集类型。
在声明返回类型时,我们要认真考虑一下,我们需要把结果集返回给谁,以免丢失返回集,导致程序错误。
-- Generic Handler
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION, SQLWARNING, NOT FOUND
BEGIN NOT ATOMIC
-- Capture SQLCODE & SQLSTATE
SELECT SQLCODE, SQLSTATE
INTO hSqlcode, hSqlstate
FROM SYSIBM.SYSDUMMY1;
-- Use the poGenStatus variable to tell the procedure -- what type of
error occurred
CASE hSqlstate
WHEN '02000' THEN
SET poGenStatus=5000;
WHEN '42724' THEN
SET poGenStatus=3;
ELSE
IF (hSqlCode < 0) THEN
SET poGenStatus=hSqlCode;
END IF;
END CASE;
END;
上面的异常处理器会在出现SQLEXCEPTION, SQLWARNING, NOT FOUND异常的时候触发。异常处理器会取出当前的SQLCODE, SQLSTATE,然后根据它们的值来设置输出参数(poGenStatus)的值。
我们还可以定制一些异常处理器。例如,我们可以定义一些对参数进行初始化的异常处理器。这里,异常处理器可以看作是一个供存储过程自己调用的内部函数。下面是这种情况的一个例子:
清单8:供存储过程自己调用的内部函数
-----------------------------------------------------
-- CONDITION declaration
-----------------------------------------------------
-- (80100~80199) SQLCODE & SQLSTATE
DECLARE sqlReset CONDITION for sqlstate '80100';
-----------------------------------------------------
-- EXCEPTION HANDLER declaration
-----------------------------------------------------
-- Handy Handler
DECLARE CONTINUE HANDLER FOR sqlReset
BEGIN NOT ATOMIC
SET hSqlcode = 0;
SET hSqlstate = '00000';
SET poGenStatus = 0;
END;
…………
-----------------------------------------------------
-- Procedure Body
-----------------------------------------------------
SIGNAL sqlreset;
-- insert the record
…………
-----------------------------------------------------
-- TEMPORARY TABLE & CURSOR declaration
-----------------------------------------------------
DECLARE GLOBAL TEMPORARY TABLE SESSION.TEMP
(
ID INTEGER,
NAME CHAR(30)
)
--WITH REPLACE
NOT LOGGED;
P2: BEGIN
DECLARE R_CRSR CURSOR WITH RETURN TO CLIENT FOR
SELECT * FROM SESSION.TEMP
FOR READ ONLY;
INSERT INTO SESSION.TEMP VALUES(1,piName);
OPEN R_CRSR;
END P2;
SELECT
RTRIM(r.routineschema) || '.' || RTRIM(r.routinename) AS spname ,
' ( '|| RTRIM(r.routineschema) || '.' || 'P'||SUBSTR(CHAR(r.lib_id+10000000),2)||' )'
FROM
SYSCAT.routines r
WHERE
r.routinetype = 'P'
AND ((r.origin = 'Q' AND r.valid != 'Y')
OR EXISTS (
SELECT 1 FROM syscat.packages
WHERE pkgschema = r.routineschema
AND pkgname = 'P'||SUBSTR(CHAR(r.lib_id+10000000),2)
AND valid !='Y'
)
)
ORDER BY
spname;