251118 TIL
2025. 11. 18. 17:31ㆍCourses/아이티윌 오라클 DBA 과정
문제
직무별 급여 유효성 검사 트리거
테이블 초기화
-- 테이블 초기화
DROP TABLE hr.emp PURGE;
CREATE TABLE hr.emp
AS
SELECT employee_id, salary, job_id
FROM hr.employees;
익명 블록
-- 익명 블록
DECLARE
v_min number;
v_max number;
BEGIN
SELECT min_salary min, max_salary max
INTO v_min, v_max
FROM hr.jobs
WHERE job_id = :b_job;
IF :b_sal NOT BETWEEN v_min AND v_max THEN
RAISE_APPLICATION_ERROR(-20000, '급여는 ' || v_min || ' ~ ' || v_max || ' 사이여야 합니다.');
END IF;
END;
/
-- ORA-20000: 급여는 4000 ~ 10000 사이여야 합니다.
프로시저
-- 프로시저
CREATE OR REPLACE PROCEDURE check_sal_proc(p_job IN varchar2, p_sal IN number)
IS
v_min number;
v_max number;
BEGIN
SELECT min_salary min, max_salary max
INTO v_min, v_max
FROM hr.jobs
WHERE job_id = p_job;
IF p_sal NOT BETWEEN v_min AND v_max THEN
RAISE_APPLICATION_ERROR(-20000, '급여는 ' || v_min || ' ~ ' || v_max || ' 사이여야 합니다.');
END IF;
END check_sal_proc;
/
exec check_sal_proc('IT_PROG', 3000)
-- ORA-20000: 급여는 4000 ~ 10000 사이여야 합니다.
트리거
-- 트리거
CREATE OR REPLACE TRIGGER check_sal_trg
BEFORE
INSERT OR UPDATE OF salary, job_id ON hr.emp
FOR EACH ROW
DECLARE
v_min number;
v_max number;
BEGIN
SELECT min_salary min, max_salary max
INTO v_min, v_max
FROM hr.jobs
WHERE job_id = :new.job_id;
IF :new.salary NOT BETWEEN v_min AND v_max THEN
RAISE_APPLICATION_ERROR(-20000, '급여는 ' || v_min || ' ~ ' || v_max || ' 사이여야 합니다.');
END IF;
END check_sal_trg;
/
INSERT INTO hr.emp(employee_id, salary, job_id) VALUES(300, 3000, 'IT_PROG');
-- ORA-20000: 급여는 4000 ~ 10000 사이여야 합니다.
UPDATE hr.emp
SET salary = 20000
WHERE employee_id = 103; -- ORA-20000: 급여는 4000 ~ 10000 사이여야 합니다.
UPDATE hr.emp
SET job_id = 'AD_ASST'
WHERE employee_id = 103; -- ORA-20000: 급여는 3000 ~ 6000 사이여야 합니다.
트리거에서 프로시저 호출
-- 트리거에서 프로시저 호출
CREATE OR REPLACE TRIGGER check_sal_trg
BEFORE
INSERT OR UPDATE OF salary, job_id ON hr.emp
FOR EACH ROW
BEGIN
check_sal_proc(:new.job_id, :new.salary);
END check_sal_trg;
/
INSERT INTO hr.emp(employee_id, salary, job_id) VALUES(300, 3000, 'IT_PROG');
-- ORA-20000: 급여는 4000 ~ 10000 사이여야 합니다.
UPDATE hr.emp
SET salary = 20000
WHERE employee_id = 103; -- ORA-20000: 급여는 4000 ~ 10000 사이여야 합니다.
UPDATE hr.emp
SET job_id = 'AD_ASST'
WHERE employee_id = 103; -- ORA-20000: 급여는 3000 ~ 6000 사이여야 합니다.
트리거에서 CALL문을 이용해서 프로시저 호출
-- call 문 : 호출문
call hr.check_sal_proc('IT_PROG', 3000);
-- 트리거에서 CALL문을 이용해서 프로시저 호출
CREATE OR REPLACE TRIGGER check_sal_trg
BEFORE
INSERT OR UPDATE OF salary, job_id ON hr.emp
FOR EACH ROW
CALL check_sal_proc(:new.job_id, :new.salary)
/
INSERT INTO hr.emp(employee_id, salary, job_id) VALUES(300, 3000, 'IT_PROG');
-- ORA-20000: 급여는 4000 ~ 10000 사이여야 합니다.
UPDATE hr.emp
SET salary = 20000
WHERE employee_id = 103; -- ORA-20000: 급여는 4000 ~ 10000 사이여야 합니다.
UPDATE hr.emp
SET job_id = 'AD_ASST'
WHERE employee_id = 103; -- ORA-20000: 급여는 3000 ~ 6000 사이여야 합니다.
TRIGGER(Cont.)
트리거 주의점
- 같은 테이블에 대해서 동시에 SELECT, DML 불가
ORA-04091: table HR.EMP is mutating, trigger/function may not see it오류 발생
CREATE OR REPLACE TRIGGER check_sal_trg
AFTER
INSERT OR UPDATE OF salary, job_id ON hr.emp
FOR EACH ROW
DECLARE
v_min number;
v_max number;
BEGIN
SELECT min(salary), max(salary)
INTO v_min, v_max
FROM hr.emp
WHERE job_id = :new.job_id;
IF :new.salary NOT BETWEEN v_min AND v_max THEN
RAISE_APPLICATION_ERROR(-20000, '급여는 ' || v_min || ' ~ ' || v_max || ' 사이여야 합니다.');
END IF;
END check_sal_trg;
/
SELECT * FROM user_triggers;
INSERT INTO hr.emp(employee_id, salary, job_id) VALUES(300, 5000, 'IT_PROG');
-- ORA-04091: table HR.EMP is mutating, trigger/function may not see it
UPDATE hr.emp
SET salary = 20000
WHERE employee_id = 103;
-- ORA-04091: table HR.EMP is mutating, trigger/function may not see it
UPDATE hr.emp
SET job_id = 'AD_ASST'
WHERE employee_id = 103;
-- ORA-04091: table HR.EMP is mutating, trigger/function may not see it
복합 트리거(COMPOUND TRIGGER)
- 4가지 트리거를 하나로 결합한 트리거
- BEFORE STATEMENT, BEFORE EACH ROW, AFTER EACH ROW, AFTER STATEMENT
직무별 직원 급여 범위 테이블 구하기
DECLARE
CURSOR cur IS
SELECT job_id, min(salary) min_sal, max(salary) max_sal
FROM hr.emp
GROUP BY job_id;
TYPE rec_type IS RECORD (
min_sal hr.emp.salary%type,
max_sal hr.emp.salary%type
);
TYPE tab_type IS TABLE OF rec_type INDEX BY varchar2(30);
v_tab tab_type;
v_job varchar2(30);
BEGIN
FOR i IN cur LOOP
v_tab(i.job_id).min_sal := i.min_sal;
v_tab(i.job_id).max_sal := i.max_sal;
END LOOP;
v_job := v_tab.first;
LOOP
dbms_output.put_line(v_job || ' ' || v_tab(v_job).min_sal || ' ' || v_tab(v_job).max_sal);
v_job := v_tab.next(v_job);
EXIT WHEN v_job is null;
END LOOP;
END;
/
복합 트리거 생성
CREATE OR REPLACE TRIGGER check_sal_trg
FOR INSERT OR UPDATE OF salary, job_id ON hr.emp
COMPOUND TRIGGER
TYPE rec_type IS RECORD (
min_sal hr.emp.salary%type,
max_sal hr.emp.salary%type
);
TYPE tab_type IS TABLE OF rec_type INDEX BY varchar2(30);
v_tab tab_type;
BEFORE STATEMENT IS
CURSOR cur IS
SELECT job_id, min(salary) min_sal, max(salary) max_sal
FROM hr.emp
GROUP BY job_id;
BEGIN
FOR i IN cur LOOP
v_tab(i.job_id).min_sal := i.min_sal;
v_tab(i.job_id).max_sal := i.max_sal;
END LOOP;
END BEFORE STATEMENT;
AFTER EACH ROW IS
BEGIN
IF :new.salary NOT BETWEEN v_tab(:new.job_id).min_sal AND v_tab(:new.job_id).max_sal THEN
RAISE_APPLICATION_ERROR(-20000, '급여는 ' || v_tab(:new.job_id).min_sal || ' ~ ' || v_tab(:new.job_id).max_sal || ' 사이여야 합니다.');
END IF;
END AFTER EACH ROW;
END check_sal_trg;
/
복합 트리거 없이 구현하기
- 패키지 글로벌 변수 사용
CREATE OR REPLACE PACKAGE check_sal_pkg
IS
TYPE rec_type IS RECORD (
min_sal hr.emp.salary%type,
max_sal hr.emp.salary%type
);
TYPE tab_type IS TABLE OF rec_type INDEX BY varchar2(30);
v_tab tab_type;
END check_sal_pkg;
/
CREATE OR REPLACE TRIGGER check_sal_before_trg
BEFORE
INSERT OR UPDATE OF salary, job_id ON hr.emp
DECLARE
CURSOR cur IS
SELECT job_id, min(salary) min_sal, max(salary) max_sal
FROM hr.emp
GROUP BY job_id;
BEGIN
FOR i IN cur LOOP
check_sal_pkg.v_tab(i.job_id).min_sal := i.min_sal;
check_sal_pkg.v_tab(i.job_id).max_sal := i.max_sal;
END LOOP;
END check_sal_before_trg;
/
CREATE OR REPLACE TRIGGER check_sal_after_row_trg
AFTER
INSERT OR UPDATE OF salary, job_id ON hr.emp
FOR EACH ROW
BEGIN
IF :new.salary NOT BETWEEN check_sal_pkg.v_tab(:new.job_id).min_sal AND check_sal_pkg.v_tab(:new.job_id).max_sal THEN
RAISE_APPLICATION_ERROR(-20000, '급여는 ' || check_sal_pkg.v_tab(:new.job_id).min_sal || ' ~ ' || check_sal_pkg.v_tab(:new.job_id).max_sal || ' 사이여야 합니다.');
END IF;
END check_sal_after_row_trg;
/
실행 결과
- mutating 오류 발생 X
INSERT INTO hr.emp(employee_id, salary, job_id) VALUES(300, 3000, 'IT_PROG');
-- ORA-20000: 급여는 4200 ~ 9000 사이여야 합니다.
UPDATE hr.emp
SET salary = 20000
WHERE employee_id = 103; -- ORA-20000: 급여는 4200 ~ 9000 사이여야 합니다.
UPDATE hr.emp
SET job_id = 'AD_ASST'
WHERE employee_id = 103; -- ORA-20000: 급여는 4400 ~ 4400 사이여야 합니다.
독립 트랜잭션(Autonomous Transaction) 처리
테이블 생성
CREATE TABLE hr.log_table(
username varchar2(30),
date_time timestamp,
message varchar2(100)
)TABLESPACE users;
CREATE TABLE hr.temp_table(n number) TABLESPACE users;
독립 트랜잭션 처리 X
CREATE OR REPLACE PROCEDURE hr.log_message(p_message IN varchar2)
IS
BEGIN
INSERT INTO hr.log_table(username, date_time, message)
VALUES(user, localtimestamp, p_message);
COMMIT;
END log_message;
/
BEGIN
hr.log_message('열심히 복습하자!!');
INSERT INTO hr.temp_table(n) VALUES(100);
hr.log_message('열심히 공부해서 꼭 취업하자!!');
ROLLBACK;
END;
/
- log_message 프로시저에 의해
INSERT INTO hr.temp_table(n) VALUES(100);도 커밋됨
PRAGMA AUTONOMOUS_TRANSACTION
CREATE OR REPLACE PROCEDURE hr.log_message(p_message IN varchar2)
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO hr.log_table(username, date_time, message)
VALUES(user, localtimestamp, p_message);
COMMIT;
END log_message;
/
BEGIN
hr.log_message('열심히 복습하자!!');
INSERT INTO hr.temp_table(n) VALUES(100);
hr.log_message('열심히 공부해서 꼭 취업하자!!');
ROLLBACK;
END;
/
- log_message 프로시저 안에서 발생한 트랜잭션만 커밋됨
INSERT INTO hr.temp_table(n) VALUES(100);는 롤백됨
CREATE TABLE hr.log_tab(id number, name varchar2(30), log_day timestamp default systimestamp);
CREATE TABLE hr.test(id number, name varchar2(30), log_day timestamp default systimestamp);
CREATE OR REPLACE TRIGGER hr.log_trigger
AFTER
INSERT ON hr.test
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO hr.log_tab(id, name, log_day) VALUES(:new.id, :new.name, :new.log_day);
COMMIT;
END log_trigger;
/
INSERT INTO hr.test(id, name) VALUES (100, 'ORACLE');
SELECT * FROM hr.log_tab;
SELECT * FROM hr.test;
ROLLBACK;
SELECT * FROM hr.log_tab; -- 로그가 남아있음
SELECT * FROM hr.test; -- 롤백됨
- test 테이블에 insert가 발생하면 log_trigger 트리거에 의해 log_tab에 insert 작업이 복제되고, 커밋됨
- insert 작업을 롤백하더라도 log_tab은 이미 커밋되었기 때문에 로그 기록이 남아 있음
'Courses > 아이티윌 오라클 DBA 과정' 카테고리의 다른 글
| 251120 TIL (0) | 2025.11.20 |
|---|---|
| 251119 TIL (0) | 2025.11.19 |
| 251117 TIL (0) | 2025.11.17 |
| 251114 TIL (0) | 2025.11.14 |
| 251113 TIL (0) | 2025.11.13 |