• Home
  • About
    • Young's Github Pages photo

      一日不作一日不食

    • About
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

오라클 DBMS 정리 12

09 Nov 2018

Reading time ~6 minutes

오라클 DBMS 정리 12 - PL/SQL CURSOR(using FOR), EXCEPTION


FOR을 사용한 CURSOR

  • 생명주기를 개발자가 관리하지 않음
  • 선언 -> 열기 -> 인출 -> 닫기
  • 두가지 형태(선언, 비선언)로 커서에서 FOR가 사용됨
    • 선언하여 커서 사용
      • 입력값으로 조건을 사용하면 값의 검증을 수행할 수 없다
    • 선언없이 커서 사용
  • 선언하여 사용
    1. 선언
    2. 인출(FETCH 사용 X)
DECLARE
    CURSOR 커서명 IS
     SELECT ... ;
...
BEGIN
...
    FOR 레코드변수명 IN 커서명 
    LOOP
        레코드변수명.컬럼명으로 사용
    END LOOP;
...
END;
/
-- 사원번호, 사원명, 입사일, 연봉을 조회
-- 입사일은 년-월-일 분기의 형태
SET serveroutput ON

DECLARE
    -- 1.선언
    CURSOR cur_emp IS
     SELECT empno, ename,
            TO_CHAR(hiredate,'yyyy-mm-dd q') hiredate, sal
     FROM   emp;
BEGIN

    -- FOR가 시작되며 커서 열리고 자동으로 FETCH, CLOSE까지 수행                     

    -- 2. FOR를 사용한 인출
    -- (FETCH를 사용하지 않는다.)
    FOR emp_data IN cur_emp LOOP
        DBMS_OUTPUT.PUT_LINE(emp_data.empno||' '
                             ||emp_data.ename||' '
                             ||emp_data.hiredate||' '
                             ||emp_data.sal);    
    END LOOP;
    -- LOOP가 끝나면서 커서 닫힘

END;
/
  • 선언하지 않고 커서 사용
    • FOR의 IN에서 조회쿼리 작성
BEGIN
...
    FOR 레코드변수명 IN (SELECT ... ) LOOP

        레코드변수명 : 조회된 결과를 얻을 수 있다

    END LOOP;
...
END;
-- 부서 번호를 입력받아 해당 부서에 따른
-- 사원번호, 사원명, 입사일, 연봉을 조회
-- 입사일은 년-월-일 분기의 형태   
-- 부서번호가 10,20,30이 아니라면
-- 10번 부서로 조회하여 출력할 것

SET serveroutput ON
SET verify OFF

ACCEPT deptno PROMPT '부서번호 : '

DECLARE         
-- 입력값에 대한 IF문을 사용한 제약설정(값 검증)은
-- DECLARE-BEGIN사이에서 할 수 없다
    i_deptno NUMBER(2) := &deptno;
BEGIN                     

    IF &deptno NOT IN (10, 20, 30) THEN
      i_deptno := 10;
    END IF;
    
    FOR emp_data IN 
      (SELECT empno, ename,
          TO_CHAR(hiredate,'yyyy-mm-dd q') hiredate, sal
      FROM   emp
      WHERE  deptno = i_deptno)
    LOOP                                 
        DBMS_OUTPUT.PUT_LINE(emp_data.empno||' '
             ||emp_data.ename||' '||emp_data.hiredate||' '
             ||emp_data.sal);    
    END LOOP;            
    
END;
/     

SYS_REFCURSOR

  • 주로 PROCEDURE에서 커서의 제어권을 외부로(실행하는 쪽) 넘길 때 사용
    • sqlplus에서 PROCEDURE를 실행한 경우 REFCURSOR로 저장
    • JAVA에서 PROCEDURE를 실행한 경우 ResultSet으로 저장
  • 값에대한 검증을 한 후 쿼리를 수행할 수 있는 특징이 있다
  • 사용법
    • 3, 4번은 외부에서 호출하는 경우에는 작성하지 않는다
      1. 커서 선언
      2. 커서 열기
      3. 인증
      4. 닫기
DECLARE
    커서명 SYS_REFCURSOR;
BEGIN
    OPEN 커서명 FOR SELECT쿼리.. ;
...
    LOOP
        FETCH 커서명 INTO 변수명, ...
    END LOOP;

    CLOSE 커서명;
END;
-- 사원테이블에서 매니저번호를 입력받아 매너지가 관리하는
-- 사원의 사원번호, 사원명, 입사일, 부서번호, 연봉을 조회
-- 입력된 매니저번호가 emp테이블에 존재하는 매니저 번호일때만
-- 검색을 수행한다. (7839, 7782, 7698, 7902, 7566, 7788)

SET serveroutput ON
SET verify OFF

    ACCEPT mgr PROMPT '매니저번호 : '

DECLARE
    i_mgr NUMBER;
    
    -- 1. SYS_REFCURSOR 선언 : 커서명 SYS_REFCURSOR;
    -- 데이터형처럼 선언
    cur_mgr SYS_REFCURSOR;
    
    -- 조회결과를 저장할 레코드형 선언
    TYPE rec_emp IS RECORD (
      empno emp.empno%TYPE,
      ename emp.ename%TYPE,
      hiredate emp.hiredate%TYPE,
      deptno emp.deptno%TYPE,
      sal emp.sal%TYPE
    );    
    
    emp_rec rec_emp;
BEGIN
    -- 입력된 매니저 번호가 존재하는지 검색
    -- 커서 돌리면 받아오는 값은 레코드 변수!
    -- '.'를 사용해서 컬럼명을 참조한다.**
    FOR temp_rec IN (SELECT DISTINCT mgr
                     FROM emp
                     WHERE mgr = &mgr)
    LOOP
        i_mgr := temp_rec.mgr;
    END LOOP;


    IF i_mgr IS NOT NULL THEN
        
        -- 2. 커서 열기 : 조회 쿼리문 작성
        OPEN cur_mgr FOR SELECT empno, ename, hiredate, 
                                deptno, sal
                         FROM emp
                         WHERE mgr = i_mgr;
       

        LOOP
            -- 3. 인출
            FETCH cur_mgr INTO emp_rec;        
            EXIT WHEN cur_mgr%NOTFOUND;
            
            DBMS_OUTPUT.PUT_LINE(emp_rec.empno||' '
                ||emp_rec.ename||' '
                ||TO_CHAR(emp_rec.hiredate,'yyyy-mm-dd')||' '
                ||emp_rec.deptno||' '||emp_rec.sal);
        END LOOP;
        
        IF cur_mgr%ISOPEN THEN
            -- 4. 닫기
            CLOSE cur_mgr;
            DBMS_OUTPUT.PUT_LINE('커서를 닫습니다..');
        END IF;  
              
    ELSE
        DBMS_OUTPUT.PUT_LINe('&mgr 은 없는 매니저번호입니다.');
  END IF;
END;
/

EXCEPTION Handling(예외 처리)

  • 코드에서 비정상적인 상황이 발생했을 때 그에대한 대비코드를 작성해 놓는 것.
  • 예외처리의 최상위 객체는 'OTHERS'
  • 예외처리는 PL/SQL 코드의 가장 마지막줄에 정의
  • 예외가 발생하면 코드의 실행을 멈추고 예외처리코드가 실행된다.
    • RAISE를 사용하면 개발자가 예외로 강제로 발생시켜 처리할 수 있다.
...
    ...
    EXCEPTION
    WHEN 예외처리객체명 THEN
        예외가 발생 시 동작할 코드;
    ...    
END;
/
  • 예외처리객체
    • DUP_VAL_ON_INDEX
      • INSERT 시 유일 인덱스(PK)에 중복값을 입력했을 때
    • NO_DATA_FOUND
      • SELECT가 조회된 결과가 없을 때
    • TOO_MANY_ROWS
      • SELECT가 여러행을 조회할 때
    • ZERO_DIVIDE
      • 0으로 나눴을 때
    • INVALID_CURSOR
      • 커서가 잘못된 연산을 수행했을 때
    • INVALID_NUMBER
      • 숫자로 변경하지 못할 때
    • OTHERS
      • 모든 예외를 다 처리할 수 있음
      • 최상위 예외이기 때문에 EXCEPTION에서 가장 먼저 정의하게 되면 하위 예외처리 객체가 예외 처리하기 전에 OTHER가 처리하게 됨
-- 아이디, 이름, 비밀번호를 입력받아 table_primary
-- 테이블에 레코드를 삽입하는 PL/SQL 작성
SET serveroutput ON
SET verify OFF
    ACCEPT name PROMPT '이름 : '
    ACCEPT id PROMPT '아이디 : '
    ACCEPT pw PROMPT '비밀번호 : '
DECLARE
    name table_primary.name%TYPE;
    id table_primary.id%TYPE := '&id';
    pw table_primary.pw%TYPE := '&pw';
BEGIN
    -- 문자열을 입력받아 입력받은 문자열의 길이가
    -- 저장할 수 있는 크기가 아니라면 예외로는 처리할 수 없고
    -- 코드로 처리해야 됨
    IF LENGTH('&name') > 10 THEN
        DBMS_OUTPUT.PUT_LINE('입력가능한 글자수를 초과하였습니다');  
    ELSE
        name := '&name';

        INSERT INTO table_primary(name, id, pw)
        VALUES(name, id, pw);
    
        DBMS_OUTPUT.PUT_LINE('추가 성공했습니다.');
        COMMIT;
    END IF;

  EXCEPTION                                          
    -- 무결성  제약조건 위배 시(PK에 중복값 입력 시)
    WHEN DUP_VAL_ON_INDEX THEN
        DBMS_OUTPUT.PUT_LINE('입력하신 '||id||'는 이미 사용중입니다.');
        -- 예외처리 객체가 제공하는 메시지 : sqlerrm
        DBMS_OUTPUT.PUT_LINE( sqlerrm );
        -- 예외처리 객체가 제공하는 에러코드 : sqlcode
        DBMS_OUTPUT.PUT_LINE( sqlcode );
END;
/

12-01

  • sqlerrm는 ORA-00001 출력
  • sqlcode는 -1출력
    • 에러코드는 ORA메시지와 안맞을 수도 있음
-- SELECT로 여러 행을 조회하거나 한 행을 조회할 때 예외처리
-- 사원번호를 입력받아 cp_emp3테이블에서 해당 사원의
-- 사원명, 부서번호, 직무, 연봉 조회하는 PL/SQL

SET serveroutput ON
SET verify OFF

    ACCEPT empno PROMPT '사원번호 : '

DECLARE
    i_empno number := &empno;
  
    TYPE rec_emp IS RECORD(
        ename cp_emp3.ename%TYPE,
        deptno cp_emp3.deptno%TYPE,
        job cp_emp3.job%TYPE,
        sal cp_emp3.sal%TYPE
    );
    
    emp_rec rec_emp;
BEGIN             

    SELECT ename, deptno, job, sal
    INTO emp_rec
    FROM cp_emp3
    WHERE empno = i_empno;
    
    DBMS_OUTPUT.PUT_LINE(emp_rec.ename||' '
                         ||emp_rec.deptno||' '
                         ||emp_rec.job||' '
                         ||emp_rec.sal);
                                                
    EXCEPTION
        WHEN TOO_MANY_ROWS THEN
            DBMS_OUTPUT.PUT_LINE(i_empno||'번 사원이 한 건 이상 존재합니다.');
        WHEN NO_DATA_FOUND THEN
            DBMS_OUTPUT.PUT_LINE(i_empno||'번 사원이 존재하지 않습니다.'); 
        WHEN OTHERS THEN
        -- OTHERS에서 모든 예외를 다 처리할 수 있다.
        -- 최상위 예외이기 때문에 EXCEPTION에서 가장 먼저 정의하게되면
        -- 하위 예외처리 객체가 예외를 처리하기 전에 OTHERS가 처리
            DBMS_OUTPUT.PUT_LINE('인식하지 못한 예외');                                            
END;
/     
...
    EXCEPTION
        WHEN OTHERS THEN                                     
        -- OTHERS안에서 비교구문으로 
        -- sqlcode를 이용해서 에러처리도 가능!               
            IF sqlcode = 100 THEN
                DBMS_OUTPUT.PUT_LINE('사원정보가 조회되지 않습니다..');
            END IF;
        
            IF sqlcode = -1422 THEN
                DBMS_OUTPUT.PUT_LINE('사원번호로 여러명의 사원이 조회됩니다.');
            END IF;
        
...

사용자 정의 예외처리

  • RAISE를 사용하면 개발자가 예외를 발생시켜 처리할 수 있다
  • 개발자가 오라클에서 제공하는 예외처리 객체를 사용하지 않고 에러코드와 메시지를 정의하고 제공하여 예외를 발생시키고 처리하는 방법
  • 에러코드
    • -20000 ~ -20999 중 하나의 코드를 사용
RAISE_APPLICATION_ERROR(에러코드, '제공할 메시지');
    EXCEPTION
-- 예외가 발생했을 때 ORACLE에서 제공하는 형식의 예외메시지를 출력하고
-- 싶을 때 사용자 정의 예외처리를 한다.
-- RAISE_APPLICATION_ERROR(에러코드, '제공할 메세지');
        WHEN OTHERS THEN
            RAISE_APPLICATION_ERROR(-20001, '사용자 정의 예외');
END; 

12-02


FUNCTION

  • 사용자 정의 함수
  • 자주 사용될 코드를 미리 작성하고 필요한 곳에서 사용하기 위해 만드는 것
    • 쿼리문안에서 사용 (간접실행)
      • 단독으로 실행될 수 없는 상태
  • 반환형 존재
    • 처리한 결과를 내보낼 때 사용
  • module로 구분이 된다
    • FUNCTION, PROCEDURE, PACKAGE, TRIGGER는 모듈로 구분됨
    • 모듈 당 SQL 파일 하나로 생각하면 됨
  • 컴파일하면 user_procedures 딕셔너리에 추가됨
    • user_procedures, user_objects 딕셔너리에서 확인 가능
    • FUNCTION은 객체기 때문에 백업하면 PL/SQL과 다르게 백업이 가능하다
  • 작성법
    • 매개변수(parameter) : 함수 외부의 데이터를 함수 내부로 전달할 때 사용
    • 매개변수를 넣고 연산 후 반환형을 갖는 결과가 반환됨
    • 매개변수는 여러개 받을 수 있지만 반환형은 한개
CREATE OR REPLACE FUNCTION 함수명(매개변수 ...)
RETURN 반환형
IS
    변수 선언
BEGIN
    코드 작성
    RETURN 결과;
END;
/
  • Parameter와 Argument.
    • 함수에 넣는값 - Argument(인수)
    • 함수로 받는 값 - Parameter(매개변수)
  • 컴파일
    • 성공 또는 실패함
    • show error - 실패시 발생한 에러를 출력(sqlplus문)
SQL> @경로\파일명.sql
  • 실행
    • 간접실행 : 쿼리문에 넣어 실행
    • 반환형이 반드시 있어야 된다(없어도 컴파일 되므로 주의!)
-- SELECT에서 함수 사용
SELECT 함수명(값)
FROM   테이블명;

-- INSERT에서 함수 사용
INSERT 테이블명(컬럼명)
VALUES (함수명(값),..)

숙제풀이



오라클DBMS