DB2 9.5 で SQLプロシージャ(3)

hoge3 を呼び出すプロシージャ hoge2 を作成してみます。

hoge2

hoge2は、カーソルを使ってループし、そのループの中でhoge3を呼び出します。
細々とした事は、ソースの下に書きます。

CREATE PROCEDURE hoge2
(
   IN  ic_USER_ID               VARCHAR(10)
   IN  ic_CODE_1                CHAR(2)
  ,OUT oi_SQLCODE               INT
  ,OUT oc_SQLSTATE              CHAR(5)
  ,OUT ov_MESSAGE_TEXT          VARCHAR(70)
)
  SPECIFIC hoge2
  LANGUAGE SQL
  DETERMINISTIC
  NO EXTERNAL ACTION
  MODIFIES SQL DATA
BEGIN
  /*--------------------------------------------------------*/
  /*  変数の宣言                                            */
  /*--------------------------------------------------------*/
  DECLARE vtemp           CHAR(10);
  DECLARE SQLCODE         INT         DEFAULT 0;            --SQLCODE (0:データが戻された,100:データが戻されない)
  DECLARE SQLSTATE        CHAR(5)     DEFAULT '00000';      --SQLSTATE
  DECLARE vi_SQLCODE      INT         DEFAULT 0;            --SQLCODE (0:データが戻された,100:データが戻されない)
  DECLARE vc_SQLSTATE     CHAR(5)     DEFAULT '00000';      --SQLSTATE
  DECLARE vv_MESSAGE_TEXT VARCHAR(70) DEFAULT 'NORMAL END'; --MESSAGE_TEXT

  DECLARE vc_col1         CHAR(10)   DEFAULT ' ';

  -- 条件ハンドラー用
  DECLARE vi_NOT_FOUND INT DEFAULT 0;
  /*--------------------------------------------------------*/
  /*  カーソルの宣言                                        */
  /*--------------------------------------------------------*/
  DECLARE cur_ForTable1 CURSOR WITH HOLD FOR
    SELECT
       col1
      ,col2
      ,col3
    FROM
       Table1
    WHERE CODE_1 = ic_CODE_1
    ORDER BY
       col1
      ,col2
  ;

  /*--------------------------------------------------------*/
  /*  条件ハンドラーの宣言                                  */
  /*--------------------------------------------------------*/
  DECLARE CONTINUE HANDLER FOR NOT FOUND --SQLSTATE='02000'アンサーセット無し
  BEGIN
    SET vi_NOT_FOUND = 1;
  END;
  
  DECLARE CONTINUE HANDLER FOR SQLWARNING --SQLSTATE='01***'
  BEGIN
  END;
  
  DECLARE EXIT HANDLER FOR SQLEXCEPTION --SQLSTATE='01***','02***'以外全部
  BEGIN
    SET vtemp       = SQLSTATE || CAST(SQLCODE AS CHAR(5));
    SET oc_SQLSTATE = SUBSTR(vtemp,1,5);
    SET oi_SQLCODE  = INT(SUBSTR(vtemp,6,5));
    
    CASE vc_SQLSTATE
        WHEN '21000' THEN  --スカラー全選択、SELECT INTOの結果が複数行になった
          SET vv_MESSAGE_TEXT = 'hoge2 : TOO MANY ROWS' || ' : ' || vc_DOCUMENT_ID;
          
        WHEN '42704' THEN  --未定義の名前
          SET vv_MESSAGE_TEXT = 'hoge2 : UNDEFINED NAME' || ' : ' || vc_DOCUMENT_ID;
          
        WHEN '22012' THEN  --ゼロ除算
          SET vv_MESSAGE_TEXT = 'hoge2 : DIVISION BY ZERO' || ' : ' || vc_DOCUMENT_ID;
          
        WHEN '23505' THEN  --ユニーク制約違反
          SET vv_MESSAGE_TEXT = 'hoge3 : VIOLATION FOR UNIQUE CONSTRAINT' || ' : ' || vc_DOCUMENT_ID;
          
        WHEN '23502' THEN  --NULL制約違反
          SET vv_MESSAGE_TEXT = 'hoge2 : VIOLATION FOR NOT NULL CONSTRAINT' || ' : ' || vc_DOCUMENT_ID;
          
        ELSE
          SET vv_MESSAGE_TEXT = 'hoge2 : OTHERS EXCEPTION' || ' : ' || vc_DOCUMENT_ID;
        
    END CASE;
    
    -- ログ出力
    CALL LOG_hoge(
            'hoge2'
           ,oi_SQLCODE
           ,oc_SQLSTATE
           ,vv_MESSAGE_TEXT
         );
    
  END;
  
  /*--------------------------------------------------------*/
  /*  ステートメント                                        */
  /*--------------------------------------------------------*/
  SET vv_MESSAGE_TEXT = 'cur_ForTable1';
  SET vi_NOT_FOUND         = 0;
  OPEN cur_ForTable1;
  ForTable1:
  LOOP
    FETCH FROM cur_ForTable1
      INTO
         vc_col1
      ;

    IF vi_NOT_FOUND = 1 THEN
      LEAVE ForTable1;
    ELSE
      CALL hoge3(
              vc_DOCUMENT_ID
             ,vc_col1
             ,vi_SQLCODE
             ,vc_SQLSTATE
           );
    END IF;
  END LOOP ForTable1;
  CLOSE cur_ForTable1;
  
  SET oc_SQLSTATE     = vc_SQLSTATE;
  SET oi_SQLCODE      = vi_SQLCODE;
  SET ov_MESSAGE_TEXT = vv_MESSAGE_TEXT;
  COMMIT;
  
END
@

カーソル使うので、行を取得できなかった時の処理も入れてます。
こういう時、PL/SQLならば、%NOTFOUNDを使って、FETCHの後にEXIT WHEN カーソル%NOTFOUND が使えるのですが、DB2 9.5 SQLプロシージャはそんなうまいものがなさそうでした。実際どのような流れになるか、大まかに書いてみましょう。

まず、「OPEN cur_ForTable1」でカーソルをオープンし、ループ開始後、「FETCH FROM cur_ForTable1 INTO vc_col1」でカーソルから取り出した行(今回は値1つ)を 「vc_col1」にセットします。ここで行取得が出来ない場合は、「SQLSTATE='02000'アンサーセット無し」が発生します。

すると制御は、条件ハンドラー部の「HANDLER FOR NOT FOUND」に遷移します。
カーソルから取得できないだけでは、エラーとしたくなかったので、「CONTINUE HANDLER FOR NOT FOUND」にして、条件ハンドラー部を処理した後は、処理が継続するようにしました。また、カーソルをループを抜けてくれるよう、フラグ(vi_NOT_FOUND)に 1 をセットしています。

この、条件ハンドラー部を処理した後は、「IF vi_NOT_FOUND = 1 THEN」に処理が遷移してきます。そして、「vi_NOT_FOUND = 1」なので、「LEAVE ForTable1」してループを抜けます。

hoge3

次にhoge3を作成します。hoge3は、hoge2から受け取ったDOC_IDを元に、table2の内容を更新します。ただし今回はUPDATEではなく、DELETE-INSERT方式としました。DOC_IDのレコードが存在しない場合は、UPDATEではなくINSERTになってしまうので。

注意:上記がベストであるとは思っていません。他にも検討の余地はありますが今回はこれで。例えば、「とりあえずINSERTしてデュプってたらUPDATE」や「MERGE」や「前もってCOUNT(DOC_ID)してゼロか否か」など色々です。

CREATE PROCEDURE hoge3
(
   IN  ic_DOC_ID                VARCHAR(16)
  ,IN  ic_col1                  CHAR(2)
  ,OUT oi_SQLCODE               INT
  ,OUT oc_SQLSTATE              CHAR(5)
)
  SPECIFIC hoge3
  LANGUAGE SQL
  DETERMINISTIC
  NO EXTERNAL ACTION
  MODIFIES SQL DATA
BEGIN
  /*--------------------------------------------------------*/
  /*  変数の宣言                                            */
  /*--------------------------------------------------------*/
  DECLARE vtemp           CHAR(10);
  DECLARE SQLCODE         INT         DEFAULT 0;            --SQLCODE (0:データが戻された,100:データが戻されない)
  DECLARE SQLSTATE        CHAR(5)     DEFAULT '00000';      --SQLSTATE
  DECLARE vi_SQLCODE      INT         DEFAULT 0;            --SQLCODE (0:データが戻された,100:データが戻されない)
  DECLARE vc_SQLSTATE     CHAR(5)     DEFAULT '00000';      --SQLSTATE
  DECLARE vv_MESSAGE_TEXT VARCHAR(70) DEFAULT 'NORMAL END'; --MESSAGE_TEXT
  
  DECLARE oc_DOC_ID        CHAR(16)  DEFAULT ' ';
  DECLARE oc_col33         CHAR(10)  DEFAULT ' ';
  
  /*--------------------------------------------------------*/
  /*  カーソルの宣言                                        */
  /*--------------------------------------------------------*/
  /*--------------------------------------------------------*/
  /*  条件ハンドラーの宣言                                  */
  /*--------------------------------------------------------*/
  DECLARE CONTINUE HANDLER FOR NOT FOUND --SQLSTATE='02000'アンサーセット無し
  BEGIN
    SET vtemp       = SQLSTATE || CAST(SQLCODE AS CHAR(5));
    SET oc_SQLSTATE = SUBSTR(vtemp,1,5);
    SET oi_SQLCODE  = INT(SUBSTR(vtemp,6,5));
  END;
  
  DECLARE CONTINUE HANDLER FOR SQLWARNING --SQLSTATE='01***'
  BEGIN
  END;
  
  DECLARE EXIT HANDLER FOR SQLEXCEPTION --SQLSTATE='01***','02***'以外全部
  BEGIN
    SET vtemp       = SQLSTATE || CAST(SQLCODE AS CHAR(5));
    SET oc_SQLSTATE = SUBSTR(vtemp,1,5);
    SET oi_SQLCODE  = INT(SUBSTR(vtemp,6,5));
    GET DIAGNOSTICS EXCEPTION 1 vv_MESSAGE_TEXT = MESSAGE_TEXT;
    
    IF vv_MESSAGE_TEXT IS NULL
    THEN
      CASE vc_SQLSTATE
        WHEN '21000' THEN  --スカラー全選択、SELECT INTOの結果が複数行になった
          SET vv_MESSAGE_TEXT = 'hoge3 : TOO MANY ROWS' || ' : ' || vc_DOCUMENT_ID;
          
        WHEN '42704' THEN  --未定義の名前
          SET vv_MESSAGE_TEXT = 'hoge3 : UNDEFINED NAME' || ' : ' || vc_DOCUMENT_ID;
          
        WHEN '22012' THEN  --ゼロ除算
          SET vv_MESSAGE_TEXT = 'hoge3 : DIVISION BY ZERO' || ' : ' || vc_DOCUMENT_ID;
          
        WHEN '23505' THEN  --ユニーク制約違反
          SET vv_MESSAGE_TEXT = 'hoge3 : VIOLATION FOR UNIQUE CONSTRAINT' || ' : ' || vc_DOCUMENT_ID;
          
        WHEN '23502' THEN  --NULL制約違反
          SET vv_MESSAGE_TEXT = 'hoge3 : VIOLATION FOR NOT NULL CONSTRAINT' || ' : ' || vc_DOCUMENT_ID;
          
        ELSE
          SET vv_MESSAGE_TEXT = 'hoge3 : OTHERS EXCEPTION' || ' : ' || vc_DOCUMENT_ID;
          
      END CASE;
    ELSE
      SET vv_MESSAGE_TEXT = vv_MESSAGE_TEXT || ' : ' || vc_DOCUMENT_ID;
    END IF;
  
    -- ログ出力
    CALL LOG_hoge(
            'hoge3'
           ,oi_SQLCODE
           ,oc_SQLSTATE
           ,vv_MESSAGE_TEXT
         );
  END;
  
  /*--------------------------------------------------------*/
  /*  ステートメント                                        */
  /*--------------------------------------------------------*/
  SET oc_DOC_ID = ic_DOC_ID;
  
  DELETE FROM table2
   WHERE DOC_ID = ic_DOC_ID
  ;
  
  SELECT
     col33
  INTO
     oc_col33
  FROM
  WHERE col1 = ic_col1
  ;
  
  INSERT INTO
     table2
  VALUES (
     oc_DOC_ID
    ,oc_col33
  )
  ;
  
  COMMIT;
  
END
@

ポイントは、DELETE-INSERT方式。DELETEする時に、存在しない場合、「SQLSTATE='02000'アンサーセット無し」が発生するので、hoge2同様、条件ハンドラー部で「CONTINUE HANDLER FOR NOT FOUND」としました。これが後々不幸な結果をもたらす事になろうとは、考えもしませんでした。
時間も遅いので今日はここまで。