Oracle PL/SQL メモ


記憶が微かになってきたので、再学習したことのメモ。
覚えては忘れて検索するので。。

serveroutput

serveroutputを有効にしないと、dbms_output.put_lineの出力がコンソールに出力されない。たまに忘れててはまる。。

set serveroutput [on | off] 

on じゃなくて、バッファサイズや、UNLIMITEDを指定もできる。
昔(9iぐらい)は、MAXのバッファサイズを指定していた。いつからか制限なしも選べるようになってて驚いた。出力が多すぎても、問題になることがあるので程々がいいのかな。。

コメントのつけ方

  • 単一行コメント:--
-- 単一行コメント
  • 複数行コメント:/* */
/*
    複数行コメント1
    複数行コメント2 
*/

変数宣言

変数名 データ型 [NOT NULL] [{ := | DEFAULT } <値> };

--例
v_no NUMBER(2);
v_no NUMBER(2) := 10;
v_no NUMBER(2) NOT NULL := 0;
v_no NUMBER(2) NOT NULL DEFAULT 1;
  • NOT NULL を指定した場合、初期値も設定する。
  • データ型のサイズを基本指定する。しないとデフォルト値?。VARCHAR2はサイズ指定が必要

定数宣言

定数名 CONSTANT データ型 [NOT NULL] [{ := | DEFAULT } <初期値> };

制御文

IF文

IF <条件式> THEN

ELSIF <条件式> THEN

ELSE

END IF;

CASE文

CASE文

CASE <選択子式>
  WHEN <条件式1> THNE
  WHEN <条件式2> THNE
  ELSE
END CASE;

--例
declare
  val number(1) := 1;
begin
  case val
    when 0 then
      DBMS_OUTPUT.PUT_LINE('CASE0');
    when 1 then
      DBMS_OUTPUT.PUT_LINE('CASE1');
    else
      DBMS_OUTPUT.PUT_LINE('CASE ELSE');
  end case;
end;

  • ELSEは必須
  • 選択子式は、文字でも数でも大丈夫

検索CASE文

CASE
  WHEN <条件式1> THNE
  WHEN <条件式2> THNE
  ELSE
END CASE;

--例
declare
  val varchar2(2) := 'AA';
begin
  case
    when val = 'AA' then
      DBMS_OUTPUT.PUT_LINE('CASEAA');
    when val = 'BB' then
      DBMS_OUTPUT.PUT_LINE('CASEBB');
    else
      DBMS_OUTPUT.PUT_LINE('CASE ELSE');
  end case;
end;
  • 検索CASE文かIF文を使うかは、好み

LOOP文

基本LOOP文

種類 概要
EXIT文 無条件にLOOPを終了
EXIT WHEN文 LOOP文の終了条件を記述、条件を満たせば終了
LOOP

END LOOP;

-- 例
declare
  val number(2) := 0;
begin
  loop
    exit when val = 2;
    dbms_output.put_line(val);
    val := val + 1;
  end loop;
end;

FOR-LOOP文

FOR <ループカウンタ> IN [REVERSE] <初期値> .. <終了値> LOOP

END LOOP;

--例
begin
  for i in 1..3 loop
    dbms_output.put_line(i);
  end loop;
end;

ループカウンタについて

  • 宣言部で定義不要
  • そのループ文の中でのみ参照可能

WHILE-LOOP文

WHILE <条件式> LOOP

END LOOP;

--例

declare
  val number(1) := 0;
begin
  while val < 3 loop
    dbms_output.put_line(val);
    val := val + 1;
  end loop;
end;

CONTINUE文

Oracle11g R11.1から追加された機能
条件式を満たすときだけ、処理を中断し、LOOPの最初に移動する。

CONTINUE WHEN <条件式>

--例

begin
  for i in 0..5 loop
    CONTINUE WHEN i = 2;
    dbms_output.put_line(i);
  end loop;
end;

--実行結果
0
1
3
4
5

NULL文

何もしないことを明確にしたいときに使う

--例1
declare
  val number(1) := 0;
begin
  if val = 0 then
    dbms_output.put_line('hoge');
  else
    null;
  end if;
end;

--例2
declare
  val number(1) := 0;
begin
  null;
end;

SELECT INTO文、カーソル文

よく使う

  • SELECT INTO文
    検索データが1行の時に使う。
  • カーソル文
    検索データが複数行の時に使う。

SELECT INTO文

SELECT <列名> [,<>] INTO <変数> [,<>] FROM <表名>;

--例 行が複数返却される
declare
  m_no member.no%type;
  m_name member.name%type;
begin
  select no, name into m_no, m_name from member;
exception 
  when others then
    dbms_output.put_line(sqlerrm);
end;
-- 結果
ORA-01422: 完全フェッチがリクエストよりも多くの行を戻しました

--例 0件
declare
  m_no member.no%type;
  m_name member.name%type;

begin

  select no, name into m_no, m_name from member where no is null;

exception 
  when others then
    dbms_output.put_line(sqlerrm);

end;
--結果
ORA-01403: データが見つかりません。

-- 例 正常終了
declare
  m_no member.no%type;
  m_name member.name%type;

begin

  select no, name into m_no, m_name from member where no = '000001';

exception 
  when others then
    dbms_output.put_line(sqlerrm);

end;


SELECT INTO文で使用したmemberテーブルは以下のとおり。

NO NAME CLASS_CODE AGE
000001 田中 太郎 1 18
000002 轟 太郎 1 18
000004 1 18
000003 伊藤 太郎 1 18
  • 問合せ結果は必ず1行にする。
    複数行:TOO_MANY_ROWS例外(ORA-01422)が発生する
    0件:NO_DATA_FOUND例外(ORA-01403)が発生する
  • 列と変数名は一致させる。

カーソル文

カーソルの使い方

declare
  ①カーソルの定義
  CURSOR <カーソル名> IS <SELECT文>;
begin
  ②カーソルのオープン
  OPEN <カーソル名>; -- SQLが実行され、結果セット(SQLの検索結果)ができている
  loop
    ③データの取り出し --結果セットからデータを1件ずつ取り出す。
    FETCH <カーソル名> INTO <変数名>; 
  end loop;
  ④カーソルのクローズ
  CLOSE <カーソル名>;
end;
--例
declare
  cursor cs is select no, name from member;
  -- レコード型の定義
  type rec is record (
  wk_no member.no%type,
  wk_name member.name%type
  );
  -- レコード型の宣言
  wk_rec rec;
begin
  open cs;
  loop
    fetch cs into wk_rec;
    exit when cs%notfound;
      dbms_output.put_line('['||wk_rec.wk_no||'].['||wk_rec.wk_name||']');

  end loop;
  close cs;
exception
  when others then
    dbms_output.put_line(SQLERRM);
end;

カーソル変数

  • 特定のSELECT文しか実行できないカーソルとは違い、動的なSQLを実行できる。
  • データ型の1つである「FOR CURSOR型」で定義した変数を、カーソル変数という。

書き方

  1. カーソル変数の定義
  2. OPEN FOR文の実行
  3. カーソル処理

宣言

1. REF CURSOR型の定義
TYPE <カーソル型> IS REF CURSOR;

2. カーソルの定義
<カーソル変数名> <カーソル型>;

実行

カーソル変数に、問合せ文を紐づける

OPEN <カーソル変数名> FOR <問合せ文>;
例1:基本
declare
  type cs is ref cursor;
  wk_cs cs;

  mem member%rowtype;
  i number(2) := 0;
begin
  open wk_cs for select * from member;
  loop
    fetch wk_cs into mem;
    i := i + 1;
    exit when wk_cs%notfound;
    dbms_output.put_line(i||':'||mem.no);
    dbms_output.put_line(i||':'||mem.name);
  end loop;
  close wk_cs;
exception
  when others then
    dbms_output.put_line(SQLERRM);

end;
--結果
1:000001
1:田中 太郎
2:000002
2:轟 太郎
3:000004
3:
4:000003
4:伊藤 太郎
例2:パラメータを使用したカーソル
declare
  type csPPP is ref cursor;
  cs_p csPPP;
  cRec classMaster%rowtype;

  i number(2) := 0;

  cursor csSSS( code in varchar2 ) is
    select
       no
      ,name
      ,class_code
    from member
    where
      class_code = code;
begin
  open cs_p for select * from classMaster;
  loop
    fetch cs_p into cRec;
    exit when cs_p%notfound;
      i := i + 1;
      dbms_output.put_line(i||':'||cRec.class_code||':'||cRec.class_name);

      for cs_s in csSSS(cRec.class_code) loop
        dbms_output.put_line(cRec.class_code||':'||cs_s.no);
        dbms_output.put_line(cRec.class_code||':'||cs_s.name);
      end loop;
  end loop;
  close cs_p;
exception
  when others then
    dbms_output.put_line(SQLERRM);
end;

--結果
1:1:一般社員
1:000001
1:田中 太郎
1:000002
1:轟 太郎
1:000004
1:
1:000006
1:岡 太郎
2:2:課長
2:000003
2:伊藤 太郎
3:3:部長

レコード、コレクション

レコード

  • %ROWTYEP:既存表の定義を使った、レコード変数
  • ユーザ定義レコード:ユーザが定義したレコード変数

ユーザ定義レコード

特徴

  • フィールドのデータ型に、%TYPE,%ROWTYPEを使える
  • フィールドにNOT NULLを使える
  • フィールドにデフォルト値を指定できる
  • 型の定義、変数の宣言が必要
1. レコード型の定義
TYEP <レコード型名> IS RECORD
(
  <フィールド名> <データ型> [ NOT NULL {:= | DEFAULT} <デフォルト値>]
  [, <>]
);
2. 変数の定義
<レコード名> <レコード型名>

レコード型の使い方

同じレコード型の変数同士は、そのまま代入できる

レコード変数1.項目名 := レコード変数2.項目名 のように1つ1つ代入しなくてもいい

declare
  cursor cs is select no, name from member where rownum = 1;
  -- レコード型の定義
  type def_rec is record (
  wk_no member.no%type,
  wk_name member.name%type
  );
  -- レコード型の宣言
  rec1 def_rec;
  rec2 def_rec;
begin
  open cs;
  loop
    fetch cs into rec1;
    exit when cs%notfound;
      dbms_output.put_line('rec1['||rec1.wk_no||'].['||rec1.wk_name||']');
  end loop;
  close cs;
  rec2 := rec1;
  dbms_output.put_line('rec2['||rec2.wk_no||'].['||rec2.wk_name||']');
exception
  when others then
    dbms_output.put_line(SQLERRM);
end;
-- 結果
rec1[000001].[田中 太郎]
rec2[000001].[田中 太郎]

パラメータ、ファンクションの戻り値に使える

create or replace procedure printRecord( rec member%rowtype)
is
begin
  dbms_output.put_line(rec.no||'.'||rec.name);

end;

declare
  row member%rowtype;
begin
  select * into row from member where rownum = 1;
  printRecord(row);

end;

--結果
000001.田中 太郎

挿入と、更新

挿入
INSERT INTO <テーブル名> VALUES <レコード名>
  • フィールドの項目属性は相互でそろえる
declare
  row1 member%rowtype;
  row2 member%rowtype;

begin
  select * into row1 from member where rownum = 1;

  row1.no := '000006';
  insert into member values row1;
  commit;

  select * into row2 from member where no = '000006';
  dbms_output.put_line('no:'||row2.no);
  dbms_output.put_line('name:'||row2.name);
  dbms_output.put_line('class_code:'||row2.class_code);

end;

--結果
no:000006
name:田中 太郎
class_code:1
更新
UPDATE <表名> SET ROW = <レコード名>;
ROWの注意点
  • 副問合せと一緒には使えない
  • 許可されるSET句は1つのみ
declare
  row1 member%rowtype;
  row2 member%rowtype;
begin
  select * into row1 from member where no = '000006';

  row1.name := '岡 太郎';
  update member set row = row1 where no = '000006';
  commit;

  select * into row2 from member where no = '000006';
  dbms_output.put_line('no:'||row2.no);
  dbms_output.put_line('name:'||row2.name);
  dbms_output.put_line('class_code:'||row2.class_code);

end;

-- 結果
no:000006
name:岡 太郎
class_code:1

コレクション

レコード型と、コレクション型の違い。

レコード型

フィールド名1 フィールド名2

コレクション型

索引 要素
1
2
n

コレクションの種類

  • 結合配列(=索引付き表、PL/SQL表)
  • ネストした表
  • VARRAY

結合配列の使用が一般的らしい。

結合配列

結合配列を使用するためには以下の2段階を踏む。

  1. 型の定義
  2. 変数の定義
宣言
型の定義

TABLE型を定義する。

TYPE <テーブル型> IS TABLE OF <データ型> [NOT NULL]
  INDEX BY {
    BINARY_INTEGER | PLS_INTEGER | <文字データ型> (<上限サイズ>)
  };

BINARY_INTEGERと、PLS_INTEGERは同じものらしい。
範囲は、「-2,147,648,467〜2,147,648,467」
PLS_INTEGERおよびBINARY_INTEGERデータ型

結合配列の定義

定義したテーブル型と結合配列を紐づける。

<結合配列名> <テーブル型名>;
使用方法

結合配列の要素を参照するには、索引番号を使う。
要素に値を代入するには、代入演算子(:=)や、SELECT INTO や、FETCH INTO を使う。

<結合配列名> (<索引番号>);
declare
  type tab is table of varchar2(10) index by pls_integer;
  wk_tab tab;

begin
  wk_tab(1) := 'hoge1';
  wk_tab(2) := 'hoge2';
  wk_tab(3) := 'hoge3';

  for i in 1..3 loop
    dbms_output.put_line(wk_tab(i));
  end loop;

exception
  when others then
    dbms_output.put_line(SQLERRM);

end;

---結果
hoge1
hoge2
hoge3

データ型には、ユーザ定義レコード型や%ROWTYPEが使える
要素へのアクセスは

<結合配列名>(<索引番号>).<フィールド名>

declare
  type myrec is record
  (
    no member.no%TYPE,
    temp varchar2(20)
  );

  wkrec myrec;

  type tab is table of myrec index by pls_integer;
  wk_tab tab;

begin
  wk_tab(1).no := '000009';
  wk_tab(1).temp := 'hoge1';
  wk_tab(2).no := '000010';
  wk_tab(2).temp := 'hoge2';

  for i in 1..2 loop
    dbms_output.put_line(wk_tab(i).no);
    dbms_output.put_line(wk_tab(i).temp);
  end loop;

exception
  when others then
    dbms_output.put_line(SQLERRM);

end;

--結果
000009
hoge1
000010
hoge2

declare

  type tab is table of number(2) index by varchar2(15);
  wk_tab tab;

begin
  wk_tab('AA') := 1;
  wk_tab('BB') := 2;

  dbms_output.put_line(wk_tab('AA'));
  dbms_output.put_line(wk_tab('BB'));

exception
  when others then
    dbms_output.put_line(SQLERRM);

end;

--結果
1
2

無名ブロック

とっても便利

基本構文

declare --宣言部
begin -- 実行部
exception -- 例外処理
end

  • 実行部のbegin~end;が最低限あればよい
begin
   null;
end;
  • 実行部にネストができる
    ネストの上限は、255個らしい。
declare

begin
  declare

  begin

  end;
end;

ストアド・サブプログラム

メモ

NOCOPYオプション

プロシージャで、OUT/IN OUTモードのパラメータにNOCOPYオプションを指定すると、通常は引数がメモリにコピーされて使用されるのを、アドレス参照にすることができるらしい。
大量のデータをやり取りする場合は、便利かも。

--呼び側
PROCEDURE xxx(p OUT NOCOPY tab);

--呼ばれる側
PROCEDURE xxx(p OUT NOCOPY tab) IS