Stored Procedure

개요

Stored procedure는 사용자 업무 절차를 SQL을 통해 작성하여 메모리에 저장한 후 이를 호출하여 결과를 만들어내는 방식을 의미한다.
기본적으로 원격 서버에서 한 개의 트랜잭션 내에 다수의 API를 호출하여 발생하는 network I/O 비용을 고려할 때 procedure를 통해 호출하는 방법이 유리한 경우에 stored procedure를 사용할 것을 권장한다. 
지역 서버 내에서도 procedure를 호출할 수 있으나 내부적으로 SQL을 처리하는 과정에서 변수와 결과에 대한 많은 expression 처리 비용이 실제 API를 호출하는 비용보다 클 수 있기 때문에 단순한 operation에 대해서는 API에 비해 빠르지 않을 수 있음을 고려해야 한다.

기능

Procedure 내에 변수를 선언하거나 parameter를 사용할 수 있으며 다음과 같은 데이터 타입이 제공된다.

Data type

C/C++ Type

설명

int

int

4 bytes

long

long long

8 bytes

short

short

2 bytes

float

float

4 bytes

double

double

8 bytes

char

char

1 byte (MAX: 30 K)

TableName.ColumnName%TYPE

VariableName%TYPE

명시된 object 타입과 동일

Procedure 내에서 사전에 선언된 variable만 사용 가능

TableName%ROWTYPE

명시된 TableName의 구조와 동일

-

EXCEPTION

사용자 exception 정의

-

CURSOR

사용자 cursor 정의

Cursor declaration은 제공하지 않음

Ref cursor, cursor variable, user defined data type 등은 지원하지 않는다.

GOLDILOCKS LITE는 다음 표와 같이 procedure 내의 기능 구문을 지원한다.

기능

지원 여부

설명

LOOP

O

-

FOR LOOP

O

-

WHILE LOOP

O

-

IF

O

-

EXIT (WHEN)

O

-

CONTINUE (WHEN)

O

-

GOTO

O

-

SIMPLE CASE

O

-

SEARCHED CASE

O

-

EXCEPTION WHEN

O

-

ASSIGN

O

-

INSERT INTO

O

Array 형태를 제공하지 않음

UPDATE SET

O

Array 형태를 제공하지 않음

DELETE FROM

O

Array 형태를 제공하지 않음

SELECT ~ INTO

O

Array 형태를 제공하지 않음

OPEN <Cursor>

O

-

FETCH <Cursor> INTO

O

Array 형태를 제공하지 않음

CLOSE <Cursor>

O

-

DBMS_OUTPUT.PUT_LINE

O

화면 로그 출력용

Package 등은 지원하지 않는다.

GOLDILOCKS LITE에서 SQL과 cursor attribute 변수를 사용하기 위해 다음 표의 변수가 자동으로 생성되어 있다.

구분

이름

초기값

설명

SQL

attribute

SQLCODE

0

SQL 수행에 따른 에러가 발생하였거나 exception에 의해 에러가 발생한 경우, 에러 코드가 설정된다.

SQLERRM

NULL

SQL 수행에 따른 에러가 발생하였거나 exception에 의해 에러가 발생한 경우, 에러 코드가 설정된다.

SQL%ISOPEN

0

SQL 문에 어떤 결과든 한 건 이상 존재할 경우, 1로 설정된다.

SQL%FOUND

0

SQL 문에 의해 수행된 결과가 있을 경우, 1로 설정된다.

SQL%NOTFOUND

1

SQL 문에 의해 수행된 결과가 없을 경우, 1로 설정된다.

SQL%ROWCOUNT

0

SQL 문을 수행한 이후에 영향을 받은 row의 개수이다.

Cursor

attribute

CursorName%ISOPEN

0

CURSOR가 OPEN 된 경우 1로 설정되고, CLOSE 된 경우 0으로 설정된다.

CursorName%FOUND

0

CURSOR 수행 시점에 데이터가 존재하는 경우, 1로 설정된다.

CursorName%NOTFOUND

1

CURSOR 수행 시점에 데이터가 없는 경우, 1로 설정된다.

CursorName%ROWCOUNT

0

FETCH가 호출될 때마다 1씩 증가한다.

PROCEDURE DDL

본 장에서는 procedure를 생성하는 방법에 대해 설명한다. GOLDILOCKS LITE에서는 수행 중인 procedure에 대해 DDL을 수행할 수 있는데, 이 때 기존 procedure를 실행 계획으로 하여 이미 구동된 프로세스는 변경된 procedure 내용을 감지하지 않기 때문에 관련 응용 프로그램들을 모두 정지시킨 후에 해당 DDL을 수행해야 한다.

Create [or replace] Procedure

create_or_replace_procedure ::= CREATE [ OR REPLACE ] PROCEDURE proc_name
                                param_declare_section
                                IS
                                declare_section
                                proc_block
                                /
                                ;


proc_name ::= Procedure 이름


param_declare_section ::= ( variable_name [IN|OUT|INOUT] data_type [, ... ] )


declare_section ::= ( variable_name declare_type; [ ... ] )


variable_name ::= Procedure 내에서 사용할 변수의 이름

declare_type ::=    INT 
                  | SHORT  
                  | DOUBLE
                  | LONG
                  | FLOAT
                  | CHAR (size)
                  | table_name.column_Name%TYPE
                  | variable_name%TYPE
                  | table_name%ROWTYPE
                  | EXCEPTION


proc_block ::= BEGIN
                   proc_stmt
               END


proc_stmt ::= GOLDILOCKS LITE가 제공하는 procedure 구문들

다음은 procedure를 생성하는 예이다.

dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1 (a1 in int )
IS
c1 INT;
BEGIN
   a1 := c1;
END;
/
success
dbmMetaManager(DEMO)>
Procedure가 정상적으로 생성되었을 경우, 다음과 같은 방법을 사용하여 dictionary instance가 있는지 여부를 확인해 볼 수 있다.
dbmMetaManager(DEMO)> set instance dict;
success
dbmMetaManager(DICT)> select * from dic_procedure;
---------------------------------------------------------------------------------
INST_NAME            : DEMO
PROC_NAME            : PROC1
PIECE_COUNT          : 1
---------------------------------------------------------------------------------

1 row selected

dbmMetaManager의 -f 옵션을 통해서만 생성할 수 있다. 또한 CREATE ~ END; / 절에서 END 와 문장의 끝을 의미하는 / 의 사이나 / 이후에는 다른 문자를 허용하지 않는다.

Drop Procedure

기존에 있던 procedure를 제거한다. Drop procedure에 의해 제거된 procedure를 수행 중인 세션이 감지할 수 없기 때문에 응용 프로그램을 중지한 후에 procedure를 제거하는 것이 좋다.
DROP PROCEDURE ::= DROP PROCEDURE <procedure_name>;

procedure_name := 삭제할 procedure 이름
dbmMetaManager(DEMO)> DROP PROCEDURE proc1;
success

Procedure View

Procedure의 정보를 확인하기 위해 다음의 두 view들을 참조할 수 있다.

dbm_procedure

Procedure의 기본 정보를 담고 있다.

Column name

Type

설명

INST_NAME

CHAR

Procedure가 속한 instance name

PROC_NAME

CHAR

Procedure name

PIECE_COUNT

CHAR

dbm_procedure_text에는 구문 전체가 나뉘어 저장되는데 이 때 나뉘어진 조각의 개수

dbm_procedure_text

Procedure를 생성한 시점에 사용된 구문 전체를 저장한다.

Column name

Type

설명

INST_NAME

CHAR

Procedure가 속한 instance name

PROC_NAME

CHAR

Procedure name

PIECE_ID

INT

저장된 조각의 순서 번호

PROC_TEXT

CHAR

Procedure syntax

PROCEDURE Language Elements

본 장에서는 procedure에서 제공되는 각 구성 요소와 기능에 대해 설명한다.

Declare Section

Procedure의 block 단위에서 사용할 수 있는 element들의 선언부이다.

Variable

SCALAR TYPE

GOLDILOCKS LITE에서 사용되는 변수 중에 scalar data type에 대한 변수를 선언한다.

scalar typed variable ::= variable_name data_type ( := init_value_expr );

variable_name := 선언하려는 변수의 이름 (동일 block 내에 중복된 이름을 허용하지 않음)

data_type :=   INT 
             | SHORT 
             | LONG 
             | FLOAT 
             | DOUBLE 
             | CHAR (size)

init_value_expr := 초기값을 지정할 경우 상수나 수식을 기술할 수 있다.

Data type

Size

C type 참고

INT

4 bytes

int

SHORT

2 bytes

short

FLOAT

4 bytes

float

DOUBLE

8 bytes

double

CHAR( size )

Size 만큼 할당 (byte 단위)

char

LONG

8 bytes

long long

Init_value가 지정되지 않을 경우 변수는 실행되기 전에 NULL로 초기화 된다. 따라서 숫자형 변수들은 모두 0으로 초기화되고 문자형 변수는 0x00 으로 표현된다.

%TYPE

이미 생성된 테이블의 column이나 앞서 선언된 변수의 타입을 그대로 사용하기 위해 사용된다.

Typed variable ::=   table_name.column_name % TYPE 
                   | variable_name % TYPE
                   | variable_name.field_name %TYPE
                   ;
다음과 같이 사용할 수 있는데 테이블이나 변수의 타입을 사용하려면 해당 테이블이나 변수가 미리 생성되어 있거나 선언되어 있는 상태여야 한다.
dbmMetaManager(DEMO)> CREATE TABLE T1 (C1 INT, C2 INT);
success
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE PROC1
IS
  V1    INT;
  V2    V1%TYPE;
  V3    T1.C1%TYPE;
BEGIN
  NULL;
END;
/
success

%TYPE의 초기값은 참조되는 변수의 초기값을 따라가지 않으므로 필요할 경우 초기값을 설정해야 한다.

%ROWTYPE

하나 이상의 필드를 갖는 변수를 선언하기 위해 사용하는데 이 때 필드는 테이블의 구조와 동일하게 구성된다.

Row typed variable ::= table_name % ROWTYPE;

다음은 테이블 구조와 동일한 ROWTYPE 변수를 선언하는 예이다.

dbmMetaManager(DEMO)> CREATE TABLE T1 (C1 INT, C2 INT);
success
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE PROC1
IS
  V1    T1%ROWTYPE;
BEGIN
  NULL;
END;
/
success
위의 예제에서 V1 변수는 각각 두 개의 field (C1, C2)를 가진 대표 변수로 선언되며 각 field는 다음과 같이 표기하여 사용할 수 있다.
dbmMetaManager(DEMO)> CREATE TABLE T1 (C1 INT, C2 INT);
success
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE PROC1
IS
  V1    T1%ROWTYPE;
BEGIN
  V1.C1 := V1.C2;
END;
/
success

%ROWTYPE 변수들의 개별 필드가 1 : 1 호환 가능한 타입 구조일 경우 해당 변수들 간에 대표명으로 assign 할 수도 있다.

dbmMetaManager(DEMO)> CREATE TABLE T1 (C1 INT, C2 INT);
success
dbmMetaManager(DEMO)> CREATE TABLE T2 (C1 INT, C2 INT);
success
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE PROC1
IS
  V1    T1%ROWTYPE;
  V2    T1%ROWTYPE;
BEGIN
  V2 := V1;
END;
/
success

%ROWTYPE으로 선언된 변수에는 초기값을 설정할 수 없으며 실행 전에 모든 필드가 0x00 으로 초기화 되어 수행된다.

변수의 유효 범위

변수의 유효 범위는 선언된 block 내부이다. 상위 block에 선언된 변수는 하위 block에서도 참조할 수 있지만 하위 block의 변수는 상위 block에서 참조할 수 없다. 또한, 동일한 변수명이 여러 개 존재할 경우 먼저 탐색된 block 내의 변수를 참조하므로 다른 상위 block의 변수를 이용하려면 해당 block의 label을 통해 접근해야 한다.
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1
IS
  V1 INT;
BEGIN
     DECLARE
         V1 INT;
     BEGIN
         V1 := 1;
         DBMS_OUTPUT.PUT_LINE( 'SCOPE1: V1 = ' || V1 );
     END;

     DBMS_OUTPUT.PUT_LINE( 'SCOPE2: V1 = ' || V1 );
END;
/
success
dbmMetaManager(DEMO)> exec proc1
SCOPE1: V1 = 1
SCOPE2: V1 = 0
success
dbmMetaManager(DEMO)>
위의 예제에서 V1은 가장 안쪽 block 내에서만 유효하며 해당 block 밖으로 나와 두 번째로 출력될 때는 상위 block을 참조하기 때문에 0으로 출력된다.

Cursor

GOLDILOCKS LITE에서 cursor는 snapshot의 개념이다. 실행 시점에 획득한 SCN 값을 기준으로 자신이 접근 가능한 record에 대해 결과 집합을 만든다. 별도의 isolation level은 제공하지 않는다.

DECLARE

Cursor_declaration ::= CURSOR cursor_name [ ( param_list ) ]  IS select_statement ;

cursor_name := Cursor 이름
param_list := Cursor 선언 내의 select 문에 사용될 parameter가 있을 경우 기술한다. 없으면 생략한다.
select_statement := 결과 집합에 대한 질의문

다음과 같이 cursor 관련 구문을 활용하여 실행할 수 있다.

dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1()
IS
  v1 INT;
  v2 CHAR(20);
  v3 DOUBLE;
  v4 t1%ROWTYPE;
  CURSOR c1 (a1 INT, a2 DOUBLE )
     IS select * from t1 where c1 >= a1 and c3 > a2;
BEGIN
  OPEN c1 (2, 3);

  LOOP
    FETCH c1 INTO v1, v2, v3;
    EXIT WHEN c1%NOTFOUND;
    dbms_output.put_line( 'v1 = ' || v1 || ', v2 = ' || v2 || ', v3 = ' || v3 || ', RowCount=' || c1%rowcount );
  END LOOP;

  CLOSE c1;

  OPEN c1 (2, 3);

  LOOP
    FETCH c1 INTO v4;
    EXIT WHEN c1%notfound;
    dbms_output.put_line( 'c1 = ' || v4.c1 || ', c2 = ' || v4.c2 || ', c3 = ' || v4.c3 || ', RowCount=' || c1%rowcount );
  END LOOP;

END;
/
success
dbmMetaManager(DEMO)> exec proc1
V1 = 3, V2 = c, V3 = 3.500000, ROWCOUNT=1
V1 = 4, V2 = d, V3 = 4.500000, ROWCOUNT=2
V1 = 5, V2 = e, V3 = 5.500000, ROWCOUNT=3
C1 = 3, C2 = c, C3 = 3.500000, ROWCOUNT=1
C1 = 4, C2 = d, C3 = 4.500000, ROWCOUNT=2
C1 = 5, C2 = e, C3 = 5.500000, ROWCOUNT=3
success

Cursor declare 절에 parameter가 기술된 경우, cursor open을 실행할 때 선언된 parameter 개수와 동일한 type과 개수의 데이터를 입력해야 한다.

User Exception

Procedure를 실행하는 과정에서 사용자가 예외 처리할 사항을 정의한다.

User_exception ::= exception_name EXCEPTION ;

exception_name := 사용자가 정의하려는 exception name

다음은 user exception을 사용한 exception handler의 예이다.

dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1
IS
  user1 EXCEPTION;
BEGIN
  BEGIN
    RAISE user1;
    EXCEPTION WHEN case_not_found THEN dbms_output.put_line('fatal execution');
  END;
EXCEPTION WHEN user1 THEN dbms_output.put_line( 'SQLCODE=' || SQLCODE || ',SQLERRM=' || SQLERRM );
END;
/
success
dbmMetaManager(DEMO)> exec proc1
SQLCODE=70121,SQLERRM=a user exception raised
success

GOLDILOCKS LITE에 미리 정의된 내부 exception은 다음 표와 같다.

EXCEPTION

NAME

Error code

설명

CASE_NOT_FOUND

70117

Simple/ searched case 구문에서 모든 경우의 수에 해당되지 않고 else 구문도 정의되지 않은 경우에 발생한다.

CURSOR_ALREADY_OPEN

70118

Cursor가 이미 열려 있는 경우에 발생한다.

DUP_VAL_ON_INDEX

70055

Insert 문에서 key가 중복되는 경우에 발생한다.

NO_DATA_FOUND

70111

Select 문에서 대상 데이터를 찾지 못한 경우에 발생한다.

TOO_MANY_ROWS

70113

Select into 문에서 두 건 이상 반환되는 경우에 발생한다.

VALUE_ERROR

70047

(divide_by_zero를 포함하여) 수식에 오류가 있는 경우에 발생한다.

Assign Statement

Assign statement는 procedure 내의 변수에 사용자가 지정한 값을 설정할 때 사용한다.

Assign_statement ::=   Variable := Expression ;

variable ::= Procedure내에 선언된 변수 이름
expression := 수식
A := B의 수식에서 A가 RowType과 같은 필드로 구성된 변수 타입인 경우, B도 A와 동일한 타입 또는 필드의 구성 타입과 개수가 동일한 타입의 변수이어야 assign을 수행할 수 있다.
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1 ()
IS
v1 t1%ROWTYPE;
v2 t1%ROWTYPE;
BEGIN
    v1.c1 := 100;
    v1.c2 := 200;

    dbms_output.put_line( 'v1.c1 = ' || v1.c1 );
    dbms_output.put_line( 'v1.c2 = ' || v1.c2 );

    v2 := v1;

    dbms_output.put_line( 'v2.c1 = ' || v2.c1 );
    dbms_output.put_line( 'v2.c2 = ' || v2.c2 );
END;
/
success
dbmMetaManager(DEMO)> exec proc1
V1.C1 = 100
V1.C2 = 200
V2.C1 = 100
V2.C2 = 200
success

Procedure assign의 target이 parameter일 경우 BindingMode가 IN이면 값을 설정할 수 없다.

Control Statements

Procedure 수행 과정에서 조건에 따라 반복, 점프, 분기 등을 수행하기 위한 구문을 제공한다.

IF

조건절을 만족할 경우, 하위 statement들을 수행한다.

IF_statement ::=  IF condition_expression THEN statements 
                  [ ( ELSIF condition_expression THEN Statements ) ... ]
                  | 
                  [ ELSE statements ]
                  END IF ;

condition_expression ::= 수행 여부를 판단하는 조건 수식
statements ::= Procedure statement의 정의
condition_expression에서 두 개 이상의 조건을 판단해야 할 경우, AND 또는 OR로 나열하여 기술한다.
다음 예제를 참조한다.
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1( a1 int )
IS
c1 INT;
BEGIN
  c1 := a1;


  IF c1 = 1 THEN
      dbms_output.put_line( 'C1(cond1) = ' || c1 );
  END IF;

  IF c1 = 1 THEN
      dbms_output.put_line( 'C1(cond2.1) = ' || c1 );
  ELSE
      dbms_output.put_line( 'C1(cond2.2) = ' || c1 );
  END IF;

  IF c1 = 1 THEN
      dbms_output.put_line( 'C1(cond3.1) = ' || c1 );
  ELSIF c1 = 2 THEN
      dbms_output.put_line( 'C1(cond3.2) = ' || c1 );
  ELSIF c1 = 3 THEN
      dbms_output.put_line( 'C1(cond3.3) = ' || c1 );
  ELSE
      dbms_output.put_line( 'C1(cond3.4) = ' || c1 );
  END IF;

END;
/
dbmMetaManager(DEMO)> exec proc1( 1 )
C1(COND1) = 1
C1(COND2.1) = 1
C1(COND3.1) = 1
success
dbmMetaManager(DEMO)> exec proc1( 2 )
C1(COND2.2) = 2
C1(COND3.2) = 2
success
dbmMetaManager(DEMO)> exec proc1( 3 )
C1(COND2.2) = 3
C1(COND3.3) = 3
success
dbmMetaManager(DEMO)> exec proc1( 4 )
C1(COND2.2) = 4
C1(COND3.4) = 4
success

Simple Case

Case 절에 기술한 값이 WHEN 절의 수식 결과값과 일치할 경우, 해당 WHEN 절에 기술된 statement들을 수행한다.

simple_case ::= CASE value_expression 
                [ WHEN value_expression THEN statements ; ( ... ) ]
                ( ELSE statements ; )
                END CASE;

value_expression ::= 수식
statements ::= Procedure에서 실행 가능한 구문

다음 예제를 참조한다.

dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1( a1 int )
IS
  c1 INT;
BEGIN

  c1 := a1;

  dbms_output.put_line( 'NoElsePart' );
  CASE a1
      WHEN 1 THEN dbms_output.put_line( '1. c1 = ' || c1 );
      WHEN 2 THEN dbms_output.put_line( '2. c1 = ' || c1 );
      WHEN 3 THEN dbms_output.put_line( '3. c1 = ' || c1 );
  END CASE;


  dbms_output.put_line( 'ElsePart' );
  CASE a1
      WHEN 1 THEN dbms_output.put_line( '1. c1 = ' || c1 );
      WHEN 2 THEN dbms_output.put_line( '2. c1 = ' || c1 );
      ELSE dbms_output.put_line( 'else c1 = ' || c1 );
  END CASE;

END;
/
success
dbmMetaManager(DEMO)> exec proc1(1)
NOELSEPART
1. C1 = 1
ELSEPART
1. C1 = 1
success
dbmMetaManager(DEMO)> exec proc1(2)
NOELSEPART
2. C1 = 2
ELSEPART
2. C1 = 2
success
dbmMetaManager(DEMO)> exec proc1(3)
NOELSEPART
3. C1 = 3
ELSEPART
ELSE C1 = 3
success

어떤 조건도 만족하지 않고 ELSE 절도 생략된 경우, CASE_NOT_FOUND 에러가 발생한다.

Searched Case

CASE WHEN 절에 기술된 수식이 참 (TRUE)인 경우, 해당 WHEN 절의 statement들을 수행한다.

searched_case::= CASE [ WHEN condition_expression THEN statements ; ( ... ) ]
                 ( ELSE statements ; )
                  END CASE;

condition_expression ::= 조건 수식
statements ::= Procedure에서 실행 가능한 구문

다음 예제를 참조한다.

dbmMetaManager(demo)> CREATE OR REPLACE PROCEDURE proc1( a1 int )
IS
  c1 INT;
BEGIN

  c1 := a1;

  dbms_output.put_line( 'NoElsePart' );
  CASE
      WHEN a1 = 1 THEN dbms_output.put_line( '1. c1 = ' || c1 );
      WHEN a1 = 2 THEN dbms_output.put_line( '2. c1 = ' || c1 );
      WHEN a1 = 3 THEN dbms_output.put_line( '3. c1 = ' || c1 );
  END CASE;


  dbms_output.put_line( 'ElsePart' );
  CASE
      WHEN a1 = 1 THEN dbms_output.put_line( '1. c1 = ' || c1 );
      WHEN a1 = 2 THEN dbms_output.put_line( '2. c1 = ' || c1 );
      ELSE dbms_output.put_line( 'else c1 = ' || c1 );
  END CASE;

END;
/
success
dbmMetaManager(DEMO)> exec proc1(1)
NOELSEPART
1. C1 = 1
ELSEPART
1. C1 = 1
success
dbmMetaManager(DEMO)> exec proc1(2)
NOELSEPART
2. C1 = 2
ELSEPART
2. C1 = 2
success
dbmMetaManager(DEMO)> exec proc1(3)
NOELSEPART
3. C1 = 3
ELSEPART
ELSE C1 = 3
success

어떤 조건도 만족하지 않고 ELSE 절도 생략된 경우, CASE_NOT_FOUND 에러가 발생한다.

GOTO

특정 label 위치로 jump 한다. 일반적인 C 언어의 goto 문과 같은 개념으로 사용한다. 단, IF 절의 안쪽으로는 진입할 수 없다.
GOTO_statement ::= GOTO label;

label ::= Procedure 내에 명시된 label name

다음 예제를 참조한다.

dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1()
IS
  c1 INT := 0;
BEGIN

    << AA >>
    BEGIN

       dbms_output.put_line( 'seoul(c1=' || c1 || ')' );

       << BB >>
       BEGIN
           c1 := c1 + 1;

           dbms_output.put_line( 'paris(c1=' || c1 || ')' );
           IF c1 < 3
           THEN
              GOTO aa;
           ELSIF c1 >= 3 AND c1 < 5 THEN
              GOTO bb;
           ELSE
              GOTO cc;
           END IF;

           dbms_output.put_line( 'never print' );

           BEGIN
               dbms_output.put_line( 'brazil(c1=' || c1 || ')' );

               BEGIN
                 << CC >>
                 dbms_output.put_line( 'sanghai(c1=' || c1 || ')' );
                 IF c1 = 5 THEN
                    GOTO aa;
                 ELSIF c1 = 6 THEN
                    GOTO bb;
                 END IF;

               END;
           END;

           dbms_output.put_line( 'taipei(c1=' || c1 || ')' );
       END;
    END;

END;
/
success
dbmMetaManager(DEMO)> exec proc1
SEOUL(C1=0)
PARIS(C1=1)
SEOUL(C1=1)
PARIS(C1=2)
SEOUL(C1=2)
PARIS(C1=3)
PARIS(C1=4)
PARIS(C1=5)
SANGHAI(C1=5)
SEOUL(C1=5)
PARIS(C1=6)
SANGHAI(C1=6)
PARIS(C1=7)
SANGHAI(C1=7)
TAIPEI(C1=7)
success

EXIT (WHEN)

LOOP를 수행하는 도중에 멈추고 해당 scope를 빠져나가기 위해 사용한다.

EXIT_statement ::= EXIT ( WHEN condition_expression ) ;

condition_expression ::= 조건을 지정하고자 할 경우 조건 수식을 기술
조건없이 EXIT를 조건 없이 기술할 경우에는 즉시 scope를 빠져나가고, 조건절이 기술된 경우에는 조건을 만족했을 때 scope를 벗어난다. 
다음 예제를 참조한다.
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1()
IS
  c1 INT;
BEGIN
    dbms_output.put_line( 'first_loop' );
    c1 := 0;

    LOOP
        c1 := c1 + 1;
        dbms_output.put_line( 'c1 = ' || c1 );
        EXIT;
    END LOOP;

    dbms_output.put_line( 'second_loop' );

    c1 := 0;
    LOOP
        c1 := c1 + 1;
        dbms_output.put_line( 'c1 = ' || c1 );
        EXIT WHEN c1 = 10;
    END LOOP;
END;
/
success
dbmMetaManager(DEMO)> exec proc1
FIRST_LOOP
C1 = 1
SECOND_LOOP
C1 = 1
C1 = 2
C1 = 3
C1 = 4
C1 = 5
C1 = 6
C1 = 7
C1 = 8
C1 = 9
C1 = 10
success

EXIT 구문은 LOOP 계열 구문에서만 사용할 수 있다.

CONTINUE (WHEN)

LOOP를 수행하는 도중에 조건을 만족할 경우, LOOP scope 내의 시작 위치로 이동하여 수행을 시작한다.

CONTINUE_statement ::= CONTINUE ( WHEN condition_expression ) ;

condition_expression ::= 조건을 지정하고자 할 경우 조건 수식을 기술
조건없이 CONTINUE를 기술할 경우에는 LOOP 내의 첫 번째 statement로 이동하고, 조건절이 기술된 경우에는 조건을 만족할 경우에 이동한다. 
다음 예제를 참조한다.
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1()
IS
  c1 INT;
BEGIN
    c1 := 0;

    LOOP
        c1 := c1 + 1;
        IF c1 < 5 THEN
            CONTINUE;
        END IF;

        CONTINUE WHEN c1 < 10;

        dbms_output.put_line( 'c1 = ' || c1 );
        EXIT;
    END LOOP;
END;
/
success
dbmMetaManager(DEMO)> exec proc1
C1 = 10
success

CONTINUE 구문은 LOOP 계열 구문에서만 사용할 수 있다.

RAISE_APPLICATION_ERROR

사용자가 특정 에러 코드와 에러 메시지를 설정하여 exception을 발생시키고자 할 경우에 사용된다.
GOLDILOCKS LITE에서 사용자 에러 코드는 음수로만 정의할 수 있다.
raise_application_error_statement ::= RAISE_APPLICATION_ERROR( errorCode, errorMessage);

errorCode ::= 음수 범위 내의 정수형 값
errorMessage ::= 사용자가 지정한 에러 메시지 (512 byte 이내로 설정 가능)

다음과 같이 exception을 발생시키는데 exception handler에 의해 처리되지 않을 경우, 상위로 에러를 전달한다.

dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1
IS
 user1 EXCEPTION;
 PRAGMA EXCEPTION_INIT( user1, -100);
BEGIN
  RAISE_APPLICATION_ERROR( -100, 'user1 raise' );
EXCEPTION WHEN user1 THEN dbms_output.put_line( 'SQLCODE=' || SQLCODE || ', SQLERRM=' || SQLERRM );
END;
/
success
dbmMetaManager(DEMO)> exec proc1
SQLCODE=-100, SQLERRM=USER1 RAISE
success

User exception의 에러 코드가 일치하지 않을 경우 unhandled exception으로 처리되어 상위로 전달된다.

Loop Statements

Procedure 내에서 반복되는 명령문을 처리하기 위한 구문들이다.

SIMPLE LOOP

LOOP와 END LOOP 구문 사이에 기술된 구문들을 반복적으로 수행한다. Loop 구간을 빠져나가려면 EXIT 구문을 사용한다.
LOOP Statement ::=   LOOP 
                        < statements > 
                     END LOOP

statements ::= Procedure 내에서 사용 가능한 구문들
dbmMetaManager(demo)> CREATE OR REPLACE PROCEDURE proc1()
IS
  c1 INT;
BEGIN
    dbms_output.put_line( 'first_loop' );
    c1 := 0;

    LOOP
        c1 := c1 + 1;
        dbms_output.put_line( 'c1 = ' || c1 );
        EXIT;
    END LOOP;

    dbms_output.put_line( 'second_loop' );

    c1 := 0;
    LOOP
        c1 := c1 + 1;
        dbms_output.put_line( 'c1 = ' || c1 );
        EXIT WHEN c1 = 10;
    END LOOP;
END;
/
success
dbmMetaManager(DEMO)> exec proc1
FIRST_LOOP
C1 = 1
SECOND_LOOP
C1 = 1
C1 = 2
C1 = 3
C1 = 4
C1 = 5
C1 = 6
C1 = 7
C1 = 8
C1 = 9
C1 = 10
success

FOR LOOP

FOR LOOP는 FOR 구문에 기술된 수식을 증가/ 감소시키면서 최종값에 도달할 때까지 LOOP ~ END LOOP 구문 사이에 기술된 procedure 구문들을 수행한다.
FOR LOOP Statement ::= FOR <identifier> IN (REVERSE) <first_expr> .. <last_expr>
                       LOOP
                            <statements>
                       END LOOP


identifier := FOR LOOP 내에서 사용될 LOOP 범위 연산의 변수명
REVERSE := 생략할 경우 First_expr을 시작으로 Last_expr 값까지 진행, 기술되면 역순으로 진행
first_expr := FOR LOOP 내의 identifier가 가질 첫 번째 수식 값, REVERSE가 기술된 경우에는 마지막 값
last_expr := FOR LOOP 내의 identifier가 가질 마지막 수식 값, REVERSE가 기술된 경우에는 첫 번째 값
statements := FOR LOOP 내에서 수행될 procedure 구문들
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1()
IS
  c1 INT;
BEGIN
  FOR i IN 1 .. 5
  LOOP
    dbms_output.put_line( 'i = ' || i );
  END LOOP;
END;
/
success
dbmMetaManager(DEMO)> EXEC proc1()
I = 1
I = 2
I = 3
I = 4
I = 5
success
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1()
IS
  c1 INT;
BEGIN
  FOR i IN REVERSE 1 .. 5
  LOOP
    dbms_output.put_line( 'i = ' || i );
  END LOOP;
END;
/
success
dbmMetaManager(DEMO)> EXEC proc1()
I = 5
I = 4
I = 3
I = 2
I = 1
success

FOR LOOP 내의 identifier는 declare 절에 미리 기술되어 있지 않아도 되지만 이 경우, FOR LOOP ~ END LOOP 구문 사이의 범위 내에서만 참조할 수 있다. 해당 scope를 벗어날 경우 참조할 수 없다.

WHILE LOOP

WHILE LOOP는 WHILE 절에 기술된 조건이 참인 경우에만 LOOP 내의 procedure 구문들을 수행하는 방식이다.

WHILE LOOP Statement ::= WHILE  <cond_expr >
                         LOOP
                            <statements>
                         END LOOP
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1()
IS
  c1 INT := 0;
BEGIN
    dbms_output.put_line( 'start: c1 = ' || c1 );

    WHILE c1 < 5
    LOOP
        c1 := c1 + 1;
    END LOOP;

    dbms_output.put_line( 'last c1 = ' || c1 );
END;
/
success
dbmMetaManager(DEMO)> EXEC proc1
START: C1 = 0
LAST C1 = 5
success

CURSOR Statements

SELECT 문을 사용하여 두 건 이상의 데이터를 가져오고자 할 경우에 사용한다.  GOLDILOCKS LITE의 cursor는 실행 시점의 snapshot을 이용하며 committed-read mode만 보장한다.
Cursor를 사용하기 위해서는 cursor 정의를 먼저 수행해야 하는데 이는 declare section의 Cursor 부분을 참조한다.

OPEN

Declare section에 정의된 cursor 구문을 실행한다.

OPEN statement ::= OPEN <cursor_name>;

cursor_name := Declare section에 정의된 cursor 이름
dbmMetaManager(DEMO)> CREATE TABLE t1 (c1 INT, c2 CHAR(20), c3 DOUBLE)
success
dbmMetaManager(DEMO)> CREATE UNIQUE INDEX idx_t1 ON t1 (c1)
success
dbmMetaManager(DEMO)> INSERT INTO t1 VALUES (1, 'a', 1.5 )
success
dbmMetaManager(DEMO)> INSERT INTO t1 VALUES (2, 'b', 2.5 )
success
dbmMetaManager(DEMO)> INSERT INTO t1 VALUES (3, 'c', 3.5 )
success
dbmMetaManager(DEMO)> INSERT INTO t1 VALUES (4, 'd', 4.5 )
success
dbmMetaManager(DEMO)> INSERT INTO t1 VALUES (5, 'e', 5.5 )
success
dbmMetaManager(DEMO)> COMMIT
success
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1()
IS
  v1 INT;
  v2 CHAR(20);
  v3 DOUBLE;
  v4 t1%ROWTYPE;
CURSOR c1 (a1 INT, a2 DOUBLE )
 IS select * from t1 where c1 >= a1 and c3 > a2;
BEGIN
  OPEN c1 (2, 3);

  LOOP
    FETCH c1 INTO v1, v2, v3;
    EXIT WHEN c1%NOTFOUND;
    dbms_output.put_line( 'v1 = ' || v1 || ', v2 = ' || v2 || ', v3 = ' || v3 || ', RowCount=' || c1%rowcount );
  END LOOP;

  CLOSE c1;

  OPEN c1 (2, 3);

  LOOP
    FETCH c1 INTO v4;
    EXIT WHEN c1%NOTFOUND;
    dbms_output.put_line( 'c1 = ' || v4.c1 || ', c2 = ' || v4.c2 || ', c3 = ' || v4.c3 || ', RowCount=' || c1%rowcount );
  END LOOP;


END;
/
success
dbmMetaManager(DEMO)> EXEC proc1
V1 = 3, V2 = c, V3 = 3.500000, ROWCOUNT=1
V1 = 4, V2 = d, V3 = 4.500000, ROWCOUNT=2
V1 = 5, V2 = e, V3 = 5.500000, ROWCOUNT=3
C1 = 3, C2 = c, C3 = 3.500000, ROWCOUNT=1
C1 = 4, C2 = d, C3 = 4.500000, ROWCOUNT=2
C1 = 5, C2 = e, C3 = 5.500000, ROWCOUNT=3
success

FETCH

OPEN 구문으로 열린 cursor에 한해 record를 한 건씩 읽어들여 INTO 절에 기술된 변수로 값을 복사한다.

FETCH Statement ::=  FETCH <cursor_name>  INTO < variable_name (, ... ) >

cursor_name := Open 구문으로 정상 처리된 cursor name
variable_name := Fetch 된 결과를 저장할 변수 이름
Scalar와 ROWTYPE 변수 둘 다 INTO 절에 기술할 수 있다. 변수를 기술하는데 제약 사항은 없지만 cursor결과를 가진 target 절의 개수와 INTO 절에 기술된 변수와의 DataType 호환에 제약이 있을 경우 fetch를 수행하는 도중에 오류가 발생할 수 있다.

CLOSE

OPEN 구문에 의해 열린 cursor를 정리한다.

CLOSE statement ::= CLOSE <cursor_name>

cursor_name := Open 구문에 의해 정상적으로 열린 cursor 이름

SQL Statements

Procedure 내에 일반 DML을 사용할 수 있는데 이에 대한 구문을 설명한다.

INSERT

한 건의 record를 지정한 테이블에 삽입한다.

INSERT Statement  ::= INSERT INTO <target_table> ( column_name (, ...) >
                      VALUES ( value_expr ( ,...) );

target_table := 대상 테이블
column_name := 테이블 내의 특정 column을 나열하고자 할 경우
value_expr := Procedure 변수를 포함한 value list

Value 절에는 procedure 내의 RowType 변수를 사용할 수 있다.

UPDATE

테이블에서 한 건 이상의 레코드의 지정된 column 값을 갱신한다.

UPDATE Statement ::= UPDATE <target_table> 
                     SET  <column_name> = <value_expr> (, ...)
                     ( WHERE cond_expr );

target_table := 대상 테이블
column_name := 값을 변경하고자 하는 대상 column 이름
value_expr := 변수등을 포함하는 value expression
cond_expr := 특정 레코드를 탐색할 경우 해당하는 조건절

DELETE

테이블에서 한 건 이상의 레코드를 삭제한다.

DELETE Statement ::= DELETE FROM <target_table> 
                     (WHERE cond_expr);

target_table := 대상 테이블
cond_expr := 특정 레코드를 탐색할 경우 조건을 기술

SELECT INTO

테이블에서 한 건의 데이터를 탐색하여 procedure의 변수에 값을 저장하고자 할 경우에 사용한다. Cursor와 달리 한 건만 fetch 할 수 있다.
SELECT INTO statement ::= SELECT <target_list> INTO <variable_list>
                                    FROM <source_table>
                                   (WHERE cond_expr);

target_list := 테이블에서 가지고 올 column 이름 ( *를 사용할 경우, 모든 column을 가지고 옴)
variable_list := Procedure 내의 변수
source_table := 대상 테이블
cond_expr := 조건절

INTO 절에는 procedure의 RowType 변수를 사용할 수 있다.

COMMIT

현재 세션에서 진행한 트랜잭션을 영구적으로 반영한다.
다음 예제와 같이 procedure 내에서 COMMIT을 사용하여 트랜잭션을 영구적으로 반영한다.  자신의 세션에서 조회할 경우 commit 하기 전이라도 세션 변경사항을 볼 수 있지만 다른 세션에서 조회할 경우에는 변경 전의 committed-image를 조회한다.
dbmMetaManager(DEMO)> CREATE TABLE t1 (c1 INT, c2 INT)
success
dbmMetaManager(DEMO)> INSERT INTO t1 VALUES (2, 2)
success
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1
IS
  V1 INT := 100;
BEGIN
    INSERT INTO T1 VALUES (1, 1);
    COMMIT;

    UPDATE T1 SET C2 = 100 WHERE C1 = 1;
    DBMS_OUTPUT.PUT_LINE( 'v1 = ' || v1 );
END;
/
success
dbmMetaManager(DEMO)> EXEC proc1
V1 = 100
success
dbmMetaManager(DEMO)> SELECT * FROM T1
---------------------------------------------------------------------------------
C1                   : 2
C2                   : 2
---------------------------------------------------------------------------------
C1                   : 1
C2                   : 100
---------------------------------------------------------------------------------

2 row selected

다른 세션에서 조회할 경우 다음과 같이 변경전 이미지를 조회한다.

[2nd_proc] dbmMetaManager(DEMO)> set instance demo
success
[2nd_proc] dbmMetaManager(DEMO)> select * from t1
---------------------------------------------------------------------------------
C1                   : 2
C2                   : 2
---------------------------------------------------------------------------------
C1                   : 1
C2                   : 1
---------------------------------------------------------------------------------

2 row selected

ROLLBACK

현재의 세션에서 진행한 트랜잭션을 철회한다.
다음 예제와 같이 갱신된 데이터에 rollback을 수행하여 트랜잭션을 이전의 원래 데이터로 복구한다.
dbmMetaManager(DEMO)> CREATE TABLE t1 (c1 INT, c2 INT)
success
dbmMetaManager(DEMO)> INSERT INTO t1 VALUES (2, 2)
success
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc1
IS
  V1 INT := 200;
BEGIN
    INSERT INTO T1 VALUES (1, 1);
    UPDATE T1 SET C2 = 100 WHERE C1 = 1;
    COMMIT;

    UPDATE T1 SET C2 = 200 WHERE C1 = 1;
    ROLLBACK;

    DBMS_OUTPUT.PUT_LINE( 'V1 = ' || V1 );
END;
/
success
dbmMetaManager(DEMO)> EXEC proc1
V1 = 200
success
dbmMetaManager(DEMO)> SELECT * FROM T1
---------------------------------------------------------------------------------
C1                   : 2
C2                   : 2
---------------------------------------------------------------------------------
C1                   : 1
C2                   : 100
---------------------------------------------------------------------------------

2 row selected

Exception Handler

Exception handler는 procedure를 수행하는 도중에 발생하는 오류를 감지하여 사용자가 지정한 코드를 수행하고 해당 블럭을 종료하는 procedure의 exception 코드들이다.
이러한 예외 처리들은 미리 정의된 구문이나 사용자 정의에 의해 구분된다. 사용자 정의 exception에 대한 자세한 앞서 설명한 Declare Section을 참조한다.

PreDefined Exception

EXCEPTION

NAME

Error code

설명

CASE_NOT_FOUND

70117

Simple/ searched case 구문에서 모든 경우의 수에 해당되지 않고 else 구문도 정의되지 않은 경우에 발생한다.

CURSOR_ALREADY_OPEN

70118

Cursor가 이미 열려 있는 경우에 발생한다.

DUP_VAL_ON_INDEX

70055

Insert 문에서 key가 중복되는 경우에 발생한다.

NO_DATA_FOUND

70111

Select 문에서 대상 데이터를 찾지 못한 경우에 발생한다.

TOO_MANY_ROWS

70113

Select into 문에서 두 건 이상 반환되는 경우에 발생한다.

VALUE_ERROR

70047

(divide_by_zero를 포함하여) 수식에 오류가 있는 경우에 발생한다.

PreDefined exception은 여타의 DBMS 구문 및 오류상황과 다를 수 있다.

Exception Handler 정의

Exception Handler :::=  EXCEPTION 
                        ( WHEN <exception_name> THEN <statements>; [, ...] )

exception_name := PreDefined exception 또는 사용자 정의 exception name
statements := Procedure 구문들
다음과 같은 방식으로 사용할 수 있다. 다음은 select 문에 의해 NOT_FOUND 에러가 발생한 경우, OTHERS exception handler를 통해 처리되는 부분과 exception scope에 대한 예이다.
dbmMetaManager(DEMO)> CREATE OR REPLACE PROCEDURE proc2 ()
IS
v1 INT;
BEGIN
    dbms_output.put_line( 'start' );
    BEGIN
        BEGIN
            select c1 into v1 from t1 where c1 = 1;
            dbms_output.put_line( 'first line' );
        EXCEPTION
        WHEN others THEN dbms_output.put_line( 'exception raise' );
        END;

        dbms_output.put_line( 'middle line' );
    EXCEPTION
    WHEN others THEN dbms_output.put_line( 'exception raise' );
    END;
    dbms_output.put_line( 'last line' );
END;
/
success
dbmMetaManager(DEMO)> EXEC proc2
START
EXCEPTION RAISE
MIDDLE LINE
LAST LINE
success

Raise Statement

Procedure를 수행하는 도중에 사용자가 임의로 exception 상태를 발생시키려고 할 경우에 사용한다.
RAISE Statement ::= RAISE ( <exception_name> );

exception_name ::= 사용자 정의 exception 이름

RAISE 문이 exception handler에서 사용될 경우에는 exception 이름을 지정할 수 없다. 즉, 현재 발생한 exception 정보만 상위 블록으로 전달하는데 이용한다. (지정되더라도 동작하지 않는다.)

Attribute Variable

SQL 수행 상태 또는 cursor의 수행 상태와 정보를 가진 변수들을 의미하며 사용자 선언 없이 내부적으로 할당되어 존재한다.

SQL Attribute Variable

SQL 문의 수행 상태에 대한 정보를 담고 있다. GOLDILOCKS LITE의 상태/ 초기값은 다른 DBMS들에서 쓰이는 것과 다를 수 있다.

Cursor Attribute Variable

CURSOR 문의 수행 상태에 대한 정보를 담고 있다. GOLDILOCKS LITE의 상태/ 초기값은 다른 DBMS들에서 쓰이는 것과 다를 수 있다. SQL attribute 변수들과 의미는 동일하며 cursor에 대해서만 사용할 수 있다.

Godlilocks Lite에서 attribute 변수들의 초기값은 다른 DBMS의 설정값과 다르기 때문에 호환되는지 여부에 주의해야 한다. 예를 들어 FOUND, NOTFOUND의 경우에는 (True/False)의 개념이 아닌 (0/1)의 개념이 적용되어 있다.

SQLCODE

Procedure를 수행 중에 발생하는 오류들 중에 가장 마지막 오류의 에러 코드를 저장한다. Exception handler의 첫 구문이 정상적으로 처리되면 에러 코드는 0으로 설정된다.

SQLERRM

Procedure 수행 중에 발생하는 오류들 중에 가장 마지막 오류의 에러 메시지를 저장한다. Exception handler의 첫 구문이 정상적으로 처리되면 오류는 초기화 된다.