RPG と SQL のあれこれ (データ構造と SQLCA、定数の定義)

"Bringing the power of SQL to your RPG Program" という記事に載っていたサンプルをちょっと変更して作成してみました。

今回確認したかったのは外部データ構造、データ構造の Qualified キーワード、SQLCA の使い方、です。

D CustRow       E DS                  ExtName(QCUSTCDT) Qualified               
 *                                                                              
 /Free                                                                          
        EXEC SQL    DECLARE CustCSR  Cursor For                                 
                      SELECT * FROM QIWS/QCUSTCDT ;                             

        EXEC SQL    OPEN CustCSR ;                                              
                                                                                
    DoU %Subst(SQLSTT:1:2) = '02' ;                                             
        EXEC SQL    FETCH NEXT FROM CustCSR INTO :CustRow ;
                                                 
     If %Subst(SQLSTT:1:2) = '00' ;                                             
          DSPLY CustRow.LstNam ;                                                  
       Else ;                                                                   
          DSPLY SQLERR ;                                                          
          Leave ;                                                                 
     EndIf ;                                                                    

    EndDo ;                                                                     
                                                                                
        EXEC SQL    CLOSE CustCSR ;                                 
                                                                   
    *inlr = *on ;                                                   
    return ;                                                        
/End-Free

プログラムの作成

後述しますが、OPTION(*SYS) を指定しているため、SQLPATH パラメータの指定が *LIBL になります。
外部データ構造の展開のため、QIWS ライブラリーの中の QCUSTCDT ファイルにアクセスする必要があり、そのためには QIWS ライブラリーがライブラリー・リストの中になければならない、というわけです。

ADDLIBLE QIWS

OBJTYPE(*PGM) でプログラムまで作成しています。

"QIWS/QCUSTCDT"と指定しているので OPTION(*SYS) を指定します。
DBGVIEW(*SOURCE) は必ずしも必要なものではありません。
また、上のような単純なソースの場合は SQLCURRULE(*DB2) である必要もないかもしれませんね。(*STD と指定することによって ISO/ANSI 規格にあわせることもできます)

CRTSQLRPGI OBJ(SQLSTATE) SRCFILE(QRPGLESRC) COMMIT(*NONE) OBJTYPE(*PGM) OPTION(*XREF *SYS) SQLCURRULE(*DB2) DBGVIEW(*SOURCE)

データ構造

外部データ構造

D CustRow E DS ExtName(QCUSTCDT)

のおかげで

EXEC SQL FETCH NEXT FROM CustCSR INTO :CustRow ;

というように、いちいちカラム名を INTO 句に指定しないですむわけです。

コンパイル・リストの方で、どのように展開されているかを見てみてください。

言ってみれば、PF を F 仕様書で記述して使用するようなことが SQL テーブルについても可能になるわけです。
やはり今後の方向性としては、こういう機能を使用しながら SQL をより活用していくようにしていくのがよさそうです。

Qualified キーワード

D CustRow E DS ExtName(QCUSTCDT) Qualified

と、データ構造に対して Qualified キーワード (V5R1 以降) を追加することによって

DSPLY CustRow.LstNam ;

データ構造の中の個々の要素を xxx.zzz の形でアクセスすることができるようになっています。

SQLCA (SQL Communication Area)

カーソルの最後の行を読み込んだかどうかは、上記した参考記事では SQLSTATE で判断しています。

SQLSTATE に関しては、ソースの中で特別な指定をしなくても以下の例のように使用できます。
最初の 2桁が '02' だった場合はカーソルの最後の行である、ということのようですね。
"SQLSTT" というフィールド名を使用していますが、V5R4 以降であれば "SQLSTATE" というよりわかりやすいフィールド名を使用するべきでしょう。

    DoU %Subst(SQLSTT:1:2) = '02' ;                                             
        EXEC SQL    FETCH NEXT FROM CustCSR INTO :CustRow ;
                                                 
     If %Subst(SQLSTT:1:2) = '00' ;                                             
          DSPLY CustRow.LstNam ;                                                  
       Else ;                                                                   
          DSPLY SQLERR ;                                                          
          Leave ;                                                                 
     EndIf ;                                                                    

    EndDo ;                   

SQLSTATE、SQLCODE 等の詳細については「SQL メッセージおよびコード」「SQLSTATE クラス・コードのリスト」「SQLSTATE 値のリスト」を参照してみてください。

実際に動かしてみると、ちょっと変なところがあるのですが、まぁとりあえず。

本筋とは関係ないですが、やはりこうやってシームレスに EXEC SQL で SQL 文を取り込めると、見やすいソースになりますね。(V5R4 からのオプション)

定数の定義

'02' や '00' などの数字は、やはり後での読みやすさを考えると、↓のように定数として定義して使うのがいいでしょう。
上のソースとまったく同じ動きをします。(SQLSTT も SQLSTATE に書き換えています)

D Not_Found       C                   '02'                     
D OK              C                   '00'                     

DoU %Subst(SQLSTATE:1:2) = Not_Found ;   
    EXEC SQL    FETCH NEXT FROM CustCSR  
            INTO :CustRow ;              

 If %Subst(SQLSTATE:1:2) = OK ;          
    DSPLY CustRow.LstNam ;               
   Else ;                                
    DSPLY SQLERR ;                       
    Leave ;                              
 EndIf ;                                 

EndDo ;                                  

コンパイル・リストでの確認

外部データ構造が、テーブルの内容を見て勝手に展開してくれているのがわかりますね。

SQLCA についても、ソース自体には何の記述もないのにコンパイラーによって自動的に組み込まれていることが確認できます。

OVERLAY キーワードで、今まで使用されていた SQLCOD などのやや省略されたフィールド名が SQLCODE などのよりわかりやすい名前に変更されているのがわかりますね。V5R4 からの新機能です。

SQLSTT が SQLSTATE というフィールドでも同じことだ、ということがここから確認できます。

アクセス・プラン

コンパイルされたプログラムを PRTSQLINF コマンドで見てみると、こんなかんじになっています。
単純な SQL ですし、テーブルも小さいので、アクセス・メソッドはテーブル・スキャンになっています。

最後の行の処理について

ちなみに"ちょっと変なところ"と先に書きましたが、実は最後に空の行が表示されてしまう、ということがちょっと問題でした。
それは、以下の処理の直前の FETCH で最後の行だったことがわかった場合には '00' ではなく '02' になるために Else 以下の SQL エラー表示処理が実行されてしまい、空のエラーメッセージが表示されることが原因でした。

If %Subst(SQLSTATE:1:2) = '00' ;
DSPLY CustRow.LstNam ;
Else ;

もっといいやり方がありそうな気もしますが、改めて SQLSTATE が '02' かどうかを判別して Leave する処理を追加することで対処は可能です。

D CustRow       E DS                  ExtName(QCUSTCDT) Qualified      
 *                                                                     
 /Free                                                                 
        EXEC SQL    DECLARE CustCSR  Cursor For                        
                      SELECT * FROM QIWS/QCUSTCDT ;                    
                                                                       
        EXEC SQL    OPEN CustCSR ;                                     
                                                                       
    DoU %Subst(SQLSTATE:1:2) = '02' ;                                    
        EXEC SQL    FETCH NEXT FROM CustCSR INTO :CustRow ;            
                                                                       
     If %Subst(SQLSTATE:1:2) = '00' ;                                    
          DSPLY CustRow.LstNam ;                                       
       Else ;                                                          
        If %Subst(SQLSTATE:1:2) = '02' ;                                 
          DSPLY 'End of File!' ;                                       
          Leave ;                                                      
        EndIf ;                                                        
          DSPLY SQLERR ;                                               
         Leave ;                          
    EndIf ;                               
                                          
   EndDo ;                                
                                          
        EXEC SQL    CLOSE CustCSR ;       
                                          
    *inlr = *on ;                          
    return ;                               
 /End-Free                                 

[Top Pageに戻る]

Ads by TOK2