データベース千夜一夜第30回

ストアドプロシージャ(4)~繰り返し処理 長谷川裕行
有限会社 手國堂

処理構造を整理する

SQLで提供されている制御構造は、Visual BasicやC++など一般的なプログラミング言語に比べて機能がシンプルなため、うまく整理しないとソースを読み取りにくくなる場合があります。条件判断と分岐の構造を読みやすくしてみましょう。


- 出口が多いと読みにくい -

前回の最後に「If~Else」の入れ子構造のサンプルとして、サーバー側のエラー発生時にエラー番号を返す@@ERROR関数を使う方法を紹介しました(ex01.sql~前回のex09.sqlと同じ内容です)。

Create Procedure YearlyTransfer3

As

SELECT * INTO 累積売上_old FROM 累積売上_dmy

If @@ERROR = 0 ----------- コピーが正常終了した場合

Begin

DELETE 累積売上_dmy ---- 削除処理を実行

If @@ERROR = 0 --------- 削除も正常終了したら

Begin ------------------ 処理終了

Print '処理は完了しました。'

Return 0 ------------- ★

End

Else ------------------- 削除がエラーの場合

Begin ------------------ メッセージを表示して終了

Print '削除操作でエラーが発生しました。'

Return @@ERROR ------- ★

End

End

Else --------------------- コピーがエラーの場合

Begin -------------------- メッセージを表示して終了

Print 'コピー操作でエラーが発生しました。'

Return @@ERROR --------- ★

End

★マークの箇所でReturn命令を使ってエラー番号(@@ERRORの戻り値)または0を返しています。しかし、1つのプロシージャ内にReturn命令が複数存在するのは、あまりよい構造とは言えません。

なぜなら、処理ブロック(この場合はプロシージャ)内に出口(プロシージャを抜ける命令)がたくさんあることで、処理の流れが読み取りにくくなるからです。


- 制御構造は見通しやすく -

Visual BasicのForやWhileで繰り返し構造(ループ)を作った場合を考えてみましょう。ループを抜けるための条件(脱出条件)があちこちに書かれていると、どのようなときにループを抜けて次の処理に移るのか見通しにくくなります。

次のリストはその典型例です。Forループの中にIfによる条件判定が複数あり、その中で変数などの状態を調べて(ループの繰り返し条件であるカウンタ変数の値とは無関係に)、あるときはループを抜け、またあるときはカウンタ変数の値を増加してループを繰り返す……という形で、どうにも美しい構造ではありません。

For intCount = 0 To __MAX_COUNT

........(処理)........

If chkLimitOn.Checked = True Then

Exit For ------------- ここで抜ける

Else

Next intCount -------- ここでは次のループへ

........(処理)........

If lngLineNum >= __MAX_LINE Then

Exit For ------------- ここでも抜ける

Else

Next intCount -------- ここでも次のループへ

........(処理)........

Next intCount ------------ 本当のループの終端


- 出口を1つにまとめる -

そこで、“YearlyTransfer3”の「エラーを判定してはその都度@@ERRORを呼び出し、Return命令でエラーコードを返す」という形を見直し、出口を1つにしてみましょう。@@ERROR関数の返す値は直前に実行された命令の結果ですから、変数を用意してエラー番号を保存し、処理の最後でその値を返すようにします。

変数への代入処理が増えるためソースは長くなりますが、流れは見通しやすくなります(ex02.sql)。

Create Procedure YearlyTransfer4

As

DECLARE @ErrCode int ---------- エラー番号を保存する変数を宣言

SELECT * INTO 累積売上_old FROM 累積売上_dmy

If @@ERROR = 0

Begin

DELETE 累積売上_dmy

If @@ERROR = 0

Begin

Print '処理は完了しました。'

Set @ErrCode = 0 ---------- エラー番号を変数に保存

End

Else

Begin

Print '削除操作でエラーが発生しました。'

Set @ErrCode = @@ERROR ---- エラー番号を変数に保存

End

End

Else

Begin

Print 'コピー操作でエラーが発生しました。'

Set @ErrCode = @@ERROR ------ エラー番号を変数に保存

End

Return @ErrCode ----------- 最後に変数の値(エラー番号)を返す


- コメント記号 -

さて、ここまでソースコードが長くなってくると、どの行で何をしているのかが把握しづらくなります。そんなときはコメントを使い、各行で何をしているかをコメントとして記述しておくと便利です。

コメントには--または/* */を使います。--は1行コメントで、--から行末(改行)までは、何を書いても処理対象とみなされません。

複数行のコメントは/**/で囲みます。--はSQL-92準拠のコメント記号ですが、/*~*/はTRANSACT-SQL独自のコメント記号です。

コメントを挿入すると、先に紹介した“YearlyTransfer4”は以下のようになります(ex03.sql)。クエリアナライザでは、SQLキーワード、関数、文字列、コメントがそれぞれ色分けされて表示されるため、コメント記号の入力を間違えるとすぐに分かります。

Create Procedure YearlyTransfer5

As

/*

途中でエラーが発生したら、メッセージを表示して

エラー番号を返す。

*/

-- エラー番号を保存する変数を宣言

DECLARE @ErrCode int

SELECT * INTO 累積売上_old FROM 累積売上_dmy

If @@ERROR = 0

Begin

DELETE 累積売上_dmy

If @@ERROR = 0

Begin

Print '処理は完了しました。'

Set @ErrCode = 0 -- エラー番号を変数に保存

End

Else

Begin

Print '削除操作でエラーが発生しました。'

Set @ErrCode = @@ERROR -- エラー番号を変数に保存

End

End

Else

Begin

Print 'コピー操作でエラーが発生しました。'

Set @ErrCode = @@ERROR -- エラー番号を変数に保存

End

-- 最後に変数の値(エラー番号)を返す

Return @ErrCode





トップページ
処理構造を整理する
出口が多いと読みにくい
制御構造は見通しやすく
出口を1つにまとめる
コメント記号
繰り返し処理(1)~Whileの基本機能
繰り返し処理(2)~脱出と継続
あとがき
Copyright © MESCIUS inc. All rights reserved.