251113 TIL

2025. 11. 13. 18:40Courses/아이티윌 오라클 DBA 과정

문제

-- [문제17] get_annual_sal 함수를 생성해주세요.
SELECT
    employee_id,
    salary,
    commission_pct,
    salary * 12 + salary * 12 * commission_pct annual_salary_1,
    salary * 12 + salary * 12 * nvl(commission_pct, 0)commission_pct_annual_salary_2,
    get_annual_sal(salary, commission_pct) annual_salary_3,
    get_annual_sal(sal=>salary, comm=>commission_pct) annual_salary_4,
    get_annual_sal(sal=>salary) annual_salary_5,
    get_annual_sal(salary) annual_salary_6
FROM hr.employees;

CREATE OR REPLACE FUNCTION get_annual_sal(sal IN number, comm IN number := 0)
RETURN number
IS
BEGIN
    RETURN sal * 12 + sal * 12 * nvl(comm, 0);
END get_annual_sal;
/

desc get_annual_sal
/*
FUNCTION get_annual_sal RETURNS NUMBER
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 SAL                            NUMBER                  IN
 COMM                           NUMBER                  IN     DEFAULT
 */

함수

함수 부작용

  • 함수는 표현식에서 함수를 호출할 때 부작용 발생
  • 같은 테이블에 대해서 동시에 SELECT, DML 불가
CREATE OR REPLACE FUNCTION query_call(p_id IN number)
RETURN number
IS
    v_sal number;
BEGIN
    SELECT salary
    INTO v_sal
    FROM hr.employees
    WHERE employee_id = p_id;

    RETURN v_sal;
EXCEPTION
    WHEN no_data_found THEN
        RETURN v_sal;
END query_call;
/

SELECT employee_id, query_call(employee_id) FROM hr.employees;

UPDATE hr.employees
SET salary = query_call(101) * 1.1
WHERE employee_id = 101;
-- ORA-04091: table HR.EMPLOYEES is mutating, trigger/function may not see it
  • 함수 내부에서 쿼리를 하기보다는 기능을 구현하는데 사용하는 것이 좋음

패키지의 필요성

CREATE OR REPLACE FUNCTION validate_comm(p_comm IN number)
RETURN boolean
IS
    v_comm number;
BEGIN
    SELECT max(commission_pct)
    INTO v_comm
    FROM hr.employees;

    IF p_comm > v_comm THEN
        RETURN FALSE;
    ELSE 
        RETURN TRUE;
    END IF;
END validate_comm;
/

desc validate_comm;
/*
FUNCTION validate_comm RETURNS BOOLEAN
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P_COMM                         NUMBER                  IN
*/

CREATE OR REPLACE PROCEDURE reset_comm(p_comm IN number)
IS
    v_comm number := 0.1;
BEGIN
    IF validate_comm(p_comm) THEN
        dbms_output.put_line('OLD : ' || v_comm);
        v_comm := p_comm;
        dbms_output.put_line('NEW : ' || v_comm);
    ELSE
        RAISE_APPLICATION_ERROR(-20000, '기존 최고값을 넘을 수 없습니다');
    END IF;
END reset_comm;
/

desc rest_comm;
/*
PROCEDURE reset_comm
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P_COMM                         NUMBER                  IN
*/

  • validate_comm 함수는 reset_comm 프로시저와 함께 사용해야 함
  • 관리해야 하는 프로그램이 2개임
  • 한 번에 묶어서 관리하기 위해 패키지 필요

PACKAGE(패키지)

  • 관련성 있는 서브 프로그램(프로시저, 함수), 변수, 데이터 타입, 커서, 예외사항을 모아 놓은 프로그램
  • Specification(Public, Global) 은 패키지의 인터페이스
    • 패키지 외부에서 참조할 수 있는 변수, 상수, 커서, 예외사항, 데이터 타입, 서브 프로그램을 선언
  • Body(Private) 는 서브 프로그램에 대한 코드를 정의

Package Specification

CREATE OR REPLACE PACKAGE package_name
IS
    pulbic 생성자() 선언
END package name;
/

Package Body

CREATE OR REPLACE PACKAGE BODY package_name
IS
    private 생성자(변수, 상수, 커서, 예외사항, 데이터 타입) 선언
    서브 프로그램 코드 정의
[BEGIN]
END package_name;
/

패키지 생성

-- package specification
CREATE OR REPLACE PACKAGE comm_pkg
IS
    g_comm number := 0.1;
    PROCEDURE reset_comm(p_comm IN number);
END comm_pkg;
/

-- package body
CREATE OR REPLACE PACKAGE BODY comm_pkg
IS
    FUNCTION validate_comm(p_comm IN number)
    RETURN boolean
    IS
        v_comm number;
    BEGIN
        SELECT max(commission_pct)
        INTO v_comm
        FROM hr.employees;

        IF p_comm > v_comm THEN
            RETURN FALSE;
        ELSE 
            RETURN TRUE;
        END IF;
    END validate_comm;

    PROCEDURE reset_comm(p_comm IN number)
    IS
    BEGIN
        IF validate_comm(p_comm) THEN
            dbms_output.put_line('OLD : ' || g_comm);
            g_comm := p_comm;
            dbms_output.put_line('NEW : ' || g_comm);
        ELSE
            RAISE_APPLICATION_ERROR(-20000, '기존 최고값을 넘을 수 없습니다');
        END IF;
    END reset_comm;
END comm_pkg;
/

패키지 소스 조회

SELECT text FROM user_source WHERE name = 'COMM_PKG' AND type = 'PACKAGE' ORDER BY line;
SELECT text FROM user_source WHERE name = 'COMM_PKG' AND type = 'PACKAGE BODY' ORDER BY line;

전방 참조

  • PACKAGE BODY에 정의되는 생성자는 전방 참조만 가능
-- package specification
CREATE OR REPLACE PACKAGE comm_pkg
IS
    PROCEDURE reset_comm(p_comm IN number);
END comm_pkg;
/

-- package body
CREATE OR REPLACE PACKAGE BODY comm_pkg
IS
    g_comm number := 0.1;

    PROCEDURE reset_comm(p_comm IN number)
    IS
    BEGIN
        IF validate_comm(p_comm) THEN
            dbms_output.put_line('OLD : ' || g_comm);
            g_comm := p_comm;
            dbms_output.put_line('NEW : ' || g_comm);
        ELSE
            RAISE_APPLICATION_ERROR(-20000, '기존 최고값을 넘을 수 없습니다');
        END IF;
    END reset_comm;

    FUNCTION validate_comm(p_comm IN number)
    RETURN boolean
    IS
        v_comm number;
    BEGIN
        SELECT max(commission_pct)
        INTO v_comm
        FROM hr.employees;

        IF p_comm > v_comm THEN
            RETURN FALSE;
        ELSE 
            RETURN TRUE;
        END IF;
    END validate_comm;
END comm_pkg;
/
/*
LINE/COL ERROR
-------- -----------------------------------------------------------------
8/9      PL/SQL: Statement ignored
8/12     PLS-00313: 'VALIDATE_COMM' not declared in this scope
*/
  • 헤더 부분을 미리 선언해서 사용
-- package specification
CREATE OR REPLACE PACKAGE comm_pkg
IS
    PROCEDURE reset_comm(p_comm IN number);
END comm_pkg;
/

-- package body
CREATE OR REPLACE PACKAGE BODY comm_pkg
IS
    g_comm number := 0.1;

    FUNCTION validate_comm(p_comm IN number)
    RETURN boolean;

    PROCEDURE reset_comm(p_comm IN number)
    IS
    BEGIN
        IF validate_comm(p_comm) THEN
            dbms_output.put_line('OLD : ' || g_comm);
            g_comm := p_comm;
            dbms_output.put_line('NEW : ' || g_comm);
        ELSE
            RAISE_APPLICATION_ERROR(-20000, '기존 최고값을 넘을 수 없습니다');
        END IF;
    END reset_comm;

    FUNCTION validate_comm(p_comm IN number)
    RETURN boolean
    IS
        v_comm number;
    BEGIN
        SELECT max(commission_pct)
        INTO v_comm
        FROM hr.employees;

        IF p_comm > v_comm THEN
            RETURN FALSE;
        ELSE 
            RETURN TRUE;
        END IF;
    END validate_comm;
END comm_pkg;
/

Package Overloading

  • 동일한 이름의 생성자(프로시저, 함수)를 만들 수 있음
  • 형식 파라미터의 개수, 모드(IN, OUT, IN OUT), 데이터 유형이 다를 경우 동일한 이름의 서브 프로그램을 생성할 수 있음
CREATE OR REPLACE PACKAGE pack_over
IS
    TYPE date_tab_type IS TABLE OF date INDEX BY pls_integer;
    TYPE num_tab_type IS TABLE OF number INDEX BY pls_integer;

    PROCEDURE init(tab OUT date_tab_type, n IN number);
    PROCEDURE init(tab OUT num_tab_type, n IN number);

END pack_over;
/

CREATE OR REPLACE PACKAGE BODY pack_over
IS
    PROCEDURE init(tab OUT date_tab_type, n IN number)
    IS
    BEGIN
        FOR i IN 1..n LOOP
            tab(i) := sysdate;
        END LOOP;
    END init;

    PROCEDURE init(tab OUT num_tab_type, n IN number)
    IS
    BEGIN
        FOR i IN 1..n LOOP
            tab(i) := i;
        END LOOP;
    END init;
END pack_over;
/

DECLARE
    date_tab pack_over.date_tab_type;
    num_tab pack_over.num_tab_type;
BEGIN
    pack_over.init(date_tab, 5);
    pack_over.init(num_tab, 10);

    FOR i IN date_tab.first..date_tab.last LOOP
        dbms_output.put_line(date_tab(i));
    END LOOP;

    FOR i IN num_tab.first..num_tab.last LOOP
        dbms_output.put_line(num_tab(i));
    END LOOP;
END;
/

문제

-- [문제18] 사원을 조회하는 프로그램을 작성해주세요.
-- execute emp_find.find(100)
-- 100 King
-- execute emp_find.find('King')
-- 100 King
-- 156 King

SELECT * FROM hr.employees WHERE employee_id = 100;
SELECT * FROM hr.employees WHERE last_name = 'King';

프로시저에서 출력

-- 프로시저에서 출력
CREATE OR REPLACE PACKAGE emp_find
IS
    PROCEDURE find(id IN number);
    PROCEDURE find(name IN varchar2);
END emp_find;
/

CREATE OR REPLACE PACKAGE BODY emp_find
IS  
    PROCEDURE find(id IN number)
    IS
        v_emp employees%rowtype;
    BEGIN
        SELECT *
        INTO v_emp
        FROM hr.employees
        WHERE employee_id = id;

        dbms_output.put_line(v_emp.employee_id || ' ' ||v_emp.last_name);
    EXCEPTION
        WHEN no_data_found THEN
            dbms_output.put_line(id || '번 사원은 존재하지 않습니다.');
    END find;

    PROCEDURE find(name IN varchar2)
    IS
        CURSOR emp_cur IS
            SELECT *
            FROM hr.employees
            WHERE last_name = initcap(name)
            ORDER BY 1;
        v_cnt number := 0;
    BEGIN
        FOR i IN emp_cur LOOP
            dbms_output.put_line(i.employee_id || ' ' || i.last_name);
            v_cnt := emp_cur%rowcount;
        END LOOP;

        IF v_cnt = 0 THEN
            dbms_output.put_line(name || ' 사원은 존재하지 않습니다.');
        END IF;
    EXCEPTION 
        WHEN others THEN
            dbms_output.put_line(sqlerrm);
    END find;
END emp_find;
/

-- 출력
execute emp_find.find(100);
execute emp_find.find('king');
execute emp_find.find(300);
execute emp_find.find('oracle');

호출 프로그램에서 출력

-- 호출 프로그램에서 출력
CREATE OR REPLACE PACKAGE emp_find
IS
    TYPE emp_tab_type IS TABLE OF employees%rowtype INDEX BY pls_integer;

    PROCEDURE find(emp_tab OUT emp_tab_type, id IN number);
    PROCEDURE find(emp_tab OUT emp_tab_type, name IN varchar2);
END emp_find;
/

CREATE OR REPLACE PACKAGE BODY emp_find
IS
    PROCEDURE find(emp_tab OUT emp_tab_type, id IN number)
    IS
    BEGIN
        SELECT *
        INTO emp_tab(1)
        FROM hr.employees
        WHERE employee_id = id;
    EXCEPTION
        WHEN no_data_found THEN
            dbms_output.put_line(id || '번 사원은 존재하지 않습니다.');
    END find;

    PROCEDURE find(emp_tab OUT emp_tab_type, name IN varchar2)
    IS
        CURSOR emp_cur IS
            SELECT *
            FROM hr.employees
            WHERE last_name = initcap(name)
            ORDER BY 1;

        v_cnt number := 0;
    BEGIN
        FOR i IN emp_cur LOOP
            v_cnt := emp_cur%rowcount;
            emp_tab(v_cnt) := i;
        END LOOP;

        IF v_cnt = 0 THEN
            dbms_output.put_line(name || ' 사원은 존재하지 않습니다.');
        END IF;
    EXCEPTION 
        WHEN others THEN
            dbms_output.put_line(sqlerrm);
    END find;
END emp_find;
/

-- 출력
DECLARE
    v_emp_tab emp_find.emp_tab_type;
BEGIN
    emp_find.find(v_emp_tab, 100);

    FOR i IN v_emp_tab.first..v_emp_tab.last LOOP
        dbms_output.put_line(v_emp_tab(i).employee_id || ' ' || v_emp_tab(i).last_name);
    END LOOP;

    dbms_output.new_line();
    emp_find.find(v_emp_tab, 'king');

    FOR i IN v_emp_tab.first..v_emp_tab.last LOOP
        dbms_output.put_line(v_emp_tab(i).employee_id || ' ' || v_emp_tab(i).last_name);
    END LOOP;

    dbms_output.new_line();
    emp_find.find(v_emp_tab, 300);

    dbms_output.new_line();
    emp_find.find(v_emp_tab, 'oracle');
END;
/

'Courses > 아이티윌 오라클 DBA 과정' 카테고리의 다른 글

251114 TIL  (0) 2025.11.14
251112 TIL  (0) 2025.11.12
251111 TIL  (0) 2025.11.11
251110 TIL  (0) 2025.11.10
251107 TIL  (0) 2025.11.07