• Home
  • About
    • Young's Github Pages photo

      一日不作一日不食

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

오라클 DBMS 정리 11

08 Nov 2018

Reading time ~5 minutes

오라클 DBMS 정리 11 - PL/SQL with DML, CURSOR


PL/SQL에서 Query문 사용하기

  • DDL, DCL, DML 사용할 수 있다.
    • 다른건 다 같으나 SELECT만 좀 다르다.
  • SELECT는 INTO절이 들어가며 한 행만 조회되어야 한다.
  • FUNCTION, PROCEDURE가 매개변수를 가지고 Query를 사용하면 매개변수명과 WHERE절에서 사용하는 컬럼명이 달라야 한다.
    • 가능하면 변수명이 컬럼명과 같지 않도록 이름을 짓는다.
  • INSERT, UPDATE, DELETE는 수행한 행의 수를 반환하는 암시적(묵시적) 커서를 사용할 수 있다.
-- 암시적(묵시적) 커서 SQL(커서명) % 속성명
-- 포스트 아래에 커서정리 참고
SQL%속성
-- PL/SQL에서 DML 사용
BEGIN
    INSERT INTO cp_emp3(empno, ename, deptno, job, sal)
    VALUES(3333, '유재석', 10, '부장', 8000);

    COMMIT;
END;
/
  -- 암시적(묵시적) 커서 ROWCOUNT속성을 이용하면 
  -- 수행한 행의 수를 얻을 수 있다.

  DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT||'행 추가');

END;
/

11-01

  ...
  -- job의 데이터크기보다 큰 데이터를 넣기때문에 error!
  -- PL/SQL에서 INSERT는 성공 또는 실패(예외)
  INSERT INTO cp_emp3(empno, ename, deptno, job, sal)
  VALUES(3333, '유재석', 10, '부장님최고', 8000);

  COMMIT;
  DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT||'행 추가');
  
  -- 예외처리는 여기서
  EXCEPTION
  WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE('문제발생');

END;
/

11-02

-- PL/SQL은 값을 제어한 후에 SQL문을 수행한다
-- PL/SQL을 사용하면 입력값에 따라 다른 테이블에 INSERT 가능하다

-- 사원번호, 사원명, 연봉을 사용자에게 입력받아 cp_emp2테이블에
-- 연봉이 5000이상이면 cp_emp3테이블에 추가하고 5000미만이라면
-- cp_emp2 테이블에 추가하는 PL/SQL 작성
-- 단, cp_emp3 테이블에 추가되는 사원의 부서 번호는 10번부서로 추가

SET serveroutput ON
SET verify OFF

ACCEPT empno PROMPT '사원번호 : '
ACCEPT ename PROMPT '사원명 : '
ACCEPT sal PROMPT '연봉 : '

DECLARE
    empno cp_emp2.empno%TYPE;
    ename cp_emp2.ename%TYPE;
    sal NUMBER := &sal;
BEGIN                

    empno := &empno;
    ename := '&ename';
    
    IF sal >= 5000 THEN
        INSERT INTO cp_emp3(empno, ename, sal)
        VALUES(empno, ename, sal, 10);
        DBMS_OUTPUT.PUT_LINE('연봉이 5000이상이므로 cp_emp3 테이블에 추가');
    ELSE                        
        INSERT INTO cp_emp2(empno, ename, sal, hiredate)
        VALUES(empno, ename, sal, SYSDATE);                      
        DBMS_OUTPUT.PUT_LINE('연봉이 5000미만이므로 cp_emp2 테이블에 추가');
    END IF;
    
    DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT||'행 입력성공');
    COMMIT;
    
    EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Exception!');

END;
/
-- 업데이트는 수행 횟수를 알아야 한다(0~n개의 레코드가 바뀜)
-- PL/SQL에서 UPDATE 사용
SET serveroutput ON
   
DECLARE
    cnt NUMBER;
BEGIN
    UPDATE cp_emp2
    SET sal = 3000
    WHERE empno = 2221;

    -- 암시적(묵시적) 커서 SQL을 사용하여 변경한 행의 수를 얻는다
    cnt := SQL%ROWCOUNT;
    -- 변경된 사원이 1명일때만 Transcation을 완료하고 그렇지 않다면
    -- 작업을 취소.

    IF cnt = 1 THEN
        DBMS_OUTPUT.PUT_LINE(cnt||'건이 변경되었습니다');
        COMMIT;
    ELSE
        DBMS_OUTPUT.PUT_LINE('조회된 사원이 여러명이거나 없으므로 변경작업을 취소합니다..');
        ROLLBACK;
    END IF;
END;
/
-- 삭제
SET serveroutput ON
BEGIN
    DELETE FROM cp_emp2
    WHERE empno = 2222;
    
    DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT||'건이 삭제되었습니다.');
END;
/

SELECT

  • 조회된 결과를 저장할 INTO절이 정의됨
    • 조회된 컬럼값이 변수명에 들어감
    • 변수와 컬럼의 데이터형과 갯수는 일치해야 한다.
    • 부분이 나눠지기 때문에 컬럼명과 변수명이 같아도 된다(INSERT 처럼)
  • 조회결과가 한행이 아니라면 error가 발생!
    • 에러발생 시 EXCEPTION 절로 넘어감
SELECT 컬럼명, ... 
INTO   변수명, ...
FROM   테이블명
...
-- cp_emp2에서 사원번호가 7566인 사원의
-- 사원명, 입사일, 연봉을 조회하여 출력하는 PL/SQL
SET serveroutput ON

DECLARE
    ename cp_emp2.ename%TYPE;
    input_date VARCHAR2(10);
    sal cp_emp2.sal%TYPE;
BEGIN
    
    SELECT ename, TO_CHAR(hiredate, 'yyyy-mm-dd'), sal
    INTO ename, input_date, sal
    FROM cp_emp2
    WHERE empno = 7566;         
    
    DBMS_OUTPUT.PUT_LINE(ename||' / ' ||input_date||' / '
                         ||sal||' / '||SQL%ROWCOUNT);
    
    EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('문제발생');

END;
/
-- SELECT와 INSERT를 수행하는 PL/SQL 예제
-- 사원번호, 사원명, 부서번호, 직무를 입력받아 
-- cp_emp3 테이블에 추가하는 PL/SQL
-- 단, 사원의 연봉은 부서번호에 따라 동일 연봉으로 추가한다.
-- 10번 부서 - emp 테이블의 사원번호가 7566인 사원과 같은 연봉,
-- 20번 부서 - emp 테이블의 사원번호가 7698인 사원과 같은 연봉,
-- 30번 부서 - emp 테이블의 사원번호가 7782인 사원과 같은 연봉,
-- 그 외 부서번호가 입력되면 7788 사원의 연봉으로 추가
SET serveroutput ON
                                           
ACCEPT empno PROMPT '사원번호 입력 : '
ACCEPT ename PROMPT '사원명 입력 : '
ACCEPT deptno PROMPT '부서번호 입력 : '
ACCEPT job PROMPT '직무 입력 : '

DECLARE                         
    i_empno cp_emp3.empno%TYPE := &empno;
    i_ename cp_emp3.ename%TYPE := '&ename';
    i_deptno cp_emp3.deptno%TYPE := &deptno;
    i_job cp_emp3.job%TYPE := '&job';    
    
    sal cp_emp3.sal%TYPE;
    select_empno cp_emp.empno%TYPE := 7788;           
BEGIN        
  
    -- 입력되는 부서번호별로 참조할 연봉을 조회하기 위한
    -- 사원번호를 설정
    IF i_deptno = 10 THEN
        select_empno := 7566;  
    ELSIF i_deptno = 20 THEN
        select_empno := 7698;  
    ELSIF i_deptno = 30 THEN
        select_empno := 7782;    
    END IF;
    
    -- 설정된 사원번호별로 연봉을 조회
    SELECT sal
    INTO sal
    FROM emp
    WHERE empno = select_empno;
    
    -- 조회된 연봉으로 추가 작업
    INSERT INTO cp_emp3(empno, ename, deptno, job, sal)
    VALUES(i_empno, i_ename, i_deptno, i_job, sal);    
    
    DBMS_OUTPUT.PUT_LINE(i_empno||'번 사원정보를 추가하였습니다.');
    DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT||'건 입력 성공');
    COMMIT;
    
    EXCEPTION
    WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('에러발생');
END;
/

CURSOR

  • 다른 언어로 보면 포인터
  • 암시적(묵시적) 커서와 명시적 커서로 제공됨
    • 암시적 커서
      • 개발자가 정의하지 않아도 쿼리문을 실행하면 자동으로 생성되는 커서.
      • 커서명 : SQL
    • 명시적커서
      • 개발자가 정의해서 사용하는 커서
      • 생명주기를 가진다
        • 선언->열기->인출->닫기
      • 사용할 수 있는 속성이 많다
  • 커서는 인라인뷰를 사용, 메모리를 계속 사용한다.
    • 꼭 닫기로 닫아줘야 메모리 누수를 잡을 수 있다.
-- 암시적(묵시적)커서
SQL%속성명

-- 명시적커서
커서명%속성명

-- 커서 속성사용
커서명%속성명

-- 커서 동작 제어
동작명 커서명
  • 커서의 동작과 속성

11-03

  • 커서 사용법
    1. 선언 : 커서가 실행할 쿼리문을 정의
    2. 열기
    3. 인출
    4. 닫기
DECLARE
  -- 1
  CURSOR 커서명 IS 
    SELECT ...
BEGIN
  -- 2
  OPEN 커서명;
  -- 3
  LOOP
    FETCH 커서명 INTO 변수명,... ;

    -- 커서에서 인출한 값이 없다면 반복문을 빠져나가야한다
    EXIT WHEN (커서명%NOTFOUND);
        
    -- 값이 있었을 때 처리할 코드들..
  END LOOP;
  -- 4
  CLOSE 커서명;  
END;
/
SET serveroutput ON

DECLARE
  -- 1. 선언
  CURSOR cur_dept IS
    SELECT deptno, dname, loc
    FROM dept;
           
  dept_data dept%ROWTYPE;
BEGIN      
  -- 2. 열기
  OPEN cur_dept;
    
    
  LOOP
    -- 3. 인출(커서가 움직이며 레코드의 컬럼값을 얻기)
    FETCH cur_dept INTO dept_data;
      
    EXIT WHEN(cur_dept%NOTFOUND);
      
    DBMS_OUTPUT.PUT_LINE(dept_data.deptno||' '||dept_data.dname
                                              ||' '||dept_data.loc);
  END LOOP;

  -- 4. 닫기
  CLOSE cur_dept;
END;
/ 
--- 데이터는 가져오는부분(처리하는부분)과 보여주는 부분(VIEW)으로 나눔

-- 부서번호를 입력받아 해당부서에 근무하는
-- 사원번호, 사원명, 연봉, 입사일을 조회하고,
-- 조회한 후 총 사원 수와, 연봉합을 구하시오.

SET serveroutput ON
SET verify OFF

ACCEPT deptno PROMPT '부서번호 : '

DECLARE
    CURSOR cur_emp IS
     SELECT empno, ename, sal,
               TO_CHAR(hiredate, 'yyyy-mm-dd') hiredate
     FROM   emp
     WHERE  deptno = &deptno;
     
    TYPE rec_emp IS RECORD (
        empno    emp.empno%TYPE,
        ename emp.ename%TYPE,
        sal emp.sal%TYPE,
        hiredate VARCHAR2(10)
    );
    
    -- 테이블 생성 (데이터 처리와 보여주는 부분을 분리시키기 위해)
    TYPE tab_emp IS TABLE OF rec_emp
        INDEX BY BINARY_INTEGER;
  
    emp_data rec_emp;                             
    arr_emp tab_emp;
  
    total_sal NUMBER := 0;
    i NUMBER := 0;
BEGIN
    IF cur_emp%ISOPEN THEN
        CLOSE cur_emp;
    END IF;
    
    OPEN cur_emp;
    
    -- 데이터 처리하는 부분    
    LOOP
        FETCH cur_emp INTO emp_data;
        EXIT WHEN cur_emp%NOTFOUND;
        i := i+1;
        arr_emp(i) := emp_data;
    END LOOP;                           
                               
    -- 화면 구성하는 부분
    IF i != 0 THEN
        DBMS_OUTPUT.PUT_LINE('사원번호 사원명  연봉   입사일');

        FOR idx IN 1 .. arr_emp.COUNT LOOP        
            DBMS_OUTPUT.PUT_LINE(arr_emp(idx).empno||
                ' '||arr_emp(idx).ename||' '||arr_emp(idx).sal||
                ' '||arr_emp(idx).hiredate);
                                                
            total_sal := total_sal + arr_emp(idx).sal;  
        END LOOP;                                   
        
        DBMS_OUTPUT.PUT(&deptno||'번 부서 사원수 : '||cur_emp%ROWCOUNT);    
        DBMS_OUTPUT.PUT_LINE(', 연봉합 : '||total_sal);        
    ELSE
      DBMS_OUTPUT.PUT_LINE(&deptno||'번 부서는 사원이 없습니다.');
    END IF;                                         
    CLOSE cur_emp;
END;
/

헷갈리는 내용 추가복습

  • TABLE TYPE
    • 테이블변수명(인덱스) := 레코드변수명
      • 레코드할당
    • 테이블변수명.COUNT
      • 테이블 레코드 수를 반환
  • 커서명 속성
    • 커서명%ROWCOUNT
      • 커서로 패치한 수를 반환
    • 커서명%NOTFOUND
      • 커서가 패치할 레코드가 없으면 FALSE를 반환
      • LOOP문 빠져나갈때 EXIT 조건으로 사용

숙제풀이



오라클DBMS