PLSQL SELECTの結果を取得する~取得結果が複数行の場合~

前回「PLSQL SELECTの結果を取得する ~取得結果が1行の場合~ 」に続き
今回はSELECTの結果が複数行の場合です。

SELECTの結果が複数行の場合はカーソルを使用します。

カーソルとは
SELECTの結果セットに対して、1行ずつデータを取り出し、順次処理していくためのものです。

カーソルを使用する手順

1. カーソルの宣言する。
   CURSOR カーソル名 IS SELECT文;

2. カーソルを開く。
   OPEN カーソル名;

3. ループ処理を開始

4. 1行ごとにデータを取り出す。
   FETCH カーソル名 INTO 変数

5. データがなければループを終了する
   EXIT WHEN カーソル名%NOTFOUND;

6. カーソルを閉じる。
   CLOSE カーソル名;

カーソルを使用してMemberテーブルの全データを取得し、1件づつ変数に代入し出力します。
※カーソルを使用したデータの取得についてはコチラ「PLSQL カーソルを使用してデータを取得する
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 SELECTの結果を取得する ~取得結果が1行の場合~ 」と同様に
変数宣言のデータ型を「%type属性」にしたパターン。
DECLARE
    --カーソル定義
    CURSOR member_csr IS 
        SELECT * FROM Member;
    --変数宣言
    memberid Member.MemberId%Type;
    membername Member.MemberName%Type;
    rank Member.Rank%Type;
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;
Memberテーブルの「%ROWTYPE属性」を使用したパターン
DECLARE
    --カーソル定義
    CURSOR member_csr IS 
        SELECT * FROM Member;
    --変数宣言
    member_rec Member%Rowtype;
BEGIN
    --カーソルオープン
    OPEN member_csr;
    
    LOOP
      --カーソルから1件データを取り出し、変数に代入
      FETCH member_csr INTO member_rec;
      --カーソルにデータがなければ、ループ終了
      EXIT WHEN member_csr%NOTFOUND;
      --出力
      SYS.DBMS_OUTPUT.PUT_LINE(member_rec.MemberId || ' ' || member_rec.MemberName || ' ' || member_rec.Rank);    
    END LOOP;
    
    --カーソルクロース
    CLOSE member_csr;
END;
カーソルの「%ROWTYPE属性」を使用したパターン
カーソルを使用した場合は「レコード変数名 カーソル名%ROWTYPE;」とすることでカーソルの1データを代入できるレコート変数ができます。
DECLARE
    --カーソル定義
    CURSOR member_csr IS 
        SELECT * FROM Member;
    --レコード型のレコード変数を宣言
    member_rec member_csr%Rowtype;

BEGIN
    --カーソルオープン
    OPEN member_csr;
    
    LOOP
      --カーソルから1件データを取り出し、変数に代入
      FETCH member_csr INTO member_rec;
      --カーソルにデータがなければ、ループ終了
      EXIT WHEN member_csr%NOTFOUND;
      --出力
      SYS.DBMS_OUTPUT.PUT_LINE(member_rec.MemberId || ' ' || member_rec.MemberName || ' ' || member_rec.Rank);    
    END LOOP;
    
    --カーソルクロース
    CLOSE member_csr;
END;
変数宣言のデータ型を自分で定義したレコート型にしたパターン。
DECLARE
    --カーソル定義
    CURSOR member_csr IS 
        SELECT * FROM Member;
     --レコード型の作成
    TYPE member_rec_type IS RECORD
        (
            MemberId  Member.MemberId%type,
            MemberName  Member.MemberName%type,
            Rank  Member.Rank%type
        );
    --レコード型のレコード変数を宣言
    member_rec member_rec_type;

BEGIN
    --カーソルオープン
    OPEN member_csr;
    
    LOOP
      --カーソルから1件データを取り出し、変数に代入
      FETCH member_csr INTO member_rec;
      --カーソルにデータがなければ、ループ終了
      EXIT WHEN member_csr%NOTFOUND;
      --出力
      SYS.DBMS_OUTPUT.PUT_LINE(member_rec.MemberId || ' ' || member_rec.MemberName || ' ' || member_rec.Rank);    
    END LOOP;
    
    --カーソルクロース
    CLOSE member_csr;
END;

また1年ぶりの投稿ですね~

去年のちょうど今頃、在宅の仕事を頂いている会社が傾いてお仕事がなくなってしまいました。
辺境地に住んでいるので仕事先がなくて、唯一あった開発会社に1年間お世話になってました。
なのでブログを書く時間がなかったんです。
ここでのお仕事は.Net(C#)。
面白い仕事ではまったくなかったけれど、個人的にLinqで遊んだり、クロージャで遊んだりしてました。
わたしC#好きだなぁ

来年からは別の会社でお世話になります。
次は.Net(VB)+PLSQL。
正社員として転職したので長~くお世話になる予定です。

PLSQLほとんど経験ないんですよね・・・とりあえずお勉強です。

このブログAndroidで来てくれる人が多いんですけど
しばらくはPLSQLの記事が続きます。

もうAndroid開発することないだろうなぁ・・・
swiftとか興味あったんだけどな・・・

PLSQL SELECTの結果を取得する ~取得結果が1行の場合~

今回はPLSQLでSELECTの結果を取得してみます。

下準備としてPLSQL SQL Developer からテーブル作成 で作成したMemberテーブルに3件ほどデータを作成します。
DECLARE 
BEGIN
    INSERT INTO Member
        ( MemberId, MemberName, Rank ) VALUES ( 1, 'Yamada', 'B');
    INSERT INTO Member
        ( MemberId, MemberName, Rank ) VALUES ( 2, 'Tanaka', 'C');
    INSERT INTO Member
        ( MemberId, MemberName, Rank ) VALUES ( 3, 'Suzuki', 'A');
END;

それではデータを1件抽出し変数に代入します。
DECLARE部でデータを代入する変数を宣言します。
次に「SELECT 列名 INTO 代入する変数」で取得したデータを変数に代入します。
データが取得できない場合や複数件取得できた場合はエラーになるので注意してください。
DECLARE
    memberid VARCHAR2(4);
    membername VARCHAR2(10);
    rank VARCHAR2(2);
BEGIN
    SELECT MemberId, MemberName, Rank INTO memberid, membername, rank
    FROM Member
    WHERE MemberId = '1';
    
    SYS.DBMS_OUTPUT.PUT_LINE('MemberId:' || memberid);
    SYS.DBMS_OUTPUT.PUT_LINE('MemberName:' || membername);
    SYS.DBMS_OUTPUT.PUT_LINE('Rank:' || rank);
END;
上記の例では変数のデータ型をSELECTで取得する列のデータ型と合わせる必要がありました。
memberid VARCHAR2(4);の「VARCHAR2(4)」部分です。
だけどイチイチ列のデータ型や精度を指定するのはメンドクサイよ!
仕様変更でデータ型や精度が変わるたびに修正するのはメンドクサイよ!
メンドクサイのはイヤなので上記の変数宣言のデータ型を「%type属性」を使用してちょっとラクします。
DECLARE
    memberid Member.MemberId%type;
    membername Member.MemberName%type;
    rank Member.Rank%type;
BEGIN
    SELECT MemberId, MemberName, Rank INTO memberid, membername, rank
    FROM Member
    WHERE MemberId = '1';
    
    SYS.DBMS_OUTPUT.PUT_LINE('MemberId:' || memberid);
    SYS.DBMS_OUTPUT.PUT_LINE('MemberName:' || membername);
    SYS.DBMS_OUTPUT.PUT_LINE('Rank:' || rank);
END;
Oracle Database PL/SQL言語リファレンスによりますと
『%TYPE属性を使用すると事前に宣言されている変数、フィールド、レコード、ネストした表またはデータベース列と同じデータ型の定数、変数、フィールドまたはパラメータを宣言できます。参照先項目が変更されると、宣言は自動的に更新されます。』とあります。

つまり「memberid Member.MemberId%type;」の部分は
MemberテーブルのMemberId列と同じデータ型にし、MemberテーブルのMemberId列のデータ型が変更されても自動的に変更するから大丈夫ということです。

う~ん
でもまだメンドクサイ。Memberテーブルの1データを取得するのに、Memberテーブルに含まれるすべての列分の変数を作るのはメンドクサイよ。
Memberテーブルの列が20列ぐらいあったら、変数も20個用意するの・・・・?ムリ。

ハイ!そんなときは「%ROWTYPE属性」を使用します。
DECLARE
    member_rec Member%rowtype;
BEGIN
    SELECT * INTO member_rec
    FROM Member
    WHERE MemberId = '1';
    
    SYS.DBMS_OUTPUT.PUT_LINE('MemberId:' || member_rec.MemberId);
    SYS.DBMS_OUTPUT.PUT_LINE('MemberName:' || member_rec.MemberName);
    SYS.DBMS_OUTPUT.PUT_LINE('Rank:' || member_rec.Rank);
END;
「レコード変数名 表名%ROWTYPE;」とすることでテーブルの1データを代入できる変数ができるんですね!
ステキです。

ステキですね~ラクですね~使いまくりたくなりますね~

でもダメなんですね~

今回Memberテーブルは3列しかないですが、Memberテーブルが20列あったとします。
そのうち使用するのは5列だったとしたら、15列分のデータは取得するけど無駄になってしまいます。

そんな場合は、使用する列だけのレコード型を自分で定義し、必要な列だけ変数に代入するようにします。
DECLARE
    --レコード型の作成
    TYPE member_rec_type IS RECORD
        (
            MemberId  Member.MemberId%type,
            MemberName  Member.MemberName%type
        );
    --レコード型のレコード変数を宣言
    member_rec member_rec_type;
BEGIN
    SELECT MemberId, MemberName INTO member_rec
    FROM Member
    WHERE MemberId = '1';
    
    SYS.DBMS_OUTPUT.PUT_LINE('MemberId:' || member_rec.MemberId);
    SYS.DBMS_OUTPUT.PUT_LINE('MemberName:' || member_rec.MemberName);
END;
「TYPE データ型名 IS RECORD (フィールド宣言・・・);」でレコードの「型」を宣言します。
次にデータ型が作成したレコード型の変数を宣言します。

レコード型を作成するのがメンドクサイですが、使い分けが必要なようです。

PLSQL 変数、定数

前回は「Hello World」を直接出力しました。
DECLARE

BEGIN

    SYS.DBMS_OUTPUT.PUT_LINE('Hello World');

END;

今回は「Hello World」を変数に代入して、出力します。
変数はDECLARE部で「変数名 変数型」の形式で宣言します。
変数への値の代入は「変数 := 代入する値」で行います。
DECLARE
    --DECLARE部で変数の宣言を行う
    str VARCHAR2(20);
BEGIN
    --変数へ値の代入は「変数 := 代入する値」
    str := 'Hello World 2'; 
    SYS.DBMS_OUTPUT.PUT_LINE(str);
END;
宣言と同時に初期値を設定することも可能です。
初期値を設定しない場合は、変数の値はnullになります。
DECLARE
    --宣言と同時に初期値設定
    str VARCHAR2(50) := 'Default Hello World';
BEGIN
    SYS.DBMS_OUTPUT.PUT_LINE(str);
END;
変数にNOT NULL制約を指定することも可能です。
変数は初期値を設定しなければ値はnullなので、NOT NULL制約を指定する場合は初期値も同時に設定する必要があります。
DECLARE
    --NOT NULL制約を指定
    str VARCHAR2(50) NOT NULL := 'Default Hello World Not Null';
BEGIN
    SYS.DBMS_OUTPUT.PUT_LINE(str);
END;
CONSTキーワードを指定すると定数になります。
「定数名 CONSTANT データ型 := 定数値」と宣言します。
DECLARE
    --定数宣言
    str CONSTANT VARCHAR2(50) := 'Const Hello World';
BEGIN
    SYS.DBMS_OUTPUT.PUT_LINE(str);
END;
定数なので下記のように定数に値を代入しようとすると、エラーになります。
DECLARE
    --定数宣言
    str CONSTANT VARCHAR2(50) := 'Const Hello World';
BEGIN
    --定数に値を代入しようとするのでエラーになる。
    str := 'Change Const Value';
    SYS.DBMS_OUTPUT.PUT_LINE(str);
END;


【関連】
PLSQL 変数の精度について

PLSQL とりあえず最初はHelloWorldを出力する

最初はやっぱりHelloWorldを出力してみます。

まずはPLSQLからの出力結果を表示するためにDBMSウィンドウを表示させます。
メニュー「表示」 > 「DBMS出力」 で表示します。

DBMSウィンドウの「+」ボタンをクリックし、接続先を選択します。

PLSQLでHelloWorld

「ワークシート」に下記のPLSQLを入力し、三角ボタンより実行します。
DBMSウィンドウに「Hello World」が出力されれば成功です。
PLSQL
DECLARE

BEGIN

    SYS.DBMS_OUTPUT.PUT_LINE('Hello World');

END;

PLSQL SQL Developer からテーブル作成

前回「PLSQL 自宅でPLSQL実行環境を作成する」でPLSQLの実行環境を作成しましたが、データベースにテーブルがないとPLSQLを実行するのに不便なので、今回はSQL Developerからデータベースにテーブルを作成します。

「接続」よりデータベースを選択します
「ワークシート」にCreateTable文を記述し、三角ボタンで実行します。


実行したSQL
Create Table Member
(
 MemberId VARCHAR2(4)  NOT NULL ,
 MemberName VARCHAR2(10) ,
 Rank  VARCHAR2(2)  
);
Alter Table Member
Add Primary Key (MemberId)
;

次回以降はこのテーブルを使用してPLSQLを実行していきたいと思います。

PLSQL 自宅でPLSQL実行環境を作成する

PLSQLを習得する必要が出てきたので、自宅に実行環境を用意します。

Oracle Database Express Edition 11g Release 2

無償で使えるOracleデータベース「Oracle Database Express Edition 11g Release 2」をインストールします。

Oracle Database Express Edition 11g Release 2 は無料で使用できる代わりに、下記のような制限があります。
  • データサイズ4GBまでであること
  • サービスネーム(データベース名)が「XE」の固定であること
  • 内部エンコードがUTF-8(AL32UTF8)の固定であること

下記サイトよりダウンロード
http://www.oracle.com/technetwork/jp/database/database-technologies/express-edition/downloads/index.html
ダウンロードにはユーザー登録が必要です。
私はWindows64bitOSなので「Oracle Database Express Edition 11g Release 2 for Windows x64」を選択しました。

ダウンロードしたファイルを実行し、インストール
※基本はデフォルトのままでOK


デフォルトインスタンスのSYS,SYSTEMユーザのパスワード入力



実行確認
スタート > すべてのプログラム > Oracle Database 11g Express Edition > SQLコマンドラインの実行 で「SQLコマンドラインの実行」を起動します。
「connect / as sysdba」と入力し、接続できれば成功です。


SQL Developer

つづいてOracleをGUIで操作できるSQL Developerを入手します。
下記サイトよりダウンロード
http://www.oracle.com/technetwork/jp/developer-tools/sql-developer/downloads/index.html
私はWindows64bitOSなので「Windows 64-bit with JDK 8 included」を選択しました。

ダウンロードしたファイルを解凍したフォルダ内に「sqldeveloper.exe」があるので、実行します。
プラスマークよりDB接続を追加します。


接続名:何でもOK
ユーザー名:system
パスワード:Oracleをインストールしたときに設定したパスワード

「テスト」ボタンを押して、ステータス:成功と出たら接続ボタン押下します。

とりあえずこれで、PLSQLを実行できる環境ができました。