DECLARE
    TYPE my_array_type IS TABLE OF varchar2(20) INDEX BY pls_integer;   -- 1차원 배열 선언
    v_array my_array_type;
    
BEGIN
    v_array(0) := 'zoro';
    v_array(1) := 'one';
    v_array(2) := 'two';
    v_array(3) := 'three';
    v_array(4) := 'four';
    v_array(9) := 'nine';
    v_array(-10) := 'negative ten';
    
    DBMS_OUTPUT.PUT_LINE(v_array(-10));

    DBMS_OUTPUT.PUT_LINE(v_array.FIRST);
    DBMS_OUTPUT.PUT_LINE(v_array.LAST);
    
    /* 
    FOR i IN v_array.FIRST..v_array.LAST LOOP        -- 오류 : i 값이 연속적이지 않아 값을 못찾음
            DBMS_OUTPUT.PUT_LINE(v_array(i));        -- ORA-01403: no data found
    END LOOP;
    */
    
    FOR i IN v_array.FIRST..v_array.LAST LOOP
        IF v_array.EXISTS(i) THEN               -- i 값이 있다면 True
            DBMS_OUTPUT.PUT_LINE(v_array(i));
        END IF;
    END LOOP;   
END;
/
negative ten
-10
9
negative ten
zoro
one
two
three
four
nine

 

 

DECLARE
    TYPE my_array_type IS TABLE OF varchar2(20) INDEX BY pls_integer;   -- 1차원 배열 선언
    v_array my_array_type;
    idx pls_integer;
    
BEGIN
    v_array(0) := 'zoro';
    v_array(1) := 'one';
    v_array(2) := 'two';
    v_array(3) := 'three';
    v_array(4) := 'four';
    v_array(9) := 'nine';
    v_array(-10) := 'negative ten';
    
    idx := v_array.FIRST;
    
    WHILE idx IS NOT NULL LOOP
        DBMS_OUTPUT.PUT_LINE(v_array(idx));
        idx := v_array.NEXT(idx);
    END LOOP;   
END;
/
negative ten
zoro
one
two
three
four
nine

 

DECLARE
    TYPE my_array_type IS TABLE OF varchar2(20) INDEX BY pls_integer;   -- 1차원 배열 선언
    v_array my_array_type;
    idx pls_integer;
    
BEGIN
    v_array(0) := 'zoro';
    v_array(1) := 'one';
    v_array(2) := 'two';
    v_array(3) := 'three';
    v_array(4) := 'four';
    v_array(9) := 'nine';
    v_array(-10) := 'negative ten';
    
    idx := v_array.LAST;                        -- 마지막부터
    
    WHILE idx IS NOT NULL LOOP
        DBMS_OUTPUT.PUT_LINE(v_array(idx));
        idx := v_array.PRIOR(idx);              -- 뒤로 카운트
    END LOOP;   
END;
/
nine
four
three
two
one
zoro
negative ten

 

[문제] 배열 변수에 있는 100,101,102,103,104,200 사원들의 근무 연수를 출력하고
근무연수가 20년이상 되었으면 급여를 10% 인상한 급여로 수정하는 프로그램을 작성.

DECLARE
    TYPE id_arr_type IS TABLE OF number INDEX BY pls_integer;   
    v_id id_arr_type;

    v_years number;
    result varchar2(100);
    
BEGIN
    v_id(1) := 100;
    v_id(2) := 101;
    v_id(3) := 102;
    v_id(4) := 103;
    v_id(5) := 104;
    v_id(6) := 200;
    
    FOR i IN v_id.FIRST..v_id.LAST LOOP
        SELECT trunc(months_between(sysdate,hire_date)/12)  -- 결과가 단일행만 나와야 한다.
        INTO v_years
        FROM hr.employees
        WHERE employee_id = v_id(i);
        
        IF v_years >= 20 THEN
            UPDATE hr.employees
            SET salary = salary * 1.1
            WHERE employee_id = v_id(i);
            
            result := '10% 인상되었습니다.';
        ELSE
            result := '인상할 수 없습니다.';
        END IF;
        
    DBMS_OUTPUT.PUT_LINE(v_id(i)||'는 근무 연수가 '||v_years||'년 입니다. 급여는 '||result);
    
    END LOOP;
    
END;
/
100는 근무 연수가 20년 입니다. 급여는 10% 인상되었습니다.
101는 근무 연수가 18년 입니다. 급여는 인상할 수 없습니다.
102는 근무 연수가 22년 입니다. 급여는 10% 인상되었습니다.
103는 근무 연수가 17년 입니다. 급여는 인상할 수 없습니다.
104는 근무 연수가 16년 입니다. 급여는 인상할 수 없습니다.
200는 근무 연수가 20년 입니다. 급여는 10% 인상되었습니다.

 

[문제] 배열변수안에 있는 사원 번호 값을 기준으로 (100,110,200)
그 사원의 last_name, hire_date, department_name 정보를 레코드 변수에 담아놓은 후 화면에 출력

DECLARE
    TYPE id_arr_type IS TABLE OF number INDEX BY pls_integer;    --배열 타입 선언
    v_id id_arr_type;
    
    TYPE rec_type IS RECORD        -- 레코드 타입 선언
        (v_name hr.employees.last_name%type,
         v_date hr.employees.hire_date%type,
         v_dept_name hr.departments.department_name%type);
    v_rec rec_type;

BEGIN
    v_id(1) := 100;
    v_id(2) := 110;
    v_id(3) := 200;

    FOR i IN v_id.FIRST..v_id.LAST LOOP
        SELECT last_name, hire_date, (SELECT department_name
                                        FROM hr.departments
                                        WHERE department_id = e.department_id) department_name
        INTO v_rec
        FROM hr.employees e
        WHERE employee_id = v_id(i);
        
        DBMS_OUTPUT.PUT_LINE(v_id(i)||' 사원의 이름은 '||v_rec.v_name||', 입사한 날짜는 '||v_rec.v_date||
                                ', 근무 부서이름은 '||v_rec.v_dept_name||' 입니다.');
    END LOOP;
END;
/
100 사원의 이름은 King, 입사한 날짜는 03/06/17, 근무 부서이름은 Executive 입니다.
110 사원의 이름은 Chen, 입사한 날짜는 05/09/28, 근무 부서이름은 Finance 입니다.
200 사원의 이름은 Whalen, 입사한 날짜는 03/09/17, 근무 부서이름은 Administration 입니다.

 


# nested table(중첩테이블)
- index by table 과 달리 인덱스키(요소번호)는 자동으로 1번부터 시작해서 최대 2G 까지 생성할 수 있다.

DECLARE
    TYPE tab_char_type IS TABLE OF  varchar2(10);
    v_city tab_char_type := tab_char_type('서울','대전','부산','광주');
BEGIN
    DBMS_OUTPUT.PUT_LINE(v_city.FIRST);
    DBMS_OUTPUT.PUT_LINE(v_city.LAST);
    DBMS_OUTPUT.PUT_LINE(v_city.NEXT(2));
    DBMS_OUTPUT.PUT_LINE(v_city.PRIOR(3));

    FOR i IN v_city.FIRST..v_city.LAST LOOP
        IF v_city.EXISTS(i) THEN
            DBMS_OUTPUT.PUT_LINE(v_city(i));
        ELSE
            DBMS_OUTPUT.PUT_LINE(i||' 요소는 존재하지 않습니다.');
        END IF;
    END LOOP;
END;
/
1
4
3
2
서울
대전
부산
광주

 

DECLARE
    TYPE tab_char_type IS TABLE OF  varchar2(10);
    v_city tab_char_type := tab_char_type('서울','대전','부산','광주');     -- 선언 시 크기 고정.
                                                                         -->> ORA-06533: Subscript beyond count
BEGIN
    v_city.extend;      -- 한개의 새로운 요소를 추가한다.
    v_city.extend(5);  -- N개의 새로운 요소를 추가한다.
    v_city.extend(3,1); -- 1번의 인덱스 값을 가지고 3개 복제
    v_city(5) := '대구';
    
    DBMS_OUTPUT.PUT_LINE(v_city.COUNT);
    DBMS_OUTPUT.PUT_LINE(v_city.FIRST);
    DBMS_OUTPUT.PUT_LINE(v_city.LAST);
    
    FOR i IN v_city.FIRST..v_city.LAST LOOP
        IF v_city.EXISTS(i) THEN
            DBMS_OUTPUT.PUT_LINE(v_city(i));
        ELSE
            DBMS_OUTPUT.PUT_LINE(i||' 요소는 존재하지 않습니다.');
        END IF;
    END LOOP;
END;
/
13
1
13
서울
대전
부산
광주
대구





서울
서울
서울

 

DECLARE
    TYPE id_arr_type IS TABLE OF number;
    v_id id_arr_type := id_arr_type(100,110,200);
    
    TYPE rec_type IS RECORD
        (v_name hr.employees.last_name%type,
         v_date hr.employees.hire_date%type,
         v_dept_name hr.departments.department_name%type);
    v_rec rec_type;

BEGIN

    FOR i IN v_id.FIRST..v_id.LAST LOOP
        SELECT last_name, hire_date, (SELECT department_name
                                        FROM hr.departments
                                        WHERE department_id = e.department_id) department_name
        INTO v_rec
        FROM hr.employees e
        WHERE employee_id = v_id(i);
        
        DBMS_OUTPUT.PUT_LINE(v_id(i)||' 사원의 이름은 '||v_rec.v_name||', 입사한 날짜는 '||v_rec.v_date||
                                ', 근무 부서이름은 '||v_rec.v_dept_name||' 입니다.');
    END LOOP;
END;
/
100 사원의 이름은 King, 입사한 날짜는 03/06/17, 근무 부서이름은 Executive 입니다.
110 사원의 이름은 Chen, 입사한 날짜는 05/09/28, 근무 부서이름은 Finance 입니다.
200 사원의 이름은 Whalen, 입사한 날짜는 03/09/17, 근무 부서이름은 Administration 입니다.

 


# varray(VARRAY-SIZE ARRAY) 가변길이 배열방식
- VARRAY 고정된 상한값이 있다.
- 최대 2G 까지 사용한다.

DECLARE
    TYPE tab_char_type IS VARRAY(5) OF  varchar2(10);       -- VARRAY(5): 최대 5개 까지 지정
    v_city tab_char_type := tab_char_type('서울','대전','부산','광주');
BEGIN
    
    v_city(5) := '인천';  -- 오류
    -- ORA-06533: Subscript beyond count
    -- 5개로 지정했지만 위 선언에서 4개 사용으로 확정. 추가 입력이 안됨.

    v_city.EXTEND;
    v_city(5) := '인천';
    -- 확장 후 입력 가능

    v_city.EXTEND(2);   -- 오류
    -- ORA-06532: Subscript outside of limit
    -- 상한값 이상으로 EXTEND 할 수 없다.    


    v_city.DELETE(2);   -- 오류
    -- PLS-00306: wrong number or types of arguments in call to 'DELETE'
    -- VARRAY에서는 DELETE 메소드를 사용할 수 없다.

    v_city.TRIM;        -- 제일 뒤에 있는 요소 삭제
    v_city.TRIM(2);     -- 제일 뒤에 있는 n개 요소 삭제
    
    FOR i IN v_city.FIRST..v_city.LAST LOOP
        IF v_city.EXISTS(i) THEN
            DBMS_OUTPUT.PUT_LINE(v_city(i));
        ELSE
            DBMS_OUTPUT.PUT_LINE(i||' 요소는 존재하지 않습니다.');
        END IF;
    END LOOP;    
END;
/

 

DECLARE
    TYPE id_arr_type IS VARRAY(3) OF number;
    v_id id_arr_type := id_arr_type(100,110,200);
    
    TYPE rec_type IS RECORD
        (v_name hr.employees.last_name%type,
         v_date hr.employees.hire_date%type,
         v_dept_name hr.departments.department_name%type);
    v_rec rec_type;

BEGIN

    FOR i IN v_id.FIRST..v_id.LAST LOOP
        SELECT last_name, hire_date, (SELECT department_name
                                        FROM hr.departments
                                        WHERE department_id = e.department_id) department_name
        INTO v_rec
        FROM hr.employees e
        WHERE employee_id = v_id(i);
        
        DBMS_OUTPUT.PUT_LINE(v_id(i)||' 사원의 이름은 '||v_rec.v_name||', 입사한 날짜는 '||v_rec.v_date||
                                ', 근무 부서이름은 '||v_rec.v_dept_name||' 입니다.');
    END LOOP;
END;
/

 


 

# 2차원배열
레코드 타입을 먼저 선언한 후 INDEX BY TABLE 생성 해야 한다.

FOR i IN 1..5 LOOP
    SELECT *
    INTO 2차원배열변수(i)
    FROM hr.departments
    WHERE department_id = i * 10;
END LOOP;
DECLARE
    TYPE dept_rec_type IS RECORD
        (id number, name varchar2(30), mgr number, loc number);     -- 레코드 타입 수동 선언
    v_rec dept_rec_type;  

BEGIN
    FOR i IN 1..5 LOOP
        SELECT *
        INTO v_rec
        FROM hr.departments
        WHERE department_id = i * 10;

        DBMS_OUTPUT.PUT_LINE(v_rec.id||' '||v_rec.name||' '||v_rec.mgr||' '||v_rec.loc);
    END LOOP;
END;
/
DECLARE
    v_rec hr.departments%rowtype;       -- 레코드 선언, 칼럼명은 테이블 동일

BEGIN
    FOR i IN 1..5 LOOP
        SELECT *
        INTO v_rec
        FROM hr.departments
        WHERE department_id = i * 10;

        DBMS_OUTPUT.PUT_LINE(v_rec.department_id||' '||v_rec.department_name||' '||v_rec.manager_id||' '||v_rec.location_id);
    END LOOP;
END;
/
10 Administration 200 1700
20 Marketing 201 1800
30 Purchasing 114 1700
40 Human Resources 203 2400
50 Shipping 121 1500

 

DECLARE
    TYPE dept_rec_type IS RECORD
        (id number, name varchar2(30), mgr number, loc number);
    v_rec dept_rec_type;
    
    TYPE dept_tab_type IS TABLE OF v_rec%type INDEX BY pls_integer;
    v_tab dept_tab_type;        -- 2차원 배열
    
BEGIN
    FOR i IN 1..5 LOOP
        SELECT *
        INTO v_tab(i)
        FROM hr.departments
        WHERE department_id = i * 10;
    END LOOP;
    
    FOR i IN v_tab.FIRST..v_tab.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(v_tab(i).id||' '||v_tab(i).name||' '||v_tab(i).mgr||' '||v_tab(i).loc);
    END LOOP;
END;
/
DECLARE
    TYPE dept_rec_type IS RECORD
        (id number, name varchar2(30), mgr number, loc number);         -- 레코드 선언
    
    TYPE dept_tab_type IS TABLE OF dept_rec_type INDEX BY pls_integer;  -- 레코드 타입을 그대로 사용한다.
    v_tab dept_tab_type;
    
BEGIN
    FOR i IN 1..5 LOOP
        SELECT *
        INTO v_tab(i)
        FROM hr.departments
        WHERE department_id = i * 10;
    END LOOP;
    
    FOR i IN v_tab.FIRST..v_tab.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(v_tab(i).id||' '||v_tab(i).name||' '||v_tab(i).mgr||' '||v_tab(i).loc);
    END LOOP;
END;
/
DECLARE
    TYPE dept_tab_type IS TABLE OF hr.departments%rowtype INDEX BY pls_integer; -- 테이블 그대로 사용 시 rowtype으로 선언
    v_tab dept_tab_type;
    
BEGIN
    FOR i IN 1..5 LOOP
        SELECT *
        INTO v_tab(i)
        FROM hr.departments
        WHERE department_id = i * 10;
    END LOOP;
    
    FOR i IN v_tab.FIRST..v_tab.LAST LOOP    -- 1~5 반복 출력
        DBMS_OUTPUT.PUT_LINE(v_tab(i).department_id||' '||v_tab(i).department_name||' '||v_tab(i).manager_id||' '||
                                v_tab(i).location_id);
    END LOOP;
END;
/
10 Administration 200 1700
20 Marketing 201 1800
30 Purchasing 114 1700
40 Human Resources 203 2400
50 Shipping 121 1500

 


■ CURSOR
- SQL문 실행 메모리 영역
- parse, bind, execute(active set), fetch(active set 결과를 화면에 출력할지, 프로그램이면 변수에 load하는 단계)
- 암시적 커서(Implicit Cursor)
    - 오라클이 자동으로 커서 관리를 한다.
    - SELECT...INTO 절
        - 반드시 1개 행 값만 FETCH해야 한다.
        - 0 : NO_DATE_FOUND
        - 2개이상 : TOO_MANY_ROWS
    - DML(INSERT, UPDATE, DELETE, MERGE), TCL(COMMIT, ROLLBACK, SAVEPOINT)
- 명시적 커서(Explicit Cursor)
    - 여러개의 행을 FETCH해야 한다면 명시적 커서를 이용해야 한다.
    - 프로그래머가 커서를 관리 해야한다.
    - 0건, 1건, 여러건의 행이든 상관없이 사용할 수 있다.

DECLARE
    /* 1. 커서 선언 */
    CURSOR emp_cur IS 
                SELECT last_name
                FROM hr.employees
                WHERE department_id = 20;
    
    -- v_name varchar2(30);
    v_name hr.employees.last_name%type;     -- 커서 내 칼럼 수 만큼 스칼라 변수 선언
    
BEGIN
    /* 2. 커서 open : 메모라 할당, SELECT문에 대해서 parse(문법체크,의미분석체크,실행계획), bind, execute */
    /* active set 결과*/
    OPEN emp_cur;
    
    /* 3. FETCH */
    /* active set 결과에 첫번째 데이터를 변수에 로드 작업한다. */
    /* 여러건의 데이터가 있으면 꼭 반복문안에서 fetch작업을 수행해야한다.*/
    LOOP
        FETCH emp_cur INTO v_name;
        EXIT WHEN emp_cur%NOTFOUND;     -- FETCH 행이 없으면 TRUE, 있으면 FALSE를 리턴하는 속성
        DBMS_OUTPUT.PUT_LINE(v_name);
    END LOOP;
    
    /* 4. cursor close : cursor 해지 */    
    CLOSE emp_cur;
END;
/
Hartstein
Fay

 

DECLARE
    CURSOR emp_cur IS 
                SELECT e.last_name, e.salary, d.department_name
                FROM hr.employees e, hr.departments d
                WHERE e.department_id = 20
                AND d.department_id = 20;

    v_rec emp_cur%rowtype;      -- 레코드로 선언
    
BEGIN
    IF NOT emp_cur%ISOPEN THEN      -- 오픈 안되어있다면 커서오픈
        OPEN emp_cur;
    END IF;

    LOOP
        FETCH emp_cur INTO v_rec;
        EXIT WHEN emp_cur%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(v_rec.last_name);
        DBMS_OUTPUT.PUT_LINE(v_rec.salary);
        DBMS_OUTPUT.PUT_LINE(v_rec.department_name);
    END LOOP;
    
    DBMS_OUTPUT.PUT_LINE(emp_cur%ROWCOUNT);     -- FETCH 작업 행의 수 출력
    
    IF emp_cur%ISOPEN THEN      -- 커서가 오픈되어있다면 종료
        CLOSE emp_cur;
    END IF;
END;
/
DECLARE
    CURSOR emp_cur IS 
                SELECT e.last_name, e.salary, d.department_name
                FROM hr.employees e, hr.departments d
                WHERE e.department_id = 20
                AND d.department_id = 20;
BEGIN
    FOR v_rec IN emp_cur LOOP
        DBMS_OUTPUT.PUT_LINE(v_rec.last_name);
        DBMS_OUTPUT.PUT_LINE(v_rec.salary);
        DBMS_OUTPUT.PUT_LINE(v_rec.department_name);
    END LOOP;
END;
/
BEGIN
    FOR v_rec IN (SELECT e.last_name, e.salary, d.department_name
                    FROM hr.employees e, hr.departments d
                    WHERE e.department_id = 20
                    AND d.department_id = 20) LOOP
        DBMS_OUTPUT.PUT_LINE(v_rec.last_name);
        DBMS_OUTPUT.PUT_LINE(v_rec.salary);
        DBMS_OUTPUT.PUT_LINE(v_rec.department_name);
    END LOOP;
END;
/
Hartstein
13000
Marketing
Fay
6000
Marketing
2

 

■ 명시적 커서 속성
명시적커서이름%NOTFOUND : FETCH 한 행이 없으면 TRUE, 있으면 FALSE 를 리턴하는 속성
명시적커서이름%FOUND : FETCH 한 행이 있으면 TRUE, 없으면 FALSE를 리턴하는 속성
명시적커서이름%ROWCOUND : FETCH 한 행의 수를 리턴하는 속성
명시적커서이름%ISOPEN : 명시적 커서가 OPEN 되어 있으면 TRUE, 아니면 FALSE


 

'Oracle SQL' 카테고리의 다른 글

19 PL/SQL PROCEDURE  (0) 2023.11.01
18 PL/SQL EXCEPTION  (1) 2023.10.31
16 PL/SQL RECORD, Array  (0) 2023.10.27
15 PL/SQL WHILE LOOP, FOR, Implicit Cursor  (1) 2023.10.26
14 PL/SQL Variable, CASE, LOOP  (0) 2023.10.25