カテゴリー別アーカイブ: 例外処理

例外処理(エラー処理)(SQL Server編)

ITコーディネータのシュウです。

IMG_3964

先日、東京駅のすぐそばのJPタワー・KITTE 4FのHall&Conferenceで行われたSecurity Days 2015のセミナーに、今年も参加してきました。目的はITコーディネータのポイント取得を兼ねてセキュリティに関する最新の情報収集。相変わらず大きな問題である標的型攻撃対策、内部犯行を含めての情報漏洩対策など、様々な内容が盛りだくさん。その中で、2015年10月から始まるマイナンバー制度や、個人情報保護法改正に向けた内容など、いろいろと気になる点もありました。「個人情報の保護レベルを世界水準に合わせよう」ということを指摘されている、新潟大学の鈴木正朝先生の講演が、私としては結構インパクトがありましたね。日本よりも規制の厳しい欧米の水準に合わせないと、ゲノムなど含めて世界の情報を日本に集めることができなり、産業的に大きな問題になるということを言っておられ、説得力がありました。

写真は、そのビルの屋上庭園「KITTEガーデン」から撮った東京駅の写真です。まだ風がちょっと寒かった!

<本日の題材>
例外処理(エラー処理) (SQL Server編)

前回前々回は、ORACLEの例外処理について取り上げました。
今回は、SQL Serverの例外処理について、見てみます。

SQLServerのTransact-SQLでは、SQLServer2005以降において例外処理がサポートされています。記述の仕方は MicrosoftのVisual C#、Visual C++ 言語での例外処理に似ていて、Transact-SQL ステートメントのグループを TRY ブロックで囲み、TRY ブロック内でエラーが発生すると、CATCH ブロックで囲まれた別のステートメントのグループに制御が渡されるというものです。

構文は以下:

BEGIN TRY
  (処理ロジック)
END TRY
BEGIN CATCH
  (ERROR 処理ロジック)
END CATCH

下記の例は、「STEP 1」表示の後、INSERT文でデータを登録するときに、桁あふれのエラーにより処理が中断され、「STEP 2」を表示することなく、エラーがCATCHされてエラー処理(エラーメッセージの表示)が実行されることを示します。

BEGIN TRY
  SELECT 'STEP 1'
  INSERT INTO dbo.社員マスタ(社員番号,社員名,拠点)
  VALUES(123456, 'TEST_MENBER', 'シカゴ');

  SELECT 'STEP 2'
END TRY
BEGIN CATCH
  SELECT
             ERROR_NUMBER() AS エラー番号
           , ERROR_SEVERITY() AS エラー重大度
           , ERROR_MESSAGE() AS エラーメッセージ
           , ERROR_LINE() AS エラー行
END CATCH
GO

tsql_例外

ここで、エラーに関する情報は、”ERROR_” で始まるシステム関数で取得することができます。
ERROR_NUMBER()         --            エラー番号
ERROR_MESSAGE()       --            エラーメッセージ
ERROR_SEVERITY()       --            エラーの重大度レベル
ERROR_STATE()               --            エラーの状態番号
ERROR_LINE()                   --            エラーが発生した行番号
ERROR_PROCEDURE()  --            エラーが発生したストアドプロシージャ                                                                         またはトリガー名

※エラーの重大度レベルについては、以下のMSのHPを参照
  https://msdn.microsoft.com/ja-jp/library/ms164086.aspx

ただし、この TRY ~ CATCH ステートメントは、以下の種類のエラーはCATCHできないということです。
・構文エラーなどのコンパイルエラー
・再コンパイルで発生するエラー(コンパイル後の名前の遅延解決により発生するオブジェクト名の解決エラーなど)

以下の例は、実際には存在しないテーブルを抽出するSQLを実行してコンパイルエラーが発生するケースで、このときにはTRY ~ CATCHではキャッチされず、実行したアプリ(例えばSQLServer Management Studio)にエラーが返されます。

BEGIN TRY
    PRINT N'処理の実行開始';
    SELECT * FROM 存在しないテーブル;
END TRY
BEGIN CATCH
    SELECT
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO
tsql_catchしない

この種のエラーは、エラーの発生場所と同一の実行レベルの TRY...CATCH 構造では処理されません。上記のような場合でもエラー処理をするためには、エラーが発生するコードを TRY ブロック内部の独立したバッチで実行、すなわち、ストアドプロシージャに記述するか、sp_executesql を使用して動的な Transact-SQL ステートメントを実行するかたちにすることで、低いレベルで発生したエラーをTRY...CATCH 構造でCATCHできるということです。以下が動的SQLを使ったその例になります。

BEGIN TRY
    PRINT N'処理の実行開始';
        DECLARE @sql VARCHAR(4000)
        SET @sql = 'SELECT * FROM 存在しないテーブル'
        EXEC(@sql)
END TRY
BEGIN CATCH
    SELECT
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO
tsql_catchできる動的sql

実行結果は、上記のようにエラーをCATCHして、BEGIN CATCHのエラー処理が実行されています。

今日は以上まで

にほんブログ村 IT技術ブログへ
にほんブログ村

例外処理(エラー処理)(ORACLE編2)

ITコーディネータのシュウです。

1423578385215

娘がディズニーランドに行ってきて、撮ってきた写真です。
昨年、社会現象にもなったアナと雪の女王をテーマにした、スペシャルイベント「アナとエルサのフローズンファンタジー」。入口のところで大きく宣伝してたのを帰る時に撮ったと言ってました。シンデレラ城を舞台にしたプロジェクションマッピング「ワンス・アポン・ア・タイム~スペシャルウィンターエディション」は座席指定券をもらう抽選に当たらなかったので、いい席では見れなかったと残念がってました。

4月からチケットが少し値上がりしてしまうのは残念だけど、それでも何度でも行ってみたくなる魅力がありますよね!

さて、話は変わりますが、dbSheetClientのユーザー事例に紀文食品様が載りました。おでん用の商品などに「紀文」の焼印が押されていることでも有名な、練り製品ではトップシェアを持つ会社ですね。
dbSheetClient
で念願の「品質検査管理システム」を構築したとのこと。詳しくは、こちらを参照してみてください。
http://www.newcom07.jp/dbsheetclient/usrvoice/usrcase8.html

<本日の題材>
例外処理(エラー処理) (Oracle編2)

前回は、ORACLEの例外処理についての一般的記述方法や制御について取り上げてみました。今回は、ORACLEの例外の種類とそれに応じた記述方法について、見てみます。
例外は内部例外とユーザー定義例外にわけられ、内部例外はさらに事前定義の内部例外と無名の内部例外に分けられます。

事前定義の内部例外には、前回の例で示した
・数値データをゼロで割ろうとした「ZERO_DIVIDE」例外(ORA-01476)
・SELECT INTO文で複数の行を戻したときの「TOO_MANY_ROWS」例外(ORA-01422)
・検索の結果1行も戻されない「NO_DATA_FOUND」例外(ORA-01403)
などいろいろあります。

こういったものは、それぞれに名前が定義されているため、例外処理部で個別に処理を指定することができますが、名前が付けられていない無名の内部例外については、宣言部で明示的に名前を付けてあげ、さらに PRAGMA EXCEPTION_INITでORACLEのエラーと例外名を関連付ける必要があります。

例)
DECLARE
  KETA_ERR EXCEPTION;
  PRAGMA EXCEPTION_INIT(KETA_ERR, -01438);
BEGIN
  INSERT INTO DEPT(DEPTNO, DNAME) VALUES(501, '企画部');
  COMMIT;
EXCEPTION
  WHEN KETA_ERR THEN
     DBMS_OUTPUT.PUT_LINE('桁数が大きすぎます');
  WHEN OTHERS THEN
     DBMS_OUTPUT.PUT_LINE(sqlcode);
     DBMS_OUTPUT.PUT_LINE(sqlerrm);
END;
/
plsql_ユーザ定義例外0

これは、DEPT表にデータを登録する際、DEPTNOは2桁の数値でなければならないところ、3桁を登録しようとしたときに出る無名の内部例外、「ORA-01438:この列に許容される指定精度より大きな値です」に名前を付けて、それを例外処理部で使っている例です。

なお、OTHERSハンドラを使用すると、例外処理部で例外名が指定されていないすべての例外を処理することができるため、上記の例では「KETA_ERR」例外以外の例外が発生した場合には、OTHERSハンドラの処理が行われるようになります。
そのときにどのような例外が発生したのかを確認するのに、SQLCODE関数(エラー番号を戻す関数)、SQLERRM関数(エラーメッセージを戻す関数)のようなエラー報告関数をすると便利です。

さて、ユーザー定義例外というのは、ORACLEのエラーではなく、ユーザーが作成する例外になります。

下の例は、FOR LOOP文の繰り返しの中で、ある数量をマイナスしていき、その数値が10未満になった場合には、ユーザー定義例外を呼出して、エラーとして処理するというものです。

例)
DECLARE
  Amount  NUMBER;
  Amount_ERR EXCEPTION;                    -- ユーザー定義例外の宣言
BEGIN
  Amount := 20;
  FOR lc IN 1..30 LOOP
    Amount := Amount – 1;
    IF Amount < 10 THEN
       RAISE Amount_ERR;                     -- ユーザー定義例外の呼出し
    END IF;
  END LOOP;
EXCEPTION
  WHEN Amount_ERR THEN
     DBMS_OUTPUT.PUT_LINE(‘ユーザー定義例外発生: 数量 = ‘||Amount);
END;
/
plsql_ユーザ定義例外1

また、ユーザーが独自に「ORA-xxxx」形式のエラーコードとエラーメッセージを定義して、エラーを表示させるような場合には、RAISE_APPLICATION_ERROR を使用します。
このときにユーザー定義のエラーとして使用が許可されている番号は ORA-20000~ORA-20999 までです。

例)
  RAISE_APPLICATION_ERROR(-20001, 'エラーが発生しました');

また、例外が発生して処理が例外処理部に移ると、制御は実行部には戻らないため、処理の途中でブロックは終了してしまいます。しかし、その後も処理を継続したい場合には、ブロックのネストを使用することで可能になります。

例)
DECLARE
  Amount  NUMBER;
  Amount_ERR EXCEPTION;
BEGIN
  Amount := 20;
  FOR lc IN 1..10 LOOP
    BEGIN                                       -- ブロックのネスト
      Amount := Amount - 2;
      IF Amount = 10 THEN
        RAISE Amount_ERR;
      ELSE
        DBMS_OUTPUT.PUT_LINE('Amount = '||Amount);
      END IF;
    EXCEPTION                                  -- ネストされたブロック内での例外処理
      WHEN Amount_ERR THEN
      DBMS_OUTPUT.PUT_LINE('ユーザー定義例外発生:数量 = '||Amount);
    END;
  END LOOP;
END;
/
plsql_ユーザ定義例外ブロックネスト

上記は、Amountの値が10になった時点で例外が発生していますが、ネストされたブロックの中での例外のため、その後のLOOPの処理が継続されていることが確認できます。

今日は以上まで

にほんブログ村 IT技術ブログへ
にほんブログ村

例外処理(エラー処理) (Oracle編)

ITコーディネータのシュウです。

DSC_2080_2

前回に引き続き、加須花崎水上公園で撮った写真です。用水路に鳥が泳いでいました。おそらくカルガモだと思います。スイスイーと気持ちよさそうに泳いでました。たまに行列を作って行進したりする姿も見かけますよね。

そういえば、「鴨の水搔き」という言葉がありますが、《気楽そうに浮かんでいる鴨も、水面下では水かきを絶えず動かしているところから》人知れない苦労があることのたとえで使われる言葉なんですよね。
見習うところも多いかもしれませんね。

<本日の題材>
例外処理(エラー処理) (Oracle編)

今回は、処理の途中で例外(エラー)が発生した場合の処理を記述する例外処理(エラー処理)について取り上げてみたいと思います。ORACLEのPL/SQL とSQL ServerのTransact SQLでは記述の仕方が異なるため、今回はまずORACLEについて見てみたいと思います。

PL/SQLでは、実行時のエラーや警告のことを例外と言いますが、もし、処理の途中で例外が発生したときに、PL/SQLブロックに実行部しかなかった場合、PL/SQLは異常終了(OS側に制御が戻る)します。

例えば、ある商品について、前年の出荷実績に対する今年の予算の比率を計算で出す処理をPL/SQLで記述した場合、新商品のためなどの理由で前年の出荷実績が 0 であった場合には、下記のようになります。

DECLARE
  Yosan  NUMBER := 12000;
  Zennen_Jisseki  NUMBER := 0;
  Tai_Zennenhi NUMBER;
BEGIN
  Tai_Zennenhi := 100.0 * Yosan / Zennen_Jisseki;
  DBMS_OUTPUT.PUT_LINE('予算/前年実績 = ' || Tai_Zennenhi||'%');
END;
/

上記を実行すると(SQL Plus)、下記のような ORA-01476 のエラーが出て異常終了します。これは数値を0で割ろうとしたときに出るエラーです。
plsql_err

これについて、下記のように例外処理部を追加してエラーのときの対応を明記することで、異常終了させずに正常終了させることができます。例外処理部は、BEGIN ~ END; の間に、EXCEPTION句を入れて、EXCEPTION ~ END; の間に例外処理を記述します。

DECLARE
  Yosan  NUMBER := 12000;
  Zennen_Jisseki  NUMBER := 0;
  Tai_Zennenhi NUMBER;
BEGIN
  Tai_Zennenhi := 100.0 * Yosan / Zennen_Jisseki;
  DBMS_OUTPUT.PUT_LINE('予算/前年実績 = ' || Tai_Zennenhi||'%');
EXCEPTION
  WHEN ZERO_DIVIDE THEN
      DBMS_OUTPUT.PUT_LINE('0で数値を割ろうとしています');
      Tai_Zennenhi := NULL; 
  WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE('他のエラーが発生しました');
      Tai_Zennenhi := NULL;
END;
/
plsql_例外処理JPG

上記は、予算を前年実績の0値で割ろうとしたときに、「ZERO_DIVIDE」という例外が発生し、EXCEPTION以下の例外処理部に処理が移り、「0で数値を割ろうとしています」というメッセージを出して処理を終了しています。

例外が発生したときの制御は以下のようになります。

plsql_例外処理制御

 ただ、上記のように 0 で割る可能性があらかじめわかっている場合は、処理1のところにエラーチェックを行うことで例外を回避できます。

DECLARE
  Yosan  NUMBER := 12000;
  Zennen_Jisseki  NUMBER := 0;
  Tai_Zennenhi NUMBER;
BEGIN
  Tai_Zennenhi := CASE Zennen_Jisseki WHEN 0 THEN NULL
                                             ELSE 100.0 * Yosan / Zennen_Jisseki END;

  IF Tai_Zennenhi IS NOT NULL THEN
    DBMS_OUTPUT.PUT_LINE('予算/前年実績 = ' || Tai_Zennenhi||'%');
  ELSE
    DBMS_OUTPUT.PUT_LINE('前年実績はありませんでした');
  END IF;
 
EXCEPTION
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE('他のエラーが発生しました');
      Tai_Zennenhi := NULL;
END;
/

plsql_例外処理回避

 この場合、予算の対前年実績比率を出す際に、CASE文で 0 で割るのではなく NULLに設定するかたちにしており、例外処理は発生していません。

今日は以上まで

にほんブログ村 IT技術ブログへ
にほんブログ村