■ PL/SQL(Procedure Language Structured Query Language)

- 프로시저 생성자를 제공한다.
    - 변수, 상수, 데이터 유형(레코드,배열)
    - 조건문, 반복문
    - 한번 작성하면 여러번 실행할 수 있는 재사용 가능한 프로그램 단위

- PL.SQL 이점
    - 모듈식 프로그램 개발
    - ORACLE 도구와 통합(ERP,HR,SCM,CRM,...)
    - 이식성
    - 예외처리

# PL/SQL 프로그램은 블록 구조 형식

# 익명블록 구조
- 객체 프로그램이 아니다.(DB에 저장되어있지 않다.)


DECLARE (선택)
        선언부분(변수, 상수, 명시적커서, 사용자정의 예외사항)
    
BEGIN (필수)
        실행부분(SQL, 로직구현)

EXCEPTION (선택)
        예외사항 : 실행부분에서 발생한 오류에 대한 처리방법
        
END; (필수)
/

 

BEGIN
        DBMS_OUTPUT.PUT_LINE('오늘 하루도 행복하자!');     -- 출력.한줄씩출력
        DBMS_OUTPUT.PUT_LINE('2주동안에는 PLSQL개발자 마음으로 출근하자!');
END;
/

DBMS 출력 창을 열자.

 

# SQLPLUS 실행할때는 꼭 SET SERVEROUTPUT ON

로그인 후
SQL> SET SERVEROUTPUT ON    --> 출력패키지

SQL> BEGIN
  2          DBMS_OUTPUT.PUT_LINE('오늘 하루도 행복하자!');     -- 출력.한줄씩출력
  3          DBMS_OUTPUT.PUT_LINE('2주동안에는 PLSQL개발자 마음으로 출근하자!');
  4  END;
  5  /

SQL> /
오늘 하루도 행복하자!
2주동안에는 PLSQL개발자 마음으로 출근하자!

PL/SQL procedure successfully completed.

SQL>  --> 방금 실행한 문장 재실행

# SQLPLUS 내 바인드 변수 출력
print :b_sal    -- : 여기서는 콜론(:) 사용 무관
print b_comm

BEGIN
        DBMS_OUTPUT.PUT_LINE('TODAY''S : '||to_char(sysdate,'yyyy-mm-dd'));
        DBMS_OUTPUT.PUT_LINE('TOMORROW''S : '||to_char(sysdate+1,'yyyy-mm-dd'));
        
        DBMS_OUTPUT.PUT_LINE(q'[TODAY'S : ]'||to_char(sysdate,'yyyy-mm-dd'));
END;
/

 

# PL/SQL 에서 SQL함수 사용 시 주의 사항
- 프로시저문에서 사용할 수 있는 함수 : 단일행 함수
- 프로시저문에서 사용할 수 없는 함수 : DECODE, 그룹함수

예)
BEGIN
    v_sum := sum(v_sum);    -- 오류 : 프로시저문에서는 그룹함수를 사용 할 수 없다.
END;
/

>> Usually a PL/SQL compilation error.

 


 

■ 변수
- 데이터를 임시로 저장하는 메모리 영역
- 문자로 시작해야한다.
- 문자, 숫자, 특수문자(_,$,#) 포함할 수 있다.
- 변수이름의 길이는 30자 이하만 가능하다ㅏ.
- 예약어는 사용못한다.
        <SYS> SELECT * FROM v$reserved_words;        -- 예약어 목록
- 변수 선언시에 NOT NULL, CONSTANT(상수)로 지정된 변수에는 꼭 초기값을 할당해야 한다.
        - 상수는 한번 받은 값만 계속 사용해야한다. 

- 변수에 값을 할당하는 연산자는 :=DEFAULT
- 변수는 변하는 값을 계속 입력할 수 있다.

DECLARE
        v_name varchar2(20);    -- 선언만 했기 때문에 null 상태

BEGIN
        DBMS_OUTPUT.PUT_LINE('My name is : '||v_name);  -- 문자 || null -> 문자
        v_name := '홍길동';
        DBMS_OUTPUT.PUT_LINE('My name is : '||v_name);
        v_name := '박찬호';
        DBMS_OUTPUT.PUT_LINE('My name is : '||v_name);
END;
/
DECLARE
        v_event varchar2(20) := q'[Father's day!]';

BEGIN
        DBMS_OUTPUT.PUT_LINE('3rd Sunday in June is : '||v_event);
        v_event := q'[Mother's day!]';
        DBMS_OUTPUT.PUT_LINE('2rd Sunday in May is : '||v_event);
END;
/
DECLARE
        /* scalar data type : 단일값만 보유하는 변수 */
        v_a number(7);
        v_b number(3) := 100;
        v_c varchar2(10) not null := 'oracle'; -- not null 제약조건 초기값 필수
        v_d constant date default sysdate;
        v_e constant number(3) := 10;

BEGIN
        v_a := 200;
        DBMS_OUTPUT.PUT_LINE(v_a);
        DBMS_OUTPUT.PUT_LINE(v_b);
        DBMS_OUTPUT.PUT_LINE(v_c);
        DBMS_OUTPUT.PUT_LINE(v_d);
        DBMS_OUTPUT.PUT_LINE(v_e);
     -- v_d := sysdate + 1;        -- 오류 : 변수를 상수로 선언하여 다른 값을 가질 수 없다.
     -- DBMS_OUTPUT.PUT_LINE(v_d);
END;
/

 


 

DECLARE
        /* scalar data type */
        /* local variable : 선언된 블록 프로그램에서만 수행하는 변수(지역변수) */
        v_sal number := 1000;
        v_comm number := 100;
        v_tatal number;

BEGIN
        v_tatal := v_sal + v_comm;
        DBMS_OUTPUT.PUT_LINE(v_tatal);
END;
/

SELECT *
FROM hr.employees
WHERE salary > v_total; --> 오류 : v_total변수는 local variable이기때문에 블록바깥쪽에서 사용할 수 없다.

>> ORA-00904: "V_TOTAL": invalid identifier



■ 바인드변수(Bind Variable)
- 호스트 환경에서 생성한다.
- 호스트변수라고도 한다.
- variable(var) 키워드를 이용해서 생성한다.
- 바인드변수는 SQL문과 PLSQL 블록에서 사용할 수 있다.
- 바인드 변수를 사용할 때는 콜론(:) 키워드를 바인드변수 이름 앞에 입력해야 한다.
- global variable처럼 쓰고 싶을 때 사용한다.
- global variable : 프로그램 어디서든지 사용하는 변수(전역변수)
- 바인드변수는 익명 블록 구조에서만 사용한다.

variable b_total number

DECLARE
        v_sal number := 1000;
        v_comm number := 100;
BEGIN
        :b_total := v_sal + v_comm;
        DBMS_OUTPUT.PUT_LINE(:b_total);
END;
/

SELECT *
FROM hr.employees
WHERE salary > :b_total;
var b_total number
var b_sal number
var b_comm number

execute :b_sal := 1000
execute :b_comm := 100

DECLARE
        v_sal number := :b_sal;
        v_comm number := :b_comm;
BEGIN
        :b_total := v_sal + v_comm;
        DBMS_OUTPUT.PUT_LINE(:b_total);
END;
/
var b_total number
var b_sal number
var b_comm number

BEGIN
        :b_sal := 1000  -- execute 대신 BEGIN 안에서 값 할당 가능
        :b_comm := 100
END;
/
var aaa number := 10000;

SELECT salary
FROM hr.employees
WHERE salary > :aaa;

 

/* main block, outer block */
DECLARE
        v_outer_variable varchar2(20) := 'global variable'; -- 프로그램 내에서 사용
BEGIN
        /* sub block, inner block */
        DECLARE
                v_inner_variable varchar2(20) := 'local variable';  -- 서브블록에서만 사용
        BEGIN
                DBMS_OUTPUT.PUT_LINE(v_inner_variable);
                DBMS_OUTPUT.PUT_LINE(v_outer_variable);
        END;
        
        DBMS_OUTPUT.PUT_LINE(v_outer_variable);
     -- DBMS_OUTPUT.PUT_LINE(v_inner_variable);    -- 오류 : 본인블록 밖에서는 사용불가
     
END;
/   -- 마지막에만 표시!!

 

# 메인블럭, 서브블럭 변수명 동일할 경우, 기본적으로 자기블럭 내 선언된 변수를 우선순위로 둔다.

DECLARE
    v_father_name varchar2(20) := 'Patrick';
    v_date_of_birth date := to_date('1960-01-01','yyyy-mm-dd');
BEGIN
    DECLARE
        v_child_name varchar2(20) := 'Mike';
        v_date_of_birth date := to_date('1990-01-01','yyyy-mm-dd');
    BEGIN
        DBMS_OUTPUT.PUT_LINE('Father''s Name : '||v_father_name);
        DBMS_OUTPUT.PUT_LINE('Date of Birth : '||v_date_of_birth);  -- 서브블럭 내 선언된 1990-01-01
        DBMS_OUTPUT.PUT_LINE('Child''s Name : '||v_child_name); 
    END;
        
    DBMS_OUTPUT.PUT_LINE('Date of Birth : '||v_date_of_birth);      -- 메인블럭 내 선언된 1960-01-01
    
END;
/

 

# 메인블럭, 서브블럭 변수명 동일할 경우, 서브에서 메인블럭의 변수를 사용하고 싶다 -> 레이블 설정

<<outer>>    -- 레이블이 있는 메인블럭의 변수를 사용하겠다.
DECLARE
    v_father_name varchar2(20) := 'Patrick';
    v_date_of_birth date := to_date('1960-01-01','yyyy-mm-dd');
BEGIN
    DECLARE
        v_child_name varchar2(20) := 'Mike';
        v_date_of_birth date := to_date('1990-01-01','yyyy-mm-dd');
    BEGIN
        DBMS_OUTPUT.PUT_LINE('Father''s Name : '||v_father_name);
        DBMS_OUTPUT.PUT_LINE('Date of Birth : '||outer.v_date_of_birth);    -- 레이블.메인변수 1960-01-01
        DBMS_OUTPUT.PUT_LINE('Child''s Name : '||v_child_name);
        DBMS_OUTPUT.PUT_LINE('Date of Birth : '||v_date_of_birth);          -- 서브변수 1990-01-01
    END;
        
    DBMS_OUTPUT.PUT_LINE('Date of Birth : '||v_date_of_birth);              -- 메인변수 1960-01-01
    
END;
/
BEGIN <<outer>>     -- 이렇게 BEGIN안에 넣어서 사용도 가능
    DECLARE
        v_father_name varchar2(20) := 'Patrick';
        v_date_of_birth date := to_date('1960-01-01','yyyy-mm-dd');
    BEGIN
        DECLARE
            v_child_name varchar2(20) := 'Mike';
            v_date_of_birth date := to_date('1990-01-01','yyyy-mm-dd');
        BEGIN
            DBMS_OUTPUT.PUT_LINE('Father''s Name : '||v_father_name);
            DBMS_OUTPUT.PUT_LINE('Date of Birth : '||outer.v_date_of_birth);
            DBMS_OUTPUT.PUT_LINE('Child''s Name : '||v_child_name);
            DBMS_OUTPUT.PUT_LINE('Date of Birth : '||v_date_of_birth);
        END;
        
        DBMS_OUTPUT.PUT_LINE('Date of Birth : '||v_date_of_birth);
    
    END;
END outer;      -- 마무리
/

 

<<outer>>
DECLARE
    v_sal number(8,2) := 60000;
    v_comm number(8,2) := v_sal * 0.20; -- 60000* 0.20 = 12000
    v_message varchar2(50) := 'eligible for commission';
BEGIN
    DECLARE
        v_sal number(8,2) := 50000;
        v_comm number(8,2) := 0;
        v_total number(8,2) := v_sal + v_comm;  -- 50000
    BEGIN
        v_message := 'Clerk not '||v_message;   -- 메인변수 수정 Clerk not eligible for commission
        outer.v_comm := v_sal * 0.30;           -- 메인변수 수정 50000 * 0.30 = 15000
        DBMS_OUTPUT.PUT_LINE('**** sub block ****');
        DBMS_OUTPUT.PUT_LINE(v_sal);            -- 서브변수 50000
        DBMS_OUTPUT.PUT_LINE(v_comm);           -- 서브변수 0
        DBMS_OUTPUT.PUT_LINE(v_total);          -- 서브변수 50000
        DBMS_OUTPUT.PUT_LINE(v_message);        -- 메인변수 Clerk not eligible for commission
    END;

    DBMS_OUTPUT.PUT_LINE('**** main block ****');
    DBMS_OUTPUT.PUT_LINE(v_sal);                    -- 메인변수 60000
    DBMS_OUTPUT.PUT_LINE(v_comm);                   -- 메인변수 15000
    DBMS_OUTPUT.PUT_LINE('Salesman '||v_message);   -- 메인변수 Salesman Clerk not eligible for commission
END;
/

 


 

■ IF 조건 제어문
- PL/SQL에서 조건에 따라 선별적으로 작업을 수행할 수 있다.
- TRUE/FALSE
- BOOLEAN DATA TYPE (TRUE, FALSE, NULL)

IF 조건 THEN
    참
ELSIF 조건 THEN
    참
ELSIF 조건 THEN
    참
...
ELSE
    기본값
END IF; 

 

DECLARE
    v_flag boolean := true;        -- boolean : 문자형과 다르다.
BEGIN
    IF v_flag THEN
        DBMS_OUTPUT.PUT_LINE('참');
    END IF;
END;
/
DECLARE
    v_flag boolean := TRUE;
BEGIN
    IF v_flag THEN
        DBMS_OUTPUT.PUT_LINE('참');
    ELSE
        DBMS_OUTPUT.PUT_LINE('거짓');
    END IF;
END;
/

 

# 비교연산자
=, >, >=, <, <=, <>, !=, ^=

# 논리연산자
NOT, AND, OR

# 기타 비교연산자
BETWEEN AND, IN, LIKE

# NULL 연산자
IS NULL, IS NOT NULL

 

DECLARE
    v_a number;
BEGIN
    IF v_a IS NULL THEN
        DBMS_OUTPUT.PUT_LINE('변수에 NULL값으로 입력되어있습니다.');
    ELSE
        DBMS_OUTPUT.PUT_LINE(v_a);
    END IF;    
END;
/

 

DECLARE
    v_num1 number(2) := 10;
    v_num2 number(2) := 5;
    
BEGIN
    IF v_num1 >= v_num2 THEN
        DBMS_OUTPUT.PUT_LINE(v_num1 - v_num2);
        
    ELSIF v_num1 < v_num2 THEN
        DBMS_OUTPUT.PUT_LINE(v_num2 - v_num1);
    
    END IF;
END;
/
DECLARE
    v_num1 number(2) := 10;
    v_num2 number(2) := 5;
    
BEGIN
    IF v_num1 >= v_num2 THEN
        DBMS_OUTPUT.PUT_LINE(v_num1 - v_num2);
        
    ELSE
        DBMS_OUTPUT.PUT_LINE(v_num2 - v_num1);
    
    END IF;
END;
/

 

[문제]
v_sal : 10000
v_comm : null
((v_sal * 12) + (v_sal * 12 * v_comm))

DECLARE
    v_sal number := 10000;
    v_comm number;
    v_total number;
    
BEGIN
    IF v_comm IS NOT NULL THEN
        v_total := ((v_sal * 12) + (v_sal * 12 * v_comm));
        DBMS_OUTPUT.PUT_LINE(q'[(v_sal * 12) + (v_sal * 12 * v_comm) = ]'||v_total);
    ELSE
        v_total := (v_sal * 12);
        DBMS_OUTPUT.PUT_LINE(q'[v_sal * 12 = ]'||v_total);
    END IF;
END;
/

 


 

 CASE 표현식

DECLARE
    v_grade char(1) := upper('a');
    v_appraisal varchar2(30);
    
BEGIN
    v_appraisal := CASE v_grade
                        WHEN 'A' THEN '참잘했어요'
                        WHEN 'B' THEN '잘했어요'
                        WHEN 'C' THEN '다음에 잘해요'
                        ELSE '니가 사람이야!!!' 
                    END;
    DBMS_OUTPUT.PUT_LINE('등급은 '||v_grade||', 평가는 '||v_appraisal);
END;
/

 

DECLARE
    v_grade char(1) := upper('a');
    v_appraisal varchar2(30);
    
BEGIN
    v_appraisal := CASE 
                        WHEN v_grade = 'A' THEN '참잘했어요'
                        WHEN v_grade IN ('B','C') THEN '잘했어요'
                        WHEN v_grade = 'D' THEN '다음에 잘해요'
                        ELSE '니가 사람이야!!!' 
                    END;
    DBMS_OUTPUT.PUT_LINE('등급은 '||v_grade||', 평가는 '||v_appraisal);
END;
/

 


 

■ 반복문

1. LOOP 문
- 조건이 없이 반복적인 작업 수행
- EXIT : 반복문 종료하는 문

DECLARE
    v_cnt number := 1;
    
BEGIN
    LOOP
        DBMS_OUTPUT.PUT_LINE(v_cnt);
        v_cnt := v_cnt + 1;
        IF v_cnt = 11 THEN
            EXIT;        -- 무한루프 종료를 위해 조건과 함께 작성
        END IF;
    END LOOP;
END;
/

 

[문제] 화면에 1 ~ 10까지 출력하는 프로그램을 작성해주세요. 단, 4,8번은 출력 제외
DECLARE
    i number := 0;
    
BEGIN
    LOOP
        i := i + 1;
        
        IF i > 10 then
            EXIT;

        ELSIF i NOT IN (4,8) THEN
            DBMS_OUTPUT.PUT_LINE(i);

        END IF;
    END LOOP;
END;
/
DECLARE
    i number := 1;
    
BEGIN
    LOOP
        IF i IN (4,8) THEN
            NULL;    -- 아무것도 안함

        ELSE
            DBMS_OUTPUT.PUT_LINE(i);

        END IF;
        
        i := i + 1;
        EXIT WHEN i > 10;

    END LOOP;
END;
/

 

CONTINUE : 현재 루프를 종료하고 다음 루프로 반복하는 문

DECLARE
    i number := 1;
    
BEGIN
    LOOP
        IF i IN (4,8) THEN
            i := i + 1;
            CONTINUE;
            
        ELSE
            DBMS_OUTPUT.PUT_LINE(i);
            
        END IF;
        
        i := i + 1;
        EXIT WHEN i > 10;

    END LOOP;
END;
/
DECLARE
    i number := 0;
    
BEGIN
    LOOP
        i := i + 1;
        CONTINUE WHEN i = 4 OR i = 8;
        
        DBMS_OUTPUT.PUT_LINE(i);
        EXIT WHEN i = 10;
        
    END LOOP;
END;
/
>> CONTINUE를 만나 IF i IN (4,8) THEN 부터 반복 >> CONTINUE를 만나 i := i + 1 부터 반복

 

[문제] 1부터 100까지 합을 출력해주세요.

DECLARE
    i number := 1;      -- 카운터를 위한 변수.
    total number := 0;  -- 누적합을 위한 변수, 초기값이 NULL이면 계산이 안되니 0으로 지정.
                        -- 변수명 sum 불가. reserved_words는 사용이 안된다.
BEGIN
    LOOP
        total := total + i;
        i := i + 1;
        EXIT WHEN i > 100;
    END LOOP;

    DBMS_OUTPUT.PUT_LINE('1부터 100까지 합 : '||total);

END;
/

 

[문제] 1부터 100까지 짝수만 출력해주세요.

DECLARE
    i number := 1;
    
BEGIN
    DBMS_OUTPUT.PUT_LINE('1부터 100까지 짝수만 출력 : ');
    
    LOOP
        IF mod(i,2) = 0 THEN
            DBMS_OUTPUT.PUT_LINE(i);
        END IF;
    
        i := i + 1;
        EXIT WHEN i >100;

    END LOOP;   
    
END;
/