[문제] 사원번호를 입력값으로 받아서 사번, 이름, 부서이름을 출력하는 프로시저를 생성하세요. |
CREATE OR REPLACE PROCEDURE id_proc (p_id IN number) IS v_id number; v_name varchar2(30); v_dept_name varchar2(30); BEGIN SELECT e.employee_id, e.last_name, d.department_name INTO v_id, v_name, v_dept_name FROM hr.employees e, hr.departments d WHERE e.department_id = d.department_id AND e.employee_id = p_id; DBMS_OUTPUT.PUT_LINE('사원번호 : '||v_id||' 사원이름 : '||upper(v_name)||' 부서이름 : '||v_dept_name); EXCEPTION WHEN no_data_found THEN DBMS_OUTPUT.PUT_LINE(p_id||'사원은 존재하지 않습니다.'); WHEN others THEN DBMS_OUTPUT.PUT_LINE(sqlerrm); END; / |
/* 호출 */ EXECUTE id_proc(100) EXECUTE id_proc(p_id => 100) |
[문제] 부서코드를 입력하면 사원번호,이름,부서이름을 출력하는 프로시저를 생성해주세요. |
CREATE OR REPLACE PROCEDURE dept_sawon(dept_id number) IS CURSOR dept_cur IS SELECT employee_id, last_name, (SELECT department_name FROM hr.departments WHERE department_id = e.department_id) department_name FROM hr.employees e WHERE department_id = dept_id; v_rec dept_cur%rowtype; dept_null EXCEPTION; BEGIN FOR v_rec IN dept_cur LOOP IF v_rec.employee_id IS NULL THEN RAISE dept_null; ELSE DBMS_OUTPUT.PUT_LINE(v_rec.employee_id||' '||v_rec.last_name||' '||v_rec.department_name); END IF; END LOOP; EXCEPTION WHEN dept_null THEN DBMS_OUTPUT.PUT_LINE(v_rec.employee_id||' 부서가 없습니다.'); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(SQLERRM); END dept_sawon; / >>> EXCEPTION 발생을 안함. FOR문은 결과가 없다면 END IF 로 간다. > 의미없이 돌아감 |
↓ 개선 : OPEN - FETCH - CLOSE 사용 |
CREATE OR REPLACE PROCEDURE dept_sawon(dept_id number) IS CURSOR dept_cur IS SELECT employee_id, last_name, (SELECT department_name FROM hr.departments WHERE department_id = e.department_id) department_name FROM hr.employees e WHERE department_id = dept_id; v_rec dept_cur%rowtype; dept_null EXCEPTION; BEGIN OPEN dept_cur; -- 메모리할당 후 행을 데이터를 포인트 지정 LOOP FETCH dept_cur INTO v_rec; -- 데이터를 조작하기 위해 변수에 대입 IF v_rec.employee_id IS NULL THEN -- FETCH 후 데이터가 NULL >> EXCEPTION 이동 RAISE dept_null; ELSIF dept_cur%NOTFOUND THEN -- FETCH 후 데이터가 끝나면 END IF <> NULL EXIT; ELSE DBMS_OUTPUT.PUT_LINE(v_rec.employee_id||' '||v_rec.last_name||' '||v_rec.department_name); END IF; END LOOP; CLOSE dept_cur; EXCEPTION WHEN dept_null THEN DBMS_OUTPUT.PUT_LINE(dept_id||' 부서가 없습니다.'); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(SQLERRM); END dept_sawon; / |
/* 호출 */ execute dept_sawon(20) execute dept_sawon(200) |
[문제] 자신의 부서 평균 급여보다 더 많이 받는 사원은 'YES' 아니면 'NO'를 리턴하는 함수를 작성해주세요. |
CREATE OR REPLACE FUNCTION avg_dept(p_id IN number, p_sal IN number) RETURN varchar2 IS v_sal number; is_null EXCEPTION; BEGIN SELECT avg(salary) INTO v_sal FROM hr.employees WHERE department_id = p_id; IF v_sal IS NULL THEN RAISE is_null; ELSIF p_sal > v_sal THEN RETURN 'YES'; ELSE RETURN 'NO'; END IF; EXCEPTION WHEN is_null THEN DBMS_OUTPUT.PUT_LINE(p_id||' 부서 데이터가 없습니다.'); RETURN NULL; END avg_dept; / |
/* 호출 */ SELECT employee_id, salary, department_id, avg_dept(department_id,salary) avg_dept FROM hr.employees; BEGIN DBMS_OUTPUT.PUT_LINE(avg_dept(300,10000)); END; / |
CREATE OR REPLACE FUNCTION query_call(p_id IN number) RETURN number IS v_sal number; v_year number; BEGIN SELECT salary, trunc(months_between(sysdate,hire_date)/12) INTO v_sal, v_year FROM hr.employees WHERE employee_id = p_id; IF v_year > 20 THEN RETURN v_sal * 1.2; ELSIF v_year < 20 AND v_year >= 17 THEN RETURN v_sal * 1.1; ELSE RETURN v_sal; END IF; END query_call; / |
/* 호출 */ SELECT employee_id, salary, query_call(employee_id) FROM hr.employees; |
UPDATE hr.employees -- 업데이트하려는데 SET salary = query_call(100) -- query_call 함수 내 hr.employees 같은 테이블을 SELECT 작업 WHERE employee_id = 100; -- ORA-04091: table HR.EMPLOYEES is mutating, trigger/function may not see it >> query_call() 내 select가 업데이트 전인지 후인지 모르기 때문에 에러 >> 동일 테이블에서 동시에 SELECT + DML 작업이 안된다. |
# SQL 표현식에서 함수를 호출할 때 부작용이 발생할 수 있다.
- 함수를 통해서 SELECT하고 있는데 함수 안에 동일한 테이블에 대해서 DML하면 부작용 발생한다.
- 함수를 통해서 DML하려고 하는데 함수 안에 동일한 테이블에 대해서 SELECT하면 부작용이 발생한다.
- 함수를 통해서 조회하고 있는데 함수안에 동일한 테이블에 대해서 SELECT하면 문제 없다.
■ LOCAL SUBPROGRAM
DECLARE TYPE emp_id_type IS TABLE OF number; -- n배열, 중첩테이블 1번부터 시작 v_id emp_id_type := emp_id_type(100,101,102); v_emp hr.employees%rowtype; -- 레코드 /* 프로그램 내에서만 사용하는 FUNCTION 생성 */ FUNCTION tax(p_salary IN number) RETURN number IS BEGIN RETURN p_salary * 0.8; END; /* 프로그램 내에서만 사용하는 PROCEDURE 생성 */ PROCEDURE message IS BEGIN DBMS_OUTPUT.PUT_LINE('꿈을 이루자.'); END; BEGIN FOR i IN v_id.FIRST..v_id.LAST LOOP SELECT * INTO v_emp FROM hr.employees WHERE employee_id = v_id(i); DBMS_OUTPUT.PUT_LINE('사원번호 : '||v_emp.employee_id||' Tax : '||tax(v_emp.salary)); END LOOP; message; END; / |
CREATE OR REPLACE FUNCTION validation_comm(v_comm IN number) RETURN BOOLEAN -- TRUE, FALSE, NULL IS v_max_comm number; BEGIN SELECT max(commission_pct) INTO v_max_comm FROM hr.employees; IF v_comm > v_max_comm THEN RETURN FALSE; ELSE RETURN TRUE; END IF; END validation_comm; / CREATE OR REPLACE PROCEDURE reset_comm(p_comm IN number) -- 실행 시 validation_comm 함수 필요 IS g_comm number := 0.1; BEGIN IF validation_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; / >> validation_comm 함수와 reset_comm 프로시저는 종속관계 |
BEGIN reset_comm(0.2); END; / BEGIN reset_comm(0.5); -- ORA-20000: 기존 최고값을 넘을 수 없습니다. END; / |
■ PACKAGE 패키지
관련성있는 서브프로그램(프로시저, 함수), 변수, DATA TYPE을 모아놓은 프로그램
1. SPEC(PUBLIC) : 필수 CREATE OR REPLACE PACKAGE comm_pkg IS g_comm number := 0.1; -- 글로벌 변수선언. 어디서든 사용가능 PROCEDURE reset_comm(p_comm IN number); -- OPEN하려는(밖에서 사용하고 싶다면) 헤더부분만 작성(IS절 위) END; / 2. BODY(PRIVATE) : 선택 ★ Package Body에 기술되어있는 서브프로그램은 전방참조만 가능하다. CREATE OR REPLACE PACKAGE BODY comm_pkg IS FUNCTION validation_comm(v_comm IN number) RETURN BOOLEAN; /* global procedure */ PROCEDURE reset_comm(p_comm IN number) IS BEGIN IF validation_comm(p_comm) THEN DBMS_OUTPUT.PUT_LINE('OLD : '||g_comm); g_comm := p_comm; -- g_comm 변수가 글로벌로 선언되어있어 섹션 내 실행할때마다 누적 업데이트 됨 DBMS_OUTPUT.PUT_LINE('NEW : '||g_comm); ELSE RAISE_APPLICATION_ERROR(-20000,'기존 최고값을 넘을 수 없습니다.'); END IF; END reset_comm; /* private function */ FUNCTION validation_comm(v_comm IN number) RETURN BOOLEAN -- TRUE, FALSE, NULL IS v_max_comm number; -- local 변수 BEGIN SELECT max(commission_pct) INTO v_max_comm FROM hr.employees; IF v_comm > v_max_comm THEN RETURN FALSE; ELSE RETURN TRUE; END IF; END validation_comm; END comm_pkg; / |
EXECUTE DBMS_OUTPUT.PUT_LINE(comm_pkg.g_comm) EXECUTE comm_pkg.reset_comm(0.3) |
SELECT * FROM user_source WHERE name = 'COMM_PKG' AND TYPE = 'PACKAGE'; SELECT * FROM user_source WHERE name = 'COMM_PKG' AND TYPE = 'PACKAGE BODY'; |
■ Package Overloading
- 동일한 이름의 프로시저, 함수를 만들 수 있다. to_char(숫자), to_char(날짜)
- 형식매개변수의 개수, 순서, 데이터 유형, 형식매개변수의 모드가 다를 경우 동일한 이름의 서브프로그램을 생성할 수 있다.
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,5); FOR i IN 1..5 LOOP DBMS_OUTPUT.PUT_LINE(date_tab(i)); END LOOP; FOR i IN 1..5 LOOP DBMS_OUTPUT.PUT_LINE(num_tab(i)); END LOOP; END; / |
23/11/03 23/11/03 23/11/03 23/11/03 23/11/03 1 2 3 4 5 |
CURSOR 선언 후 FOR문 사용 시 (OPEN-FETCH-CLOSE 미사용)
NULL값 = 결과가 없다면 END IF 로 반복문을 종료한다.
↓
LOOP + OPEN - FETCH - CLOSE 사용하자
OPEN dept_cur; -- 메모리할당 후 행을 데이터를 포인트 지정
LOOP
FETCH dept_cur INTO v_rec; -- 데이터를 조작하기 위해 변수에 대입
IF v_rec.employee_id IS NULL THEN -- FETCH 후 데이터가 NULL >> EXCEPTION 이동
ELSIF dept_cur%NOTFOUND THEN -- FETCH 후 데이터가 끝나면 END IF 정상 종료 <> NULL과는 다르다
END LOOP;
CLOSE dept_cur;
# 동일 테이블에서 동시에 SELECT + DML 작업이 안된다.
함수 내 select가 업데이트 전인지 후인지 모르기 때문에 에러
# PACKAGE 패키지
CREATE OR REPLACE PACKAGE comm_pkg
IS
/* SPEC(PUBLIC) 필수 영역 */
글로벌 변수 선언
Overloading
END;
/
CREATE OR REPLACE PACKAGE BODY comm_pkg -- 명칭은 SPEC영역과 동일
IS
/* BODY(PRIVATE) 선택 영역 */
SPEC 부분의 소스
END;
/
'Oracle SQL' 카테고리의 다른 글
23 PL/SQL TRIGGER (0) | 2023.11.07 |
---|---|
22 PL/SQL PACKAGE, TRIGGER (1) | 2023.11.06 |
20 PL/SQL PROCEDURE, FUNCTION (0) | 2023.11.02 |
19 PL/SQL PROCEDURE (0) | 2023.11.01 |
18 PL/SQL EXCEPTION (1) | 2023.10.31 |