PL/SQL うっかりミス集

PL/SQL の触り始めに苦労するのが、エラー文の癖の強さです。

PL/SQL 自体が一般的なプログラミング言語と構文の感覚が違っていることもあって、慣れるまではエラーを出力されても間違いがどこにあるのか判りづらい…。

中でも地味に、しかし頻繁に時間を奪われるのが細かいエラーへの対処。

誤字やセミコロンの付け間違いのようなうっかりミスで、総合してどれだけの時間を奪われたことか。

この記事では、そういうしょぼい例を列挙していきます。

環境
----

- Oracle 11g
- SQL Developer
  - 日本語環境

事例
----

### `IF` 文のあたりでエラーがある

#### ???

~~~sql:SQL
BEGIN
  IF TRUE THEN
    DBMS_OUTPUT.PUT_LINE('Hello!');
  END;
END;
/
~~~

~~~text:ErrorReport
ORA-06550: 行4、列6:
PLS-00103: 記号";"が見つかりました。 次のうちの1つが入るとき:

   if
記号"if" は続行のために";"に代わりました。
06550. 00000 -  "line %s, column %s:\n%s"
*Cause:    Usually a PL/SQL compilation error.
*Action:
~~~

#### !!!

`IF` を閉じるときは `END IF` 。

~~~sql:SQL
BEGIN
  IF TRUE THEN
    DBMS_OUTPUT.PUT_LINE('Hello!');
  END IF;
END;
/
~~~



### `IF` 文のあたりでエラーがある 2

#### ???

~~~sql:SQL
BEGIN
  IF TRUE THEN
    -- あとで書く
  END IF;
END;
/
~~~

~~~text:ErrorReport
ORA-06550: 行4、列3:
PLS-00103: 記号"END"が見つかりました。 次のうちの1つが入るとき:

   ( begin case declare exit for goto if loop mod null pragma
   raise return select update while with <an identifier>
   <a double-quoted delimited-identifier> <a bind variable> <<
   continue close current delete fetch lock insert open rollback
   savepoint set sql execute commit forall merge pipe purge
06550. 00000 -  "line %s, column %s:\n%s"
*Cause:    Usually a PL/SQL compilation error.
*Action:
~~~

#### !!!

`IF` 文の中になんでもいいから処理を書く。

ちなみに `BEGIN` ～ `END` も同様。

~~~sql:SQL
BEGIN
  IF TRUE THEN
    -- あとで書く
    NULL;
  END IF;
END;
/
~~~

`NULL` は何もしない処理。



### 見えないものが見えると言われ、無視していないものを無視していると詰め寄られる

#### ?

~~~sql:SQL
BEGIN
　DBMS_OUTPUT.PUT_LINE('Hello!');
END;
/
~~~

~~~text:ErrorReport
ORA-06550: 行2、列3:
PLS-00103: 記号""が見つかりました。 次のうちの1つが入るとき:

   ( begin case declare exit for goto if loop mod null pragma
   raise return select update while with <an identifier>
   <a double-quoted delimited-identifier> <a bind variable> <<
   continue close current delete fetch lock insert open rollback
   savepoint set sql execute commit forall merge pipe purge
記号"" は無視されました。
06550. 00000 -  "line %s, column %s:\n%s"
*Cause:    Usually a PL/SQL compilation error.
*Action:
~~~

#### !

インデントが全角スペースだった。

~~~sql:SQL
BEGIN
  DBMS_OUTPUT.PUT_LINE('Hello!');
END;
/
~~~



### 関数がうまくコンパイルされない

#### ???

~~~sql:SQL
CREATE OR REPLACE PROCEDURE proc1
IS
BEGIN
  DBMS_OUTPUT.PUT_LINE('in proc1');
END proc1;

BEGIN
  proc1;
END;
/
~~~

~~~text:CompilerLog
Error(6,1): PLS-00103: 記号"BEGIN"が見つかりました。 
~~~

#### !!!

プロシージャ定義の後にスラッシュが抜けてた。

~~~sql:SQL
CREATE OR REPLACE PROCEDURE proc1
IS
BEGIN
  DBMS_OUTPUT.PUT_LINE('in proc1');
END proc1;
/

BEGIN
  proc1;
END;
/
~~~



### 関数がうまくコンパイルされない 2

#### ???

~~~sql:SQL
CREATE OR REPLACE PROCEDURE coolProc()
IS
BEGIN
  DBMS_OUTPUT.PUT_LINE('in coolProc');
END coolProc;
/

BEGIN
  coolProc();
END;
/
~~~

~~~text:CompilerLog
Error: PLS-00103: 記号")"が見つかりました。 次のうちの1つが入るとき:     <an identifier> <a double-quoted delimited-identifier>    current delete exists prior 
~~~

#### !!!

引数なし関数を定義するときに括弧を付けてはいけない。

~~~sql:SQL
CREATE OR REPLACE PROCEDURE coolProc
IS
BEGIN
  DBMS_OUTPUT.PUT_LINE('in coolProc');
END coolProc;
/

BEGIN
  coolProc();
END;
/
~~~

呼び出すときは括弧を付けてもいい。



### 関数がうまくコンパイルされない 3

#### ???

~~~sql:SQL
CREATE OR REPLACE PROCEDURE writePrefix(
    text VARCHAR2
    )
IS
  prefix VARCHAR2;
BEGIN
  prefix := SUBSTR(text, 1, 1);
  DBMS_OUTPUT.PUT_LINE(text || 'の接頭辞は' || prefix);
END writePrefix;
/

BEGIN
  writePrefix('テキスト');
END;
/
~~~

~~~text:ErrorReport
ORA-06550: 行2、列3:
PLS-00905: オブジェクト'TESTUSER.WRITEPREFIX'が無効です。
ORA-06550: 行2、列3:
PL/SQL: Statement ignored
06550. 00000 -  "line %s, column %s:\n%s"
*Cause:    Usually a PL/SQL compilation error.
*Action:
~~~

~~~text:CompilerLog
Error(3,10): PLS-00215: 文字列の長さは制約範囲内(1から32767)である必要があります
~~~

#### !!!

引数の場合、長さの設定が必要な型でも設定は禁止だが、それ以外の変数では長さの設定が必要。

~~~sql:SQL
CREATE OR REPLACE PROCEDURE writePrefix(
    text VARCHAR2
    )
IS
  prefix VARCHAR2(3);
BEGIN
  prefix := SUBSTR(text, 1, 1);
  DBMS_OUTPUT.PUT_LINE(text || 'の接頭辞は' || prefix);
END writePrefix;
/

BEGIN
  writePrefix('テキスト');
END;
/
~~~



### ファンクションまわりでなんかおかしい

#### ???

~~~sql:SQL
CREATE OR REPLACE FUNCTION func1
    RETURN NUMBER
IS
BEGIN
  RETURN(1);
END func1;
/

BEGIN
  func1;
END;
/
~~~

~~~text:ErrorReport
ORA-06550: 行2、列3:
PLS-00221: 'FUNC1'がプロシージャではないか、または未定義です。
ORA-06550: 行2、列3:
PL/SQL: Statement ignored
06550. 00000 -  "line %s, column %s:\n%s"
*Cause:    Usually a PL/SQL compilation error.
*Action:
~~~

#### !!!

ファンクションの戻り値は、呼び出し側で受け取る必要がある。

~~~sql:SQL
CREATE OR REPLACE FUNCTION func1
    RETURN NUMBER
IS
BEGIN
  RETURN(1);
END func1;
/

DECLARE
  n NUMBER;
BEGIN
  n := func1;
END;
/
~~~



### パッケージの定義がうまくいかない

#### ???

~~~sql:SQL
CREATE OR REPLACE PACKAGE package1
IS
  PROCEDURE proc1(text VARCHAR2);
END package1;
/

CREATE OR REPLACE PACKAGE BODY package1
IS
  PROCEDURE proc1(text VARCHAR2);
  IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE(text);
  END proc1;
END package1;
/

BEGIN
  package1.proc1('テキスト');
END;
/
~~~

~~~text:CompilerLog
Error(3,3): PLS-00103: 記号"IS"が見つかりました。 次のうちの1つが入るとき:     begin end function pragma procedure subtype type    <an identifier> <a double-quoted delimited-identifier>    current cursor delete exists prior 
Error(7,5): PLS-00103: 記号"PACKAGE1"が見つかりました。 次のうちの1つが入るとき:     ; 
~~~

#### !!!

`PACKAGE BODY` 内のプロシージャの引数定義直後にセミコロンが入ってた。

~~~sql:SQL
CREATE OR REPLACE PACKAGE package1
IS
  PROCEDURE proc1(text VARCHAR2);
END package1;
/

CREATE OR REPLACE PACKAGE BODY package1
IS
  PROCEDURE proc1(text VARCHAR2)
  IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE(text);
  END proc1;
END package1;
/

BEGIN
  package1.proc1('テキスト');
END;
/
~~~



### パッケージの定義がうまくいかない 2

#### ???

~~~sql:SQL
CREATE OR REPLACE PACKAGE package1
IS
  PROCEDURE proc1(
      description VARCHAR2
      );
END package1;
/

CREATE OR REPLACE PACKAGE BODY package1
IS
  PROCEDURE proc1(
      text VARCHAR2
      )
  IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE(text);
  END proc1;
END package1;
/
~~~

~~~text:CompilerLog
Error(2,13): PLS-00323: パッケージ仕様部で未定義のサブプログラムまたはカーソルPROC1が宣言されています。
~~~

#### !!!

`PACKAGE` と `PACKAGE BODY` の関数定義で、違う引数名を使ってはいけない。

~~~sql:SQL
CREATE OR REPLACE PACKAGE package1
IS
  PROCEDURE proc1(
      description VARCHAR2
      );
END package1;
/

CREATE OR REPLACE PACKAGE BODY package1
IS
  PROCEDURE proc1(
      description VARCHAR2
      )
  IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE(description);
  END proc1;
END package1;
/
~~~



### オーバーライドがうまくいかない

#### ???

~~~sql:SQL
CREATE OR REPLACE PACKAGE package1
IS
  PROCEDURE proc1(
      rec EXAMPLE_WORK%ROWTYPE
      );
      
  PROCEDURE proc1(
      rec EXAMPLE_EMPLOYEE%ROWTYPE
      );
END package1;
/

CREATE OR REPLACE PACKAGE BODY package1
IS
  PROCEDURE proc1(
      rec EXAMPLE_WORK%ROWTYPE
      )
  IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE(rec.WORK_NAME);
  END proc1;
      
  PROCEDURE proc1(
      rec EXAMPLE_EMPLOYEE%ROWTYPE
      )
  IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE(rec.EMPLOYEE_NAME);
  END proc1;
END package1;
/

BEGIN
  FOR rec IN (
      SELECT * FROM EXAMPLE_WORK
      ) LOOP
    package1.proc1(rec);
  END LOOP;
END;
/
~~~

~~~text:ErroReport
ORA-06550: 行5、列5:
PLS-00307: このコールに一致する'PROC1'が複数宣言されています。
ORA-06550: 行5、列5:
PL/SQL: Statement ignored
06550. 00000 -  "line %s, column %s:\n%s"
*Cause:    Usually a PL/SQL compilation error.
*Action:
~~~

#### !!!

テーブルの列構造が同じ。

- EXAMPLE_WORK

| COLUMN_NAME | DATA_TYPE    |
|-------------|--------------|
| ID          | NUMBER       |
| WORK_NAME   | VARCHAR(20)  |

- EXAMPLE_EMPLOYEE

| COLUMN_NAME | DATA_TYPE    |
|-------------|--------------|
| ID          | NUMBER       |
| WORK_NAME   | VARCHAR(100) |

`%ROWTYPE` や `FOR` で扱う行オブジェクトは、テーブルの列構造のみを真似る。テーブルの名前や制約等の情報はもたない。

実行環境によっては、列構造かぶりのオーバーライドでもエラーを吐かずに実行可能だが、その場合は先に定義した方の関数がしれっと実行される。



### テーブルの値をうまく処理できない

#### ???

~~~sql
DECLARE
  TYPE EXAMPLE_EMPLOYEE_TYPE IS RECORD(
      EMPLOYEE_NAME VARCHAR(20)
     ,ID NUMBER(20)
      );

  CURSOR cur
  IS
      SELECT
        *
      FROM
        EXAMPLE_EMPLOYEE
      ;

  employee_rec EXAMPLE_EMPLOYEE_TYPE;
BEGIN
  OPEN cur;
  LOOP
    FETCH cur INTO employee_rec;
    EXIT WHEN cur%NOTFOUND;

    DBMS_OUTPUT.PUT_LINE(
        'name: ' || employee_rec.EMPLOYEE_NAME);
  END LOOP;
  CLOSE cur;
END;
/
~~~

~~~text:ErroReport
ORA-06502: PL/SQL: 数値または値のエラー: 文字から数値への変換エラー。が発生しました
ORA-06512: 行19
06502. 00000 -  "PL/SQL: numeric or value error%s"
*Cause:    An arithmetic, numeric, string, conversion, or constraint error
           occurred. For example, this error occurs if an attempt is made to
           assign the value NULL to a variable declared NOT NULL, or if an
           attempt is made to assign an integer larger than 99 to a variable
           declared NUMBER(2).
*Action:   Change the data, how it is manipulated, or how it is declared so
           that values do not violate constraints.
~~~

#### !!!

テーブル `EXAMPLE_EMPLOYEE` と型 `EXAMPLE_EMPLOYEE_TYPE` の列の並びが違う。

- EXAMPLE_EMPLOYEE

| COLUMN_NAME   | DATA_TYPE     | COLUMN_ID  |
|---------------|---------------|------------|
| ID            | NUMBER        | 1          |
| EMPLOYEE_NAME | VARCHAR(100)  | 2          |