DROP TABLE hr.emp PURGE; /* 테이블 생성 */ CREATE TABLE hr.emp AS SELECT employee_id id, last_name name, salary sal, department_id dept_id FROM hr.employees; |
<< DML 문장 트리거 >>
영향을 받은 행이 전혀 없더라도 무조건 한번 수행되는 트리거 -- 있든 없든 무조건!!
/* 요일 형식 체크 */ select to_char(sysdate,'dy') from dual; CREATE OR REPLACE TRIGGER secure_emp /* 타이밍 필수 BEFORE or AFTER */ BEFORE /* 해당 테이블의 DML 작업을 하는 모든 SESSION에서 무조건 작동 */ INSERT OR UPDATE OR DELETE ON hr.emp BEGIN /* 특정 요일 또는 특정 시간대에 에러 발생 */ IF to_char(sysdate,'dy') IN ('월','화') OR to_char(sysdate,'hh24:mi') BETWEEN '09:30' AND '10:00' THEN RAISE_APPLICATION_ERROR(-20000,'작업 시간이 아닙니다.'); END IF; END; / /* 트리거 생성 확인 */ SELECT * FROM user_triggers; |
INSERT INTO hr.emp (id,name,sal,dept_id) VALUES (300,'ORACLE',1000,10); -- ORA-20000: 작업 시간이 아닙니다. DELETE FROM hr.emp WHERE dept_id = 20; -- ORA-20000: 작업 시간이 아닙니다. UPDATE hr.emp SET sal = sal * 1.1 WHERE id = 200; -- ORA-20000: 작업 시간이 아닙니다. |
CREATE OR REPLACE TRIGGER secure_emp /* 타이밍 필수 BEFORE or AFTER */ BEFORE /* 해당 테이블의 DML 작업을 하는 모든 SESSION에서 무조건 작동 */ INSERT OR UPDATE OR DELETE ON hr.emp BEGIN /* 특정 요일 또는 특정 시간대에 에러 발생 */ IF to_char(sysdate,'dy') IN ('월','화') OR to_char(sysdate,'hh24:mi') BETWEEN '09:30' AND '10:00' THEN /* 작업별 에러메세지 적용*/ IF INSERTING THEN RAISE_APPLICATION_ERROR(-20000,'INSERT 작업 시간이 아닙니다.'); ELSIF DELETING THEN RAISE_APPLICATION_ERROR(-20001,'DELETE 작업 시간이 아닙니다.'); /* UPDATE는 행 또는 특정 칼럼 지정 가능 */ ELSIF UPDATING('SAL') THEN RAISE_APPLICATION_ERROR(-20002,'급여수정 작업 시간이 아닙니다.'); ELSE RAISE_APPLICATION_ERROR(-20003,'수정 작업 시간이 아닙니다.'); END IF; END IF; END; / |
INSERT INTO hr.emp (id,name,sal,dept_id) VALUES (300,'ORACLE',1000,10); -- ORA-20000: INSERT 작업 시간이 아닙니다. DELETE FROM hr.emp WHERE dept_id = 20; -- ORA-20001: DELETE 작업 시간이 아닙니다. UPDATE hr.emp SET sal = sal * 1.1 WHERE id = 200; -- ORA-20002: 급여수정 작업 시간이 아닙니다. |
# RAISE_APPLICATION_ERROR()
강제 비정상 종료이기 때문에 ROLLBACK 발생
DROP TABLE hr.emp PURGE; -- 테이블 삭제와 함께 종속된 트리거도 같이 삭제 된다. /* 테이블 생성 */ CREATE TABLE hr.emp AS SELECT employee_id id, last_name name, salary sal, job_id job, department_id dept_id FROM hr.employees; |
<< DML 행 트리거 >>
- FOR EACH ROW 절
- 영향을 받은 각 행에 대해서 한번 실행된다. -- 각 행 마다!
- 영향을 받은 행이 없으면 트리거는 수행되지 않습니다.
CREATE OR REPLACE TRIGGER restrict_alary BEFORE INSERT OR UPDATE OF sal ON hr.emp FOR EACH ROW BEGIN IF :new.job NOT IN ('AD_PRES','AD_VP') AND :new.sal > 15000 THEN RAISE_APPLICATION_ERROR(-20000,'Employee cannot earn more then $15,000'); END IF; END; / |
INSERT INTO hr.emp(id,name,sal,job,dept_id) VALUES (300,'ORACLE',1000,'IT_PROG',10); -- 조건에 FALSE >> 저장 INSERT INTO hr.emp(id,name,sal,job,dept_id) VALUES (400,'HADOOP',20000,'IT_PROG',20); -- ORA-20000: Employee cannot earn more then $15,000 UPDATE hr.emp SET sal = 30000 WHERE id = 200; -- ORA-20000: Employee cannot earn more then $15,000 -- job : AD_ASST UPDATE hr.emp SET name = 'james' WHERE id = 200; -- 저장, 트리거 조건에 미해당 |
BEGIN INSERT INTO hr.emp(id,name,sal,job,dept_id) VALUES (300,'ORACLE',1000,'IT_PROG',10); INSERT INTO hr.emp(id,name,sal,job,dept_id) VALUES (400,'HADOOP',20000,'IT_PROG',20); END; / -- ORA-20000: Employee cannot earn more then $15,000 -->> 300성공 -> 400에러 -> ROLLACK : 프로그램 내에선 하나로 보아 에러 시 비정상 종료 |
# 행트리거내에서 old, new 수식자를 사용해서 데이터 변경 이전과 이후 값을 참조할 수 있다.
- BEGIN 절 안에서 old, new 수식자 앞에 콜론(:) 접두어로 붙여야 한다.
- WHEN 절에서는 old, new 수식자 앞에 콜론(:) 접두어를 붙이면 안된다.
작업 | OLD | NEW |
INSERT | NULL | 새로운 입력 값 |
UPDATE | 갱신전값 | 갱신후값 |
DELETE | 삭제전값 | NULL |
WHEN 절 | old. | new. |
BEGIN 절 | :old. | :new. |
/* 백업 테이블 생성*/ CREATE TABLE hr.emp_target (id number, name varchar2(30), day timestamp default systimestamp, sal number); /* 메인 테이블 생성 */ CREATE TABLE hr.emp_source (id number, name varchar2(30), day timestamp default systimestamp, sal number); |
/* emp_source테이블의 작업 시 emp_target테이블에 백업 */ CREATE OR REPLACE TRIGGER emp_copy_trigger AFTER INSERT OR DELETE OR UPDATE ON hr.emp_source FOR EACH ROW BEGIN IF INSERTING THEN INSERT INTO hr.emp_target(id,name,day,sal) VALUES (:new.id,:new.name,:new.day,:new.sal); ELSIF DELETING THEN DELETE FROM hr.emp_target WHERE id = :old.id; ELSIF UPDATING('sal') THEN UPDATE hr.emp_target SET sal = :new.sal WHERE id = :old.id; ELSIF UPDATING('name') THEN UPDATE hr.emp_target SET name = :new.name WHERE id = :old.id; /* 모든 작업에 테이블을 똑같이 만들고 싶다면 각 칼럼 작업별 다 만들어야 한다. */ END IF; END; / |
INSERT INTO hr.emp_source(id,name,day,sal) VALUES (100,'oracle',default,1000); UPDATE hr.emp_source SET sal = 2000 WHERE id = 100; UPDATE hr.emp_source SET name = 'james' WHERE id = 100; DELETE FROM hr.emp_source WHERE id = 100; SELECT * FROM hr.emp_source; SELECT * FROM hr.emp_target; |
< sys session > /* 유저 생성 */ CREATE USER green IDENTIFIED BY oracle; GRANT CREATE SESSION TO green; |
/* 테이블 생성 audit_emp_sal */ CREATE TABLE audit_emp_sal( name varchar2(30), day timestamp, id number, old_sal number, new_sal number); /* 프로시저 생성 update_proc */ CREATE OR REPLACE PROCEDURE update_proc(p_id IN number) IS BEGIN UPDATE hr.employees SET salary = salary * 1.1 WHERE employee_id = p_id; END update_proc; / /* test */ SELECT salary FROM hr.employees WHERE employee_id = 100; execute update_proc(100) SELECT salary FROM hr.employees WHERE employee_id = 100; rollback; |
/* 유저 권한부여 */ GRANT EXECUTE ON hr.update_proc TO green; SELECT * FROM user_tab_privs; |
/* 트리거 생성 : 접속한 유저가 INSERT 작업 시 audit_emp_sal 테이블에 정보 기입 */ CREATE OR REPLACE TRIGGER sal_audit AFTER UPDATE OF salary ON hr.employees FOR EACH ROW WHEN (new.salary != old.salary) BEGIN INSERT INTO hr.audit_emp_sal (name, day, id, old_sal, new_sal) /* user : 현재 접속한 유저 */ VALUES (user, systimestamp, :old.employee_id, :old.salary, :new.salary); END; / || 또는 CREATE OR REPLACE TRIGGER sal_audit after UPDATE OF salary ON hr.employees FOR EACH ROW BEGIN /* WHEN 대신 IF 사용 */ IF new.salary != old.salary THEN INSERT INTO hr.audit_emp_sal (name,day,id,old_sal,new_sal) VALUES (user,systimestamp,:old.employee_id,:old.salary,:new.salary); END IF; END; / |
<green session> /* 권한 확인 */ SELECT * FROM user_tab_privs; execute hr.update_proc(100) --> green 유저가 작업 -> 트리거 작동 |
SELECT * FROM hr.audit_emp_sal; --> hr 입장에서는 안보인다. |
<green session> COMMIT; --> 영구저장 후 테이블에 반영되어 audit_emp_sal 테이블에서 확인 됨 |
<green session> execute hr.update_proc(101) -- green 유저가 트랜젝션 시작 |
UPDATE hr.employees SET salary = salary * 1.2 WHERE employee_id = 101; --> hr 입장에선 작업대기 중 |
<green session> rollback; -->> green 유저가 트랜젝션을 종료하여 hr 작업물 진행 |
SELECT * FROM hr.audit_emp_sal; --> hr 조회 됨. 트랜잭션 진행 중 rollback; -- 트랜젝션 종료 |
# 여러 사용자가 있을 경우 같은 테이블의 행에 대해 트랜젝션 진행 중이면 동시 작업 불가
# 작업 진행 후 트랜젝션까지 종료 가능하게 셋팅해야한다.
/* 트리거 확인 및 삭제*/
SELECT * FROM user_triggers;
DROP TRIGGER sal_audit;
/* 프로시저 확인 및 삭제 */
SELECT * FROM user_source WHERE name = 'UPDATE_PROC';
DROP PROCEDURE update_proc;
/* 오브젝트 정보 */
SELECT * FROM USER_OBJECTS;
[문제] 사원들의 신규 데이터 또는 급여를 수정할 때 그 사원의 job_id별 최저 임금에서 최고 임금 사이에 급여값으로 입력, 수정 되어야 합니다. |
SELECT j.job_id, j.min_salary, j.max_salary FROM hr.employees e, hr.jobs j WHERE e.job_id = j.job_id AND e.employee_id = 115; CREATE OR REPLACE TRIGGER hr.check_salary_trg BEFORE INSERT OR UPDATE ON hr.employees FOR EACH ROW -- 행단위에서 new, old 사용 가능 DECLARE min_sal number; max_sal number; BEGIN SELECT min_salary, max_salary INTO min_sal, max_sal FROM hr.jobs WHERE job_id = :new.job_id; -- new: INSERT OR UPDATE IF :new.salary NOT BETWEEN min_sal AND max_sal THEN RAISE_APPLICATION_ERROR(-20100,'Invalid salary '||trim(to_char(:new.salary,'$999999'))||'. Salaries for job '|| :new.job_id||' must be between '||trim(to_char(min_sal,'$999999'))||' and '|| trim(to_char(max_sal,'$999999'))); END IF; END; / |
/* 호출 */ UPDATE hr.employees SET salary = 5000 WHERE employee_id = 115; -- ORA-20100: Invalid salary $6000. Salaries for job PU_CLERK must be between $2500 and $5500 INSERT INTO hr.employees(employee_id,last_name,email,hire_date,job_id,salary) VALUES (300,'oracle','aa@aa.com',sysdate,'IT_PROG',20000); -- ORA-20100: Invalid salary $20000. Salaries for job IT_PROG must be between $4000 and $10000 |
# INSERT 작업 시 NOT NULL 정보 체크
desc hr.employees
/* 테이블 생성 hr.new_emp */ CREATE TABLE hr.new_emp AS SELECT employee_id, last_name, salary, department_id, email, job_id, hire_date FROM hr.employees; /* 테이블 생성 hr.new_dept */ CREATE TABLE hr.new_dept AS SELECT d.department_id, d.department_name, d.location_id, e.tot_dept_sal FROM (SELECT department_id, sum(salary) tot_dept_sal FROM hr.employees GROUP BY department_id) e, hr.departments d WHERE e.department_id = d.department_id; SELECT * FROM hr.new_emp; SELECT * FROM hr.new_dept; |
/* 복합 뷰 생성 emp_details */ CREATE OR REPLACE VIEW emp_details AS SELECT e.employee_id, e.last_name, e.salary, e.department_id, e.email, e.job_id, e.hire_date, d.department_name, d.location_id, d.tot_dept_sal FROM hr.new_emp e, hr.new_dept d WHERE e.department_id = d.department_id; SELECT * FROM emp_details; |
# 복합뷰는 DML 작업을 수행할 수 없다.
# 복합뷰에 대해서 DML 작업을 수행할 수 있도록 하려면 TRIGGER 를 만들어서 해결할 수 있다.
CREATE OR REPLACE TRIGGER emp_dept_trigger /* 뷰에서는 INSTEAD OF 전용. BEFORE, AFTER 사용 불가*/ INSTEAD OF INSERT OR UPDATE OR DELETE ON hr.emp_details /* 뷰에서는 무조건 행 트리거 사용 */ FOR EACH ROW BEGIN IF INSERTING THEN /* new_emp 테이블에 사원 정보 추가 */ INSERT INTO hr.new_emp(employee_id,last_name,salary,department_id,email,job_id,hire_date) VALUES (:new.employee_id,:new.last_name,:new.salary,:new.department_id,:new.email,:new.job_id,:new.hire_date); /* new_dept 테이블에 부서별 급여 업데이트 */ UPDATE hr.new_dept SET tot_dept_sal = tot_dept_sal + :new.salary WHERE department_id = :new.department_id; ELSIF DELETING THEN /* new_emp 테이블에 사원 정보 삭제 */ DELETE FROM hr.new_emp WHERE employee_id = :old.employee_id; /* new_dept 테이블에 부서별 급여 업데이트 */ UPDATE hr.new_dept SET tot_dept_sal = tot_dept_sal - :old.salary WHERE department_id = :old.department_id; ELSIF UPDATING('salary') THEN /* new_emp 테이블의 salary 업데이트 */ UPDATE hr.new_emp SET salary = :new.salary * 1.1 WHERE employee_id = :new.employee_id; /* new_dept 테이블의 부서별 급여 업데이트 */ UPDATE hr.new_dept SET tot_dept_sal = tot_dept_sal + (:new.salary - :old.salary) -- 차액 계산 WHERE department_id = :new.department_id; ELSIF UPDATING('department_id') THEN /* new_emp 테이블의 해당 사원에 대한 부서번호 업데이트 */ UPDATE hr.new_emp SET department_id = :new.department_id WHERE employee_id = :new.employee_id; /* new_dept 테이블의 변경전 부서에 대한 부서별급여 업데이트 */ UPDATE hr.new_dept SET tot_dept_sal = tot_dept_sal - :old.salary WHERE department_id = :old.department_id; /* new_dept 테이블의 변경후 부서에 대한 부서별급여 업데이트 */ UPDATE hr.new_dept SET tot_dept_sal = tot_dept_sal + :new.salary WHERE department_id = :new.department_id; END IF; END; / |
INSERT INTO hr.emp_details(employee_id,last_name,salary,department_id,email,job_id,hire_date) VALUES (300,'oracle',1000,10,'oracle','IT_PROG',sysdate); DELETE FROM hr.emp_details WHERE employee_id = 300; UPDATE hr.emp_details SET salary = salary * 1.1 WHERE employee_id = 101; UPDATE hr.emp_details SET department_id = 20 WHERE employee_id = 101; |
- 문장트리거 : 영향이 있든 없든 무조건 실행
- 행트리거 : 영향을 받은 행에 대해 실행
FOR EACH ROW -- 필수
WHEN ( 조건 ) -- 옵션
# RAISE_APPLICATION_ERROR(-20000, 메세지)
강제 비정상 종료이기 때문에 ROLLBACK 발생
작업 | OLD | NEW |
INSERT | NULL | 새로운 입력 값 |
UPDATE | 갱신전값 | 갱신후값 |
DELETE | 삭제전값 | NULL |
WHEN 절 | old. | new. |
BEGIN 절 | :old. | :new. |
# 여러 사용자가 있을 경우 같은 테이블의 행에 대해 트랜젝션 진행 중이면 동시 작업 불가
# 작업 진행 후 트랜젝션까지 종료 가능하게 셋팅해야한다.
/* 오브젝트 정보 */
SELECT * FROM USER_OBJECTS;
# INSERT 작업 시 칼럼에 대한 NOT NULL 정보 체크
DESC 소유자.테이블명
# 복합뷰는 DML 작업을 수행할 수 없다.
# 복합뷰에 대해서 DML 작업을 수행할 수 있도록 하려면 TRIGGER 를 만들어서 해결할 수 있다.
CREATE OR REPLACE TRIGGER 트리거명
INSTEAD OF -- INSTEAD OF : VIEW 전용. BEFORE, AFTER 사용 불가
INSERT OR UPDATE OR DELETE ON 테이블명
FOR EACH ROW -- VIEW 에서는 무조건 행 트리거 사용
WHEN () -- 행트리거 사용 시 옵션
DECLARE -- 옵션
BEGIN
END;
/
- USER : 작업중인 session의 작업자명
SELECT user
FROM dual;
'Oracle SQL' 카테고리의 다른 글
24 PL/SQL Autonomous Transaction (0) | 2023.11.08 |
---|---|
22 PL/SQL PACKAGE, TRIGGER (1) | 2023.11.06 |
21 PL/SQL PACKAGE (1) | 2023.11.03 |
20 PL/SQL PROCEDURE, FUNCTION (0) | 2023.11.02 |
19 PL/SQL PROCEDURE (0) | 2023.11.01 |