PL/SQLの変数スコープとか制御とか

いっつも忘れて分からなくなるのでまとめてみた。

# public なスコープ #
当然だけど、Package内に定義している変数などは、他のProcedure/Function/Packageなどから参照可能。

```sql:PACKAGE_TEST_Header
CREATE OR REPLACE PACKAGE PACKAGE_TEST
IS
  G_VALUE VARCHAR2(100) := 'test';
END;
/
```

```sql:PRCEODURE_TEST
CREATE OR REPLACE PROCEDURE PRCEODURE_TEST
IS
BEGIN
  DBMS_OUTPUT.PUT_LINE(PACKAGE_TEST.G_VALUE);
END;
/
```

```console:結果
【 DBMS_OUTPUT 値 】
test
```

# 一度きりだけ走行する処理 #
PakcageBody直下内のBeginを記述すると、セッション単位に、Package内のいずれかのProcedure/Functionが呼び出された時の一度だけ走行する。

```sql:Package_Body
CREATE OR REPLACE PACKAGE BODY PACKAGE_TEST
IS
  G_VALUE VARCHAR2(100);
  G_VALUE2 VARCHAR2(100);
  
  PROCEDURE SET_VALUE
  IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE('SET_VALUE の実行');
    G_VALUE := 100 + DBMS_RANDOM.VALUE(1, 100);
    G_VALUE2 := 100 + DBMS_RANDOM.VALUE(1, 100);
  END;
  
  PROCEDURE OUTPUT_VALUE
  IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE('OUTPUT_VALUE の実行');
    DBMS_OUTPUT.PUT_LINE(G_VALUE);
    DBMS_OUTPUT.PUT_LINE(G_VALUE2);
  END;
BEGIN
  DBMS_OUTPUT.PUT_LINE('一度だけ走行');
END;
/
```

PACKAGE_TEST.SET_VALUE, OUTPUT_VALUEを初回実行すると

```console:結果
【 DBMS_OUTPUT 値 】
一度だけ走行
SET_VALUE の実行
```

```console:結果
【 DBMS_OUTPUT 値 】
一度だけ走行
OUTPUT_VALUE の実行
```

ってなる。
同一セッション内で、二回目以降の実行は、『一度だけ走行』は出なくなる。

```console:結果
【 DBMS_OUTPUT 値 】
SET_VALUE の実行
```

```console:結果
【 DBMS_OUTPUT 値 】
OUTPUT_VALUE の実行
147.231114988875585617262455129890355607
116.325296511190334966424271757893484029
```

# セッション内で生き続ける変数 #
Package内、PackageBody直下内で定義している変数などは、同一セッション内で常に有効。
Package内にあるからといって、Package内のProcedureが走行し終わったらメモリ解放されるわけではない。
プログラミング言語でいえば、以下のような感じ。

|Oracle      |プログラムイメージ|
|----------|---|
|セッション |プロセス|
|変数      |インスタンス化されたオブジェクトのpublicスコープフィールド<br>または<br>public staticフィールド|

```sql:PACKAGE_TEST_Header
CREATE OR REPLACE PACKAGE PACKAGE_TEST
IS
  G_VALUE VARCHAR2(100);

  PROCEDURE SET_VALUE;
  
  PROCEDURE OUTPUT_VALUE;
END;
/
```

```sql:PACKAGE_TEST_Body
CREATE OR REPLACE PACKAGE BODY PACKAGE_TEST
IS
  G_VALUE2 VARCHAR2(100);
  
  PROCEDURE SET_VALUE
  IS
  BEGIN
    G_VALUE := 100 + DBMS_RANDOM.VALUE(1, 100);
    G_VALUE2 := 100 + DBMS_RANDOM.VALUE(1, 100);
  END;
  
  PROCEDURE OUTPUT_VALUE
  IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE(G_VALUE);
    DBMS_OUTPUT.PUT_LINE(G_VALUE2);
  END;
END;
/
```

1. OUTPUT_VALUE実行
2. SET_VALUE実行
3. OUTPUT_VALUE実行  

```console:3.OUTPUT_VALUEを実行した結果
【 DBMS_OUTPUT 値 】
178.710590796449652958964852862452749078
189.197685205020329282152319349008719927
```

このことから、public なスコープは、自然とセッション内で生き続ける変数になるので、定数以外の公開は避けた方が良いだろう。
変数に対してReadOnly、WriteOnlyというような制御がないため、このような変数の直接的な操作を行う必要があり、どうしても公開する必要がある場合、以下のように、変数を公開するのではなくアクセサを用意すると良いと思われる。

```sql:PACKAGE_TEST_Header
CREATE OR REPLACE PACKAGE PACKAGE_TEST
IS
  PROCEDURE SET_VALUE;
  
  PROCEDURE OUTPUT_VALUE;
  
  FUNCTION GET_G_VALUE RETURN VARCHAR2;
  
  FUNCTION GET_G_VALUE2 RETURN VARCHAR2;
END;
/
```

```sql:PACKAGE_TEST_Body
CREATE OR REPLACE PACKAGE BODY PACKAGE_TEST
IS
  G_VALUE VARCHAR2(100);
  G_VALUE2 VARCHAR2(100);
  
  FUNCTION GET_G_VALUE RETURN VARCHAR2
  IS
  BEGIN
    RETURN G_VALUE;
  END;
  
  FUNCTION GET_G_VALUE2 RETURN VARCHAR2
  IS
  BEGIN
    RETURN G_VALUE2;
  END;
  
  PROCEDURE SET_VALUE
  IS
  BEGIN
    G_VALUE := 100 + DBMS_RANDOM.VALUE(1, 100);
    G_VALUE2 := 100 + DBMS_RANDOM.VALUE(1, 100);
  END;
  
  PROCEDURE OUTPUT_VALUE
  IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE(GET_G_VALUE());
    DBMS_OUTPUT.PUT_LINE(GET_G_VALUE2());
  END;
END;
/
```

面倒かもしれないけど、明示的にアクセサを用意してあげることで、アクセサがない＝外からの変更を許していない、という意味付けができると思う。
GETはともかく、SETはPackageの内容に応じて臨機応変でいいと思う。
色んな人が開発に携わったり、メンテナンスを考えた時にどうあった方がいいかと考えて、ということが重要かと。

# Procedureの中にあるProcedure/Function #
Procedure内にProcedure/Functionを記述することも可能。
文面の関係上、インラインプロシージャと命名しよう！（名前知らないｗ）
インラインプロシージャは何階層もできるっぽい。（最大階層未確認）
これについては、自分より広いスコープの変数やProcedure/Functionの実行が可能。

```sql:Package_Body
CREATE OR REPLACE PACKAGE BODY PACKAGE_TEST
IS
  G_VALUE VARCHAR2(100);

  PROCEDURE OUTPUT
  IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE('OUTPUT');
  END;
  
  PROCEDURE PROC_TEST
  IS
    P_VALUE VARCHAR2(100);
    
    PROCEDURE PROC_TEST_INLINE
    IS
    BEGIN
      P_VALUE := 200;
      G_VALUE := 200 + DBMS_RANDOM.VALUE(1, 100);
      OUTPUT();
    END;
    
  BEGIN
    PROC_TEST_INLINE;
    
    DBMS_OUTPUT.PUT_LINE(G_VALUE);
    DBMS_OUTPUT.PUT_LINE(P_VALUE);
  END;
END;
/
```

```console:結果
【 DBMS_OUTPUT 値 】
OUTPUT
278.804077362976893092009880210639518591
200
```

同じ名前の変数が、インラインプロシージャ内、それより広いスコープのどちらでも定義されている場合、インラインプロシージャ内の変数が利用される。
広いスコープの変数を利用したい場合は、『Procedure名.変数名』などの記述を行う。（インラインに限った話じゃないけど・・・）

```sql:Package_Body
CREATE OR REPLACE PACKAGE BODY PACKAGE_TEST
IS
  P_VALUE VARCHAR2(100);
  
  PROCEDURE PROC_TEST
  IS
    P_VALUE VARCHAR2(100);
    
    PROCEDURE PROC_TEST_INLINE
    IS
      P_VALUE VARCHAR2(100);
    BEGIN
      P_VALUE := 200;
      PROC_TEST.P_VALUE := 300;
      PACKAGE_TEST.P_VALUE := 400;
    END;
    
  BEGIN
    PROC_TEST_INLINE;
    
    DBMS_OUTPUT.PUT_LINE('PROC_TEST.P_VALUE:' || P_VALUE);
    DBMS_OUTPUT.PUT_LINE('PACKAGE_TEST.PROC_TEST:' || PACKAGE_TEST.P_VALUE);
  END;
END;
/
```

```console:結果
【 DBMS_OUTPUT 値 】
PROC_TEST.P_VALUE:300
PACKAGE_TEST.PROC_TEST:400
```

# 変数の命名規則や実装ルールに悩む #
- 上記スコープを考える。
- テーブルカラム名と同一の変数名にすると、SQL文の記述とかで困る。
- 変数名は最大30文字。
- 大文字／小文字を区別しない。
- SQL文の最中に記述されることがあるので、変数であるということが分かりやすい方がいい。

そんなことを考えて、とりあえずこんなになった。
公式にコーディング規約があると助かるんだけど。

### 変数命名 ###
- Packageの変数（公開）  
  S_PublicVariable VARCHAR2(100)みたいな。
  - Pascal形式。
  - プリフィックス『S_』。  
- Packageの変数（非公開）  
  sPublicVariable VARCHAR2(100)みたいな。
  - Camel形式。
  - プリフィックス『s』。 
- Procedure/Functionの変数（インラインプロシージャへ公開）  
  V_ProcedureVariable VARCHAR2(100)みたいな。
  - Pascal形式。
  - プリフィックス『V_』。
- Procedure/Functionの変数（インラインプロシージャへ非公開）  
  vProcedureVariable VARCHAR2(100)みたいな。
  - Camel形式。
  - プリフィックス『v』。 
- Procedure/Functionの引数  
  aArgumentVariable IN VARCHAR2みたいな。
  - Camel形式。
  - プリフィックス『a』。 
- 共通
  - テーブルのカラム名に合わせる。  
    テーブルのカラム名が『USER_ID』だとしたら、『V_UserId』、『vUserId』とする。（アンダーバーを取り除いて、Pascal/Camel）
  - 極力システムハンガリアンを採用しない。  
    カーソルであることとかはCurとかを接尾辞にする。

### 書きっぷり ###
- 自分のスコープ内の変数を直接外部へ公開しない。
  - アクセサを用意する。  
  - 『Packageの変数（公開）』、『Procedure/Functionの変数（インラインプロシージャへ公開）』の禁止。  
    （命名しといてあれだけど、まずやっちゃいけない）
- インラインプロシージャは一階層のみ許可するが、極力避ける。
  - 変数、実装スコープの単純化。  
  - 基本的に責任範疇を明確にして、Package/Procedure/Functionの分割を検討する。

なんでも自由、臨機応変に解釈される、というのもルールがないとどんどん複雑化していってしまうので考えものですなぁ。