복습)
# DML 문장 트리거
- DML 문장 트리거는 영향을 받는 행이 전혀 없더라도 문장 트리거는 한 번 실행된다.
- 특정 요일과 시간을 제한을 주고 싶을 땐 문장 트리거를 많이 쓴다.
# DML 행 트리거
- DML 행 트리거는 영향을 받는 행이 없을 경우에는 트리거가 수행되지 않는다.
- 영향을 받은 각 행에 대해서 트리거는 수행된다.
- FOR EACH ROW 절을 사용해야 한다.
- CHECK 제약조건으로 안 되는 CHECK 제약조건을 만들고 싶을 때 행 트리거로 많이 구현한다.
ex) DML 행 트리거
- 간단 테이블 생성
create table hr.emp
as
select employee_id id, salary sal, job_id job, department_id dept_id
from hr.employees;
- 'AD_PRES','AD_VP' job이 아닌데 급여가 15000 이상이 들어오면 에러 발생
create or replace trigger hr.restrict_salary
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 than 15,000');
end if;
end restrict_salary;
/
insert into hr.emp(id, sal, job, dept_id)
values(300, 1000, 'IT_PROG', 10);
▶ 문제 없이 insert 된다.
insert into hr.emp(id, sal, job, dept_id)
values(400, 20000, 'IT_PROG', 20);
▶ 트리거에서 제한한 job 외에 job이 15000이상의 급여를 insert 했기 때문에 에러가 발생한다.
update hr.emp
set sal = 16000
where id = 200;
▶ 200번 사원은 job이 AD_ASST이고 15000이상 받으면 에러를 발생하는 구문을 트리거에서 지정했기 때문에 에러가 발생한다.
update hr.emp
set sal = 30000
where id = 100;
▶ 100번 사원은 AD_PRES이다. 그렇기 때문에 15000 이상 급여를 수정해도 에러가 발생하지 않고 정상 수정이 된 것이다.
ex) source 테이블에 새로운 값이 들어올 때 target 테이블에도 같이 매핑이 될 수 있도록 하고 싶을 경우
- 복제 테이블 구성
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);
- 트리거 생성
- 트리거 안에서 커밋이나 롤백을 쓰면 안 된다. 영구히 저장이나 롤백을 하고 싶은 경우엔 insert 작업 다하고 그 후에 밖에서 커밋이나 롤백하면 된다.
create or replace trigger hr.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;
commit;
end emp_copy_trigger;
/
▶ 주의 : 트리거 안에 트랜잭션이 실행 중이다. 근데 구문 안에 커밋이나 롤백을 하면 컴파일 에러가 발생한다. 트리거 안에선 트랜잭션을 제어하는 롤백이나 커밋을 쓰면 안 된다. 사용하게 되면 insert 구문까지 영향을 주기 때문에 트리거는 생성되어도 DML 작업 시 에러가 발생
create or replace trigger hr.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 emp_copy_trigger;
/
insert into hr.emp_source(id, name, day, sal)
values(100, 'ORA1', default, 1000);
select * from hr.emp_source;
select * from hr.emp_target;
▶ 2개의 테이블에 동일한 데이터가 들어간 걸 볼 수 있다.
update hr.emp_source
set sal = 2000
where id = 100;
commit;
update hr.emp_source
set name = 'oracle'
where id = 100;
commit;
delete from hr.emp_source where id = 100;
commit;
# 값의 감사를 구현해야 할 때
- SYS SESSION
create user green
identified by oracle
default tablespace users
temporary tablespace temp
quota unlimited on users;
grant create session to green;
- HR SESSION
create table hr.audit_emp_sal
(name varchar2(30),
day timestamp,
id number,
old_sal number,
new_sal number);
create or replace procedure hr.update_proc(p_id in number)
is
begin
update hr.emp
set sal = sal * 1.1
where id = p_id;
end update_proc;
/
- 프로시저가 제대로 실행되는지 확인
select * from hr.emp where id = 200;
exec hr.update_proc(200)
select * from hr.emp where id = 200;
- green에게 프로시저에 대한 excute 할 수 있는 권한 주기
grant execute on hr.update_proc to green;
- 값에 대한 감사 Trigger 생성
create or replace trigger hr.emp_sal_audit
after
update of sal on hr.emp
for each row
begin
if :old.sal != :new.sal then
insert into hr.audit_emp_sal(name, day, id, old_sal, new_sal)
values(user, systimestamp, :new.id, :old.sal, :new.sal);
end if;
end emp_sal_audit;
/
▶ values 값에 user의 의미는 현재 접속한 유저로 받겠다는 의미이다.
▶ insert into 절에 id 값에 :new, :old 둘 중에 아무거나 써도 상관없다.
select * from hr.audit_emp_sal;
update hr.emp
set sal = sal * 1.1
where id = 200;
select * from hr.audit_emp_sal;
▶ 현재 접속한 유저인 HR이 들어가고, 현재 시간이 들어가고 이전 급여가 얼마였고, 수정된 급여는 뭔지 나오는 게 감사이다.
# 롤백하고 수행하면
- green으로 접속하여 권한 확인하기
select * from user_tab_privs;
- excute 해보기 (green 유저에서 sqlplus에서 함)
exec hr.update_proc(200)
commit;
- 조회해보기 (HR SESSION)
select * from hr.audit_emp_sal;
▶ 프로시저에 excute 권한만 green한테 주고 감사를 한 것이다. 이런 걸 간접 엑세스라고 한다.
■ INSTEAD OF TRIGGER
- VIEW에 생성하는 트리거
- 복합뷰는 DML 작업을 수행할 수 없다. 가능하도록 만들려면 INSTEAD OF TRIGGER를 생성하면 된다.
- 뷰에 트리거는 행트리거만 사용해야 한다.
ex)
- 샘플 테이블 2개 생성
create table hr.new_emps
as
select employee_id, salary, hire_date, department_id
from hr.employees;
create table hr.new_depts
as
select d.department_id, d.department_name, sum(e.salary) tot_dept_sal
from hr.employees e, hr.departments d
where e.department_id = d.department_id
group by d.department_id, d.department_name;
- 샘플 테이블을 이용한 VIEW 생성
create view hr.emp_details
as
select e.employee_id, e.salary, e.hire_date, e.department_id, d.department_name, d.tot_dept_sal
from hr.new_emps e, hr.new_depts d
where e.department_id = d.department_id;
▶ JOIN이나 group by절이 들어가면 복합뷰이다. 복합뷰에서는 DML 작업이 불가능하다.
- 복합뷰이지만 hr.emp_details 뷰에 신규 회원의 데이터를 넣고 싶을 경우
▶트리거로 구현하면 된다고 하자!
▶ emp 사원테이블에는 insert가 되면 되고, depts 부서 테이블에는 update가 되게 하면 된다.
- INSTEAD OF TRIGGER 트리거 생성
create or replace trigger hr.emp_dept
instead of -- 뷰에 트리거를 걸 경우엔 무조건 instead of 사용
insert or update or delete on hr.emp_details
for each row
begin
if inserting then
insert into hr.new_emps(employee_id, salary, hire_date, department_id)
values(:new.employee_id, :new.salary, :new.hire_date, :new.department_id);
update hr.new_depts
set tot_dept_sal = tot_dept_sal + :new.salary
where department_id = :new.department_id;
elsif deleting then
delete from hr.new_emps where employee_id = :old.employee_id;
update hr.new_depts
set tot_dept_sal = tot_dept_sal - :old.salary
where department_id = :old.department_id;
elsif updating('salary') then
update hr.new_emps
set salary = :new.salary
where employee_id = :old.employee_id;
update hr.new_depts
set tot_dept_sal = tot_dept_sal + (:new.salary - :old.salary)
where department_id = :old.department_id;
elsif updating('department_id') then
update hr.new_emps
set department_id = :new.department_id
where employee_id = :old.employee_id;
update hr.new_depts
set tot_dept_sal = tot_dept_sal - :old.salary
where department_id = :old.department_id;
update hr.new_depts
set tot_dept_sal = tot_dept_sal + :new.salary
where department_id = :new.department_id;
end if;
end emp_dept;
/
- 뷰를 통해 신규 사원을 넣고 데이터를 조회해보자
select * from hr.new_depts where department_id = 60;
insert into hr.emp_details(employee_id, salary, hire_date, department_id, department_name)
values(300, 1000, sysdate, 60, 'IT');
select * from hr.new_emps where employee_id = 300;
select * from hr.new_depts where department_id = 60;
# 사원의 급여를 수정
select * from hr.new_emps where employee_id = 300;
update hr.new_emps
set salary = 2000
where employee_id = 300;
commit;
select * from hr.new_depts where department_id = 60;
▶ 사원테이블에서 수정해도 부서테이블에 수정이 되지 않았다. 그 이유는 update 구문에서 뷰를 통해서 수정을 해야 하는데 사원테이블인 실제 테이블에서 했기 때문이다.
# 60번에서 70번으로 부서 이동할 경우
select * from hr.new_emps where employee_id = 300;
select * from hr.new_depts where department_id = 60;
select * from hr.new_depts where department_id = 70;
update hr.emp_details
set department_id = 70
where employee_id = 300;
commit;
select * from hr.new_depts where department_id = 70;
# 사원 삭제
delete from hr.emp_details where employee_id = 300;
commit;
# 트리거 생성 시 주의사항
- 트리거 작성 시에 트리거 오류 발생했으면 빨리 디버깅해야 된다. 안 할 시엔 내가 시점을 준(insert, update, delete) 작업 시 계속 에러 난다. 그 외 작업엔 문제가 되진 않는다.
create or replace trigger hr.dept_row_before
before
insert on hr.dept
for each row
begin
dbms_output.put_line('insert 하기 전 행 트리거 수행')
end dept_row_before;
/
▶ 빨리 디버깅을 하는지 트리거를 DROP 하던지 해야 한다.
select status from user_triggers where trigger_name = 'DEPT_ROW_BEFORE';
▶ 기본값이 ENABLED이다.
# 해결방법
alter trigger hr.dept_row_before disable;
select status from user_triggers where trigger_name = 'DEPT_ROW_BEFORE';
▶ 비활성화시킨 거다. (insert 작업도 가능하다.)
▶ DISABLED로 바꿔놓으면 insert작업은 문제없이 돌아간다.
# 처음부터 오류날 것을 대비해서 DISABLE 상태로 만들 수 있다.
create or replace trigger hr.dept_row_before
before
insert on hr.dept
for each row
disable
begin
dbms_output.put_line('insert 하기 전 행 트리거 수행')
end dept_row_before;
/
▶ 오류는 났지만 disable로 들어간 거다.
select status from user_triggers where trigger_name = 'DEPT_ROW_BEFORE';
# 오류 안 나고 disable 했을 경우
create or replace trigger hr.dept_row_before
before
insert on hr.dept
for each row
disable
begin
dbms_output.put_line('insert 하기 전 행 트리거 수행');
end dept_row_before;
/
select status from user_triggers where trigger_name = 'DEPT_ROW_BEFORE';
▶ 컴파일 오류가 안 나고 정상 출력이 되면 꼭 활성화 상태인 ENABLE 상태로 바꿔줘야 트리거가 작동이 된다.
# 다시 활성화시키고 싶을 경우
alter trigger hr.dept_row_before enable;
select status from user_triggers where trigger_name = 'DEPT_ROW_BEFORE';
▶ 다시 insert 작업하면 잘 수행이 된다.
■ local subprogram (nested subprogram)
ex)
declare
type emp_id_type is table of number;
v_id emp_id_type := emp_id_type(100, 101, 102);
v_emp hr.employees%rowtype;
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 : '||v_emp.salary * 0.8);
end loop;
end;
/
# 사원급여를 0.8 곱해서 리턴해주고 싶을 경우
- object처럼 함수를 쓰는 것이 아니라 내가 만든 프로그램 안에서만 수행하는 함수를 만들고 싶을 경우
declare
type emp_id_type is table of number;
v_id emp_id_type := emp_id_type(100, 101, 102);
v_emp hr.employees%rowtype;
function tax(p_sal in number) return number
is
begin
return p_sal * 0.8;
end tax;
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;
end;
/
▶ 선언 부분에 함수를 정의만 해주면 된다. (create or replace 빼고!)
# 프로시저도 정의할 경우
declare
type emp_id_type is table of number;
v_id emp_id_type := emp_id_type(100, 101, 102);
v_emp hr.employees%rowtype;
function tax(p_sal in number) return number
is
begin
return p_sal * 0.8;
end tax;
procedure message
is
begin
dbms_output.put_line('꿈을 이루자...!!!');
end message;
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;
/
'oracle PLSQL' 카테고리의 다른 글
2025.01.07 PL/SQL 13일차 (0) | 2025.01.07 |
---|---|
2025.01.03 PL/SQL 11일차 (2) | 2025.01.03 |
2025.01.02 PL/SQL 10일차 (3) | 2025.01.02 |
2024.12.31 PL/SQL 9일차 (0) | 2024.12.31 |
2024.12.30 PL/SQL 8일차 (0) | 2024.12.30 |