.Net(VB C#) LINQ Aggregateを使用して集計する

LINQのAggregateを使用して、リストの値を集計します。

いつものごとく
テスト用の高級果物クラスです。
【C#】
private class Fruit
{
    public string Name { get; set; }
    public string Rank { get; set; }
    public decimal Price { get; set; }
}
【VB#】
Private Class Fruit
    Public Property Name As String
    Public Property Rank As String
    Public Property Price As Decimal
End Class
テストデータを作成します。
【C#】
var fruits = new List<Fruit>()
                {
                    new Fruit(){Name = "りんご", Rank = "A" , Price = 1000 },
                    new Fruit(){Name = "みかん", Rank = "A" , Price = 600 },
                    new Fruit(){Name = "ぶどう", Rank = "B" , Price = 1200 },
                    new Fruit(){Name = "りんご", Rank = "B" , Price = 800 },
                    new Fruit(){Name = "みかん", Rank = "A" , Price = 500 }

                };
【VB】
Dim fruits = New List(Of Fruit)() From
            {
                New Fruit() With {.Name = "りんご", .Rank = "A", .Price = 1000},
                New Fruit() With {.Name = "みかん", .Rank = "A", .Price = 600},
                New Fruit() With {.Name = "ぶどう", .Rank = "B", .Price = 1200},
                New Fruit() With {.Name = "りんご", .Rank = "B", .Price = 800},
                New Fruit() With {.Name = "みかん", .Rank = "A", .Price = 500}
            }
果物名別の合計金額を集計します。

まずはGroupByで名前でグループ化します。
つづいてグループ化した各アイテムに対してAgreegateで金額を集計します。
Agreegateの第1引数がseed(種)で、第二引数でseedに対して各アイテムのPriceを加算していきます。
【C#】
var nameSumPriceList = fruits
    .GroupBy(itm => itm.Name)
    .Select(grp =>
        new
        {
            Name = grp.Key,
            SumPrice = grp.Aggregate(0m, (seedSum, curItm) =>  seedSum += curItm.Price)
        });
//--出力--
// りんご 1800
// みかん 1100
// ぶどう 1200
nameSumPriceList.ToList().ForEach(itm =>
 { Console.WriteLine(string.Format("{0} {1}", itm.Name, itm.SumPrice)); });
【VB】
Dim nameSumPriceList = fruits _
            .GroupBy(Function(itm) itm.Name) _
            .Select(Function(grp) _
                        New With
                        {
                            .Name = grp.Key,
                            .SumPrice = grp.Aggregate(0D, Function(seedSum, curItm) seedSum + curItm.Price)
                        })
'--出力--
' りんご 1800
' みかん 1100
' ぶどう 1200
nameSumPriceList.ToList().ForEach(Sub(itm) _
  Console.WriteLine(String.Format("{0} {1}", itm.Name, itm.SumPrice)))

次は果物名別ランク別の最大金額を集計します。
【C#】
//名前別ランク別の最大金額          
var namePriceList = fruits
    .GroupBy(itm => new { itm.Name, itm.Rank })
    .Select(grp =>
        new
        {
            Name = grp.Key.Name,
            Rank = grp.Key.Rank,
            MaxPrice = 
                grp.Aggregate(0m, (seedMax, curItm) => 
                    (seedMax > curItm.Price) ? seedMax : curItm.Price)
        });

//--出力
// りんご A 1000
// みかん A 600
// ぶどう B 1200
// りんご B 800
namePriceList.ToList().ForEach(itm =>
    { Console.WriteLine(string.Format("{0} {1} {2}", itm.Name, itm.Rank, itm.MaxPrice)); });
【VB】
GroupByでグループ化のキーに匿名クラスを使用する際は、キーに使用するプロパティの前にKeyキーワードを付けます。
'名前ランク別の最大金額          
Dim namePriceList = fruits _
        .GroupBy(Function(itm) New With {Key itm.Name, Key itm.Rank}) _
        .Select(Function(grp) _
           New With
           {
               .Name = grp.Key.Name,
               .Rank = grp.Key.Rank,
               .MaxPrice = 
                   grp.Aggregate(0D, Function(seedMax, curItm) 
                       If(seedMax > curItm.Price, seedMax, curItm.Price))
           })

'--出力
' りんご A 1000
' みかん A 600
' ぶどう B 1200
' りんご B 800
namePriceList.ToList().ForEach(
    Sub(itm) Console.WriteLine(String.Format("{0} {1} {2}", itm.Name, itm.Rank, itm.MaxPrice)))

.Net(VB,C#) LINQ で重複のないデータを抽出する

LINQを使用して、リストから重複のないデータを抽出します。

テスト用の高級果物クラスです。
【C#】
private class Fruit
{
    public string Name { get; set; }
    public string Rank { get; set; }
    public decimal Price { get; set; }
}
【VB】
Private Class Fruit
    Public Property Name As String
    Public Property Rank As String
    Public Property Price As Decimal
End Class
テストデータを作成します。
【C#】
var fruits = new List<Fruit>()
                {
                    new Fruit(){Name = "りんご", Rank = "A" , Price = 1000 },
                    new Fruit(){Name = "みかん", Rank = "A" , Price = 600 },
                    new Fruit(){Name = "ぶどう", Rank = "B" , Price = 1200 },
                    new Fruit(){Name = "りんご", Rank = "B" , Price = 800 },
                    new Fruit(){Name = "みかん", Rank = "A" , Price = 500 }
                };
【VB】
Dim fruits = New List(Of Fruit)() From
            {
                New Fruit() With {.Name = "りんご", .Rank = "A", .Price = 1000},
                New Fruit() With {.Name = "みかん", .Rank = "A", .Price = 600},
                New Fruit() With {.Name = "ぶどう", .Rank = "B", .Price = 1200},
                New Fruit() With {.Name = "りんご", .Rank = "B", .Price = 800},
                New Fruit() With {.Name = "みかん", .Rank = "A", .Price = 500}
            }
果物名で重複のないデータを抽出します。
【C#】
var nameList = fruits.Select(itm => itm.Name).Distinct();
//--出力--
//りんご
//みかん
//ぶどう
nameList.ToList().ForEach(itm => { Console.WriteLine(itm); });
【VB】
 Dim nameList = fruits.Select(Function(itm) itm.Name).Distinct()
'--出力--
' りんご
' みかん
' ぶどう
nameList.ToList().ForEach(Sub(itm) Console.WriteLine(itm))
果物名とランクで重複のないデータを抽出します。
【C#】
var nameRankList = fruits.Select(itm => new { itm.Name, itm.Rank }).Distinct();
//--出力--
// りんご A
// みかん A
// ぶどう B
// りんご B
nameRankList.ToList().ForEach(itm =>
 { Console.WriteLine(string.Format("{0} {1}", itm.Name, itm.Rank)); });
【VB】
VBでは匿名クラスをキー項目にしたい場合、キーにしたい各プロパティの前にKeyキーワードを付けます。
Dim nameRankList = fruits.Select(Function(itm) New With {Key itm.Name, Key itm.Rank}).Distinct()
'--出力--
' りんご A
' みかん A
' ぶどう B
' りんご B
nameRankList.ToList().ForEach(Sub(itm) _
     Console.WriteLine(String.Format("{0} {1}", itm.Name, itm.Rank)))

他にもGroupByして、各グループの先頭1件目を抽出する方法もあります。
【C#】
var nameRankList = fruits.GroupBy(itm => new { itm.Name, itm.Rank })
                    .Select(grp => grp.First());
//--出力--
// りんご A
// みかん A
// ぶどう B
// りんご B
nameRankList.ToList().ForEach(itm =>
                { Console.WriteLine(string.Format("{0} {1}", itm.Name, itm.Rank)); });
【VB】
VBで匿名クラスのプロパティをキー項目にしたい場合、キーにしたい各プロパティの前にKeyキーワードを付けます。
'重複のない名称&ランク
Dim nameRankList = fruits.GroupBy(Function(itm) New With {Key itm.Name, Key itm.Rank}) _
                        .Select(Function(grp) grp.First())
'--出力--
' りんご A
' みかん A
' ぶどう B
' りんご B
nameRankList.ToList().ForEach(Sub(itm) _
     Console.WriteLine(String.Format("{0} {1}", itm.Name, itm.Rank)))

.NET(VB C#) オブジェクト初期化子、コレクション初期化子、匿名クラス

今年1年C#で書いてたら、来年からVBです。
VBでのLINQの書き方がわかりません。
LINQラブなのでVBでの書き方をまとめていきます。

まずはLINQを書く上ではずせない
オブジェクト初期化子、コレクション初期化子、匿名クラスについてVB、C#での書き方をまとめます。

まずはテスト用のクラスとして高級果物クラスです。

【C#】
private class Fruit
{
    public string Name { get; set; }
    public string Rank { get; set; }
    public decimal Price { get; set; }
}
【VB】
Private Class Fruit
    Public Property Name As String
    Public Property Rank As String
    Public Property Price As Decimal
End Class

オブジェクト初期化子


【C#】
Fruit fruit = new Fruit() { Name = "りんご", Rank = "A", Price = 1000 };
【VB】
Withキーワードを使用します。
※Withキーワード以降で改行しても「_(アンダーバー)」は不要です。(注1)
Dim fuit As New Fruit() With { .Name = "りんご", .Rank = "A", .Price = 1000 }

コレクション初期化子


【C#】
var fruits = new List<Fruit>()
     {
         new Fruit(){Name = "りんご", Rank = "A" , Price = 1000 },
         new Fruit(){Name = "みかん", Rank = "A" , Price = 600 },
         new Fruit(){Name = "ぶどう", Rank = "B" , Price = 1200 },
         new Fruit(){Name = "りんご", Rank = "B" , Price = 800 },
         new Fruit(){Name = "みかん", Rank = "A" , Price = 500 }

     };
【VB】
Fromキーワードを使用します。
※Fromキーワード以降で改行しても「_(アンダーバー)」は不要です。(注1)
Dim fruits = New List(Of Fruit)() From
     {
         New Fruit() With {.Name = "りんご", .Rank = "A", .Price = 1000},
         New Fruit() With {.Name = "みかん", .Rank = "A", .Price = 600},
         New Fruit() With {.Name = "ぶどう", .Rank = "B", .Price = 1200},
         New Fruit() With {.Name = "りんご", .Rank = "B", .Price = 800},
         New Fruit() With {.Name = "みかん", .Rank = "A", .Price = 500}
     }

匿名クラス


【C#】
dynamic myfruit = new { Name = "金のりんご", Rank = "AAA", Price = 9999999999 };
【VB】
Withキーワードを使用します。
Dim myfruit = New With {.Name = "金のりんご", .Rank = "AAA", .Price = 9999999999}

注1
VBで改行「_(アンダーバー)」がVS2010より条件により省略できるようになりました。
省略できる条件はコチラ
Visual Basic 2010 の新機能 暗黙の行継続


C#の方がわかり易い。書き易い。
VB11年。C#1年。
C#のほうがイイなぁ・・・

PLSQL カーソルを使用してデータを取得する

以前に「PLSQL SELECTの結果を取得する~取得結果が複数行の場合~ 」でカーソルの使い方についてまとめました。

以下はその時に使用したコードで、カーソルを使用して、データを取得しています。
DECLARE
    --カーソル定義
    CURSOR member_csr IS 
        SELECT * FROM Member;
    --変数宣言
    memberid VARCHAR2(4);
    membername VARCHAR2(10);
    rank VARCHAR2(2);
BEGIN
    --カーソルオープン
    OPEN member_csr;
    
    LOOP
      --カーソルから1件データを取り出し、変数に代入
      FETCH member_csr INTO memberid, membername, rank;
      --カーソルにデータがなければ、ループ終了
      EXIT WHEN member_csr%NOTFOUND;
      --出力
      SYS.DBMS_OUTPUT.PUT_LINE(memberid || ' ' || membername || ' ' || rank);    
    END LOOP;
    
    --カーソルクローズ
    CLOSE member_csr;
END;
しかし、このコードではカーソルを開いてから例外が発生すると、カーソルが閉じられません。
そこで「PLSQL 例外処理のネスト 」でまとめたように、例外が発生してもカーソルが確実に閉じられるようにします。
DECLARE
    --カーソル定義
    CURSOR member_csr IS 
        SELECT * FROM Member;
    --変数宣言
    memberid VARCHAR2(4);
    membername VARCHAR2(10);
    rank VARCHAR2(2);
BEGIN
    --カーソルオープン
    OPEN member_csr;
    
    BEGIN

        LOOP
          --カーソルから1件データを取り出し、変数に代入
          FETCH member_csr INTO memberid, membername, rank;
          --カーソルにデータがなければ、ループ終了
          EXIT WHEN member_csr%NOTFOUND;
          --出力
          SYS.DBMS_OUTPUT.PUT_LINE(memberid || ' ' || membername || ' ' || rank);    
        END LOOP;
        
        --カーソルクローズ
        CLOSE member_csr;

    EXCEPTION
        WHEN others THEN
            --例外が発生してもカーソルをクローズする
            CLOSE member_csr;
            --例外を親ブロックへ伝播する。(必要な場合のみ)
            RAISE;
    END;

END;

カーソルFORループ


カーソルFORループを使用すると、カーソルを使用してデータを取得する際の以下の処理が省略できます。
  • カーソルのデータを格納する変数を宣言する。
  • カーソルをオープンする。
  • フェッチで1行分のデータを取り出す。
  • カーソルを閉じる。
カーソルが自動で閉じられるので、例外発生時にカーソルが確実に閉じられるよう考慮する必要もありません。

カーソルFORループは以下のように定義します。
FOR レコード変数 IN カーソル LOOP
    処理
END LOOP;

カーソルFORループを使用して、データを取得するコードです。
DECLARE
    --カーソル定義
    CURSOR member_csr IS 
        SELECT * FROM Member;
   
BEGIN

    FOR member_rec IN member_csr LOOP
        SYS.DBMS_OUTPUT.PUT_LINE(member_rec.memberid || ' ' || member_rec.membername);    
    END LOOP;
   
END;

PLSQL 例外処理のネスト

前回の「PLSQL 例外処理」に続き、
今回は例外処理のネストについてまとめます。


例外の発生が予想され、例外を適切に処理したあと正常系の処理を行いたいとします。
以下のコードでは6行目でSELECT文の結果が0件のためno_data_found例外が発生し、
「例外が発生しました。SQLCODE=100、エラーメッセージ=ORA-01403: データが見つかりません。」と表示されます。
「その後の処理」は出力されません。
DECLARE
    name Member.MemberName%Type;
 
BEGIN
    
    SELECT MemberName INTO name 
        FROM Member WHERE MemberId = 99;       
    SYS.DBMS_OUTPUT.PUT_LINE('MemberId=1のMemberName=' || name);
    
    
    SYS.DBMS_OUTPUT.PUT_LINE('その後の処理');
    
EXCEPTION
    WHEN others THEN
        SYS.DBMS_OUTPUT.PUT_LINE('例外が発生しました。SQLCODE=' || SQLCODE || '、エラーメッセージ=' || SQLERRM);  
END;

6行目のno_data_foundは予想された処理であり、データがなかったときは例外を握りつぶして正常系の処理に戻りたいとします。
そのような場合は6行目の処理を子ブロックにし例外を処理します。

以下のコードではno_data_found例外が発生してもnull文で例外を握りつぶしているので、「その後の処理」が出力されます。
(null文は「何もしない」ということ)
DECLARE
    name Member.MemberName%Type;
 
BEGIN
    
    BEGIN
        --結果が0件となるSELECT文
        SELECT MemberName INTO name 
            FROM Member WHERE MemberId = 99;       
        SYS.DBMS_OUTPUT.PUT_LINE('MemberId=1のMemberName=' || name);
    EXCEPTION
        --該当データがなければ例外を握りつぶす。
        WHEN no_data_found THEN
            null;
    END;
    
    SYS.DBMS_OUTPUT.PUT_LINE('その後の処理');
    
EXCEPTION
    WHEN others THEN
        SYS.DBMS_OUTPUT.PUT_LINE('例外が発生しました。SQLCODE=' || SQLCODE || '、エラーメッセージ=' || SQLERRM);  
END;
子ブロックで処理しなかった例外は親ブロックへ伝播(でんぱ)します。
先ほどの6行目SELECT文を修正して複数行のデータが返るようにします。
すると6行目ではtoo_many_rows例外が発生しますが、子ブロックのEXCEPTION部でハンドリングされていない例外なので
親ブロックのEXCEPTION部のothersハンドラで補足され、
「例外が発生しました。SQLCODE=-1422、エラーメッセージ=ORA-01422: 完全フェッチがリクエストよりも多くの行を戻しました。」と表示されます。
DECLARE
    name Member.MemberName%Type;
 
BEGIN
    
    BEGIN
        --結果が複数件となるSELECT文
        SELECT MemberName INTO name 
            FROM Member;       
        SYS.DBMS_OUTPUT.PUT_LINE('MemberId=1のMemberName=' || name);
    EXCEPTION
        --該当データがなければ例外を握りつぶす。
        WHEN no_data_found THEN
            null;
    END;
    
    SYS.DBMS_OUTPUT.PUT_LINE('その後の処理');
    
EXCEPTION
    WHEN others THEN
        SYS.DBMS_OUTPUT.PUT_LINE('例外が発生しました。SQLCODE=' || SQLCODE || '、エラーメッセージ=' || SQLERRM);  
END;

例外は補足して処理するが、正常系の処理には戻らず、親ブロックの例外に伝播してほしい場合もあります。
このような場合はRAISE文により、補足して例外を再発生させます。

以下のコードはカーソルを開いてデータを取得しています。
例外が発生してもカーソルが閉じられるるように、EXCEPTION部をネストして、カーソルを閉じています。
その後は正常系の処理には戻らず、RAISE文により例外を再発生させ、親ブロックのEXCEPTION部に伝播し、エラー内容を出力させています。
DECLARE
    --カーソル宣言
    CURSOR member_csr IS SELECT * FROM Member; 
    --カーソル変数
    member_rec member_csr%Rowtype;
    
    numValue NUMBER;
BEGIN
    
    --カーソル オープン
    OPEN member_csr;
    
    BEGIN
        LOOP
            FETCH member_csr INTO member_rec;
            EXIT WHEN member_csr%NOTFOUND;
            --例外発生
            numValue := 1/0;
        END LOOP;
        
        --正常系処理でのカーソルクローズ
        CLOSE member_csr;
        SYS.DBMS_OUTPUT.PUT_LINE('正常系処理内でカーソルを閉じました。');
        
    EXCEPTION
        WHEN others THEN
            --例外が発生してもカーソルをクローズするようにする
            CLOSE member_csr;
            SYS.DBMS_OUTPUT.PUT_LINE('異常系処理内でカーソルを閉じました。');
            --例外を再発生させる
            RAISE;
    END;
    
    SYS.DBMS_OUTPUT.PUT_LINE('その後の処理');
    
EXCEPTION
    WHEN others THEN
        SYS.DBMS_OUTPUT.PUT_LINE('例外が発生しました。SQLCODE=' || SQLCODE || '、エラーメッセージ=' || SQLERRM);  
END;

PLSQL 例外処理

PLSQLの例外はEXCEPTION部で処理します。
DECLARE
    宣言部
    
BEGIN
    実行部

EXCEPTION
    例外処理部
    
END;

実行部で例外が発生すると、例外が発生した以降の処理は実行されず、例外処理部に処理が移ります。
例外処理部では、例外を補足する例外ハンドラを定義し、例外に応じた処理を行います。

例外ハンドラは以下のような構文で定義します。
WHEN 例外名 THEN
複数の例外に対して同じ処理を行いたい場合は、例外名称をORで繋ぎます。
WHEN 例外名1 OR 例外名2 THEN

まとめると以下のようになります。
DECLARE

BEGIN
    
    例外発生!EXCEPTION部へ処理が遷移

    例外発生以降の処理は行われない

EXCEPTION
    
    WHEN 例外名1 THEN
        例外1が発生時の処理
        
    WHEN 例外名2 OR 例外名3 THEN
        例外2、例外3が発生した時の処理
END;

例外の発生を確認してみます。
以前の記事「PLSQL SQL Developer からテーブル作成 」で作成したテーブルからMemberId=99のMemberNameを取得します。
しかしMemberId=99のデータがないので例外が発生します。
DECLARE
    name Member.MemberName%Type;
    
BEGIN
    
    SELECT MemberName INTO name 
        FROM Member WHERE MemberId = 99;
        
    SYS.DBMS_OUTPUT.PUT_LINE('MemberId=99のMemberName=' || name);

END;
6行目で「ORA-01403 no data found データが見つかりません。」とエラーが発生します。

例外処理を加えてみます。
DECLARE
    name Member.MemberName%Type;
    
BEGIN
    
    SELECT MemberName INTO name 
        FROM Member WHERE MemberId = 99;
        
    SYS.DBMS_OUTPUT.PUT_LINE('MemberId=99のMemberName=' || name);

EXCEPTION
    WHEN no_data_found THEN
      SYS.DBMS_OUTPUT.PUT_LINE('例外が発生しました。SQLCODE=' || SQLCODE || '、エラーメッセージ=' || SQLERRM);  
END;
今度はエラーが発生せず「例外が発生しました。SQLCODE=100、エラーメッセージ=ORA-01403: データが見つかりません」と表示されます。


EXCEPTION部で使用されている
SQLCODEはOracleのエラー番号を返す関数です。
SQLERRMはOracleのエラーメッセージを返す関数です。

WHEN no_data_found THEN はSELECT INTO文でデータが取得できなかった時の例外をキャッチします。


no_data_found以外にも、PLSQLでは最低限必要な例外が事前定義されています。
事前定義されている例外は以下のようなものがあります。
例外OracleエラーSQLCODE値発生原因
ACCESS_INTO_NULL06530-6530プログラムが未初期化オブジェクトの属性に値を代入しようとしたとき。
CASE_NOT_FOUND06592-6592CASE文のWHEN句で何も選択されておらず、ELSE句もない場合。
COLLECTION_IS_NULL06531-6531プログラムがEXISTS以外のコレクション・メソッドを未初期化のネストした表またはVARRAYに適用しようとしたか、または未初期化のネストした表またはVARRAYの要素に値を代入しようとしたとき。
CURSOR_ALREADY_OPEN06511-6511すでにオープンされているカーソルをオープンしようとしたとき。
カーソルをオープンするには、一度クローズする必要があります。
カーソルFORループは、参照するカーソルを自動的にオープンします。
このため、ループの内側ではカーソルをオープンできません。
DUP_VAL_ON_INDEX00001-1UNIQUE索引によって制約されている列に、重複した値を格納しようとしたとき。
INVALID_CURSOR01001-1001オープンされていないカーソルをクローズするなど、不正なカーソル操作を実行しようとしたとき。
INVALID_NUMBER01722-1722SQL文の中で、文字列が正しい数値を表していなかったために、文字列から数値への変換が失敗したとき。
(プロシージャ文では、VALUE_ERRORが呼び出されます。)
この例外は、バルクFETCH文のLIMIT句の式が正数に評価されない場合にも呼び出されます。
LOGIN_DENIED01017-1017不正なユーザー名またはパスワードでデータベースにログオンしようとした場合。
NO_DATA_FOUND01403+100SELECT INTO文が行を戻さなかったとき、ネストした表で削除された要素を参照したとき、または索引付き表で未初期化の要素を参照したとき。
この例外は、いくつかのSQLファンクションで終了したことを通知するために内部的に使用されているため、問合せの一部として起動されるファンクション内部で呼び出された場合は、この例外が伝播されても信頼しないでください。
NOT_LOGGED_ON01012-1012データベースに接続していないプログラムが、データベース・コールを発行した場合。
PROGRAM_ERROR06501-6501PL/SQLに内部的な問題が発生した場合。
ROWTYPE_MISMATCH06504-65041つの代入の中に含まれるホスト・カーソル変数とPL/SQLカーソル変数の戻り型に互換性がない場合。
オープン・ホスト・カーソル変数をストアド・サブプログラムに渡すとき、実パラメータの戻り型と仮パラメータの戻り型には互換性が必要です。
SELF_IS_NULL30625-30625プログラムがMEMBERメソッドの起動を試行したが、オブジェクト型のインスタンスが初期化されなかった場合。
つまり、組込みパラメータSELFがオブジェクトを指している場合です。
このパラメータは、常にMEMBERメソッドに最初に渡されるパラメータです。
STORAGE_ERROR06500-6500PL/SQLのメモリーが足りなくなった場合、またはメモリーが破損された場合。
SUBSCRIPT_BEYOND_COUNT06533-6533コレクション中の要素数より大きい索引番号を使用してネストした表またはVARRAYの要素を参照した場合。
SUBSCRIPT_OUTSIDE_LIMIT06532-6532有効範囲外(たとえば-1)の索引番号を使用してネストした表またはVARRAYの要素を参照した場合。
SYS_INVALID_ROWID01410-1410文字列が正しいROWIDを表していなかったために、文字列からユニバーサルROWIDへの変換が失敗した場合。
TIMEOUT_ON_RESOURCE00051-51データベースがリソースを求めて待機しているときにタイムアウトが発生した場合。
TOO_MANY_ROWS01422-1422SELECT INTO文が複数の行を戻した場合。
VALUE_ERROR06502-6502算術エラー、変換エラー、切捨てエラー、またはサイズ制約エラーが発生した場合。
たとえば、列値を選択し文字変数に代入するときに、その値が変数の宣言された長さよりも長い場合、PL/SQLはその代入を停止してVALUE_ERRORを呼び出します。
プロシージャ文では、文字列から数値への変換が失敗した場合にVALUE_ERRORが呼び出されます。 (SQL文では、INVALID_NUMBERが呼び出されます。)
ZERO_DIVIDE01476-1476数値を0(ゼロ)で割ろうとしたとき。

事前に定義されていない例外を補足するには othersハンドラ を使用します。
othersハンドラはすべての例外を補足します。
他の例外をハンドリングしている場合は、一番最後にothersハンドラを書きます。
othersハンドラを他の例外ハンドリングより先に書くとエラーになります。

先ほどのコードを少し修正して、SELECTの結果が複数行返るようにします。
例外はothersハンドラでキャッチされ、エラーメッセージが表示されます。
DECLARE
    name Member.MemberName%Type;
 
BEGIN
    
    SELECT MemberName INTO name 
        FROM Member;
        
    SYS.DBMS_OUTPUT.PUT_LINE('MemberId=1のMemberName=' || name);
    
    
EXCEPTION
    WHEN no_data_found THEN
        SYS.DBMS_OUTPUT.PUT_LINE('例外が発生しました。SQLCODE=' || SQLCODE || '、エラーメッセージ=' || SQLERRM);  
    
    WHEN others THEN
        SYS.DBMS_OUTPUT.PUT_LINE('その他の例外が発生しました。SQLCODE=' || SQLCODE || '、エラーメッセージ=' || SQLERRM);  
  
END;

PLSQL 変数の精度について

以前の記事で「PLSQL 変数、定数 」で簡単に変数の使い方を書きました。
よく使う変数のデータ型に以下のようなものがあります。(他にもありますが、私が今のところよく使うのはこれだけ)
  • NUMBER
  • VARCHAR2
  • CHAR
  • DATE
  • BOOLEAN

このうち精度を指定するのはNUMBER型、VARCHAR2型、CHAR型です。
DECLARE
    numValue NUMBER(10);
    strValue VARCHAR(10);
    charValue CHAR(10);
    dateValue DATE;
    boolValue BOOLEAN;
BEGIN    
    
    NULL;
    
END;
NUMBER(10)型を設定した変数は10桁の数値が代入できます。
VARCHAR2(10)型を設定した変数は10バイトの文字列が代入できます。
CHAR(10)型を設定した変数は10バイトの文字列が代入できます。

データ型により精度を省略できるものがあります。

NUMBERは精度を指定しないと最大の38桁の数値が代入できます。

CHARは精度指定しないと1バイトの文字が代入できます。
VARCHAR2は精度を指定しないとエラーになります。


NUMBER型の精度が省略できるならVARCHAR2も省略できるハズ~♪と思ったのは私だけではないと思うんだけどなぁ。
ちょっとググるとCHARの精度を省略したら最大バイトになると思っていたナカマがいたぁ ヾ(´∇`。*)ノ 

ちなみにVARCHAR2とCHARに指定できる精度は32767が上限です。



【関連】
PLSQL 変数、定数