ํธ๋ฆฌ๊ฑฐ(Trigger)
ํน์ ์ด๋ฒคํธ(INSERT, UPDATE, DELETE)๊ฐ ๋ฐ์ํ์ ๋ ์๋์ผ๋ก ์คํ๋๋ PL/SQL ๋ธ๋ก
โข
์ฌ์ฉ์๊ฐ ์ง์ ์คํํ์ง ์์๋ ๋จ
โข
ํ
์ด๋ธ/๋ทฐ์์ ๋ฐ์ดํฐ ๋ณํ๊ฐ ์์ ๋ ์๋ ์คํ
ํธ๋ฆฌ๊ฑฐ ์ฌ์ฉ ์ฌ๋ก ์์
์ฌ๋ก 1: ์ฌ๊ณ ์๋ ์ ๋ฐ์ดํธ
์ฃผ๋ฌธ์ด ๋ฐ์ํ๋ฉด ์๋์ผ๋ก ์ฌ๊ณ ๋ฅผ ์ฐจ๊ฐํ๋ ํธ๋ฆฌ๊ฑฐ
CREATE OR REPLACE TRIGGER trg_update_stock
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
UPDATE products
SET stock = stock - :NEW.quantity
WHERE product_id = :NEW.product_id;
END;
/
SQL
๋ณต์ฌ
ํจ๊ณผ: ์ฃผ๋ฌธ ๋ฐ์ดํฐ ์
๋ ฅ ์ ์ฌ๊ณ ๊ด๋ฆฌ ํ
์ด๋ธ์ด ์๋ ๊ฐฑ์ ๋จ
์ฌ๋ก 2: ๊ธ์ฌ ๋ณ๊ฒฝ ์ด๋ ฅ ๊ด๋ฆฌ
์ง์์ ๊ธ์ฌ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ๋ณ๊ฒฝ ์ด๋ ฅ์ ์๋์ผ๋ก ๊ธฐ๋ก
CREATE OR REPLACE TRIGGER trg_salary_history
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
INSERT INTO salary_history (emp_id, old_salary, new_salary, change_date)
VALUES (:OLD.emp_id, :OLD.salary, :NEW.salary, SYSDATE);
END;
/
SQL
๋ณต์ฌ
ํจ๊ณผ: ๊ธ์ฌ ๋ณ๊ฒฝ ๋ด์ญ์ด ์๋์ผ๋ก ์ถ์ ๋์ด ๊ฐ์ฌ ๋ฐ ๋ถ์์ ํ์ฉ ๊ฐ๋ฅ
์ฌ๋ก 3: ๋ฐ์ดํฐ ์ ๋ ฅ ์ ์๋ ๊ฒ์ฆ
ํ์ ๊ฐ์
์ ์ด๋ฉ์ผ ํ์์ด ์๋ชป๋์์ผ๋ฉด ์๋ฌ ๋ฐ์
CREATE OR REPLACE TRIGGER trg_validate_email
BEFORE INSERT ON members
FOR EACH ROW
BEGIN
IF :NEW.email NOT LIKE '%@%.%' THEN
RAISE_APPLICATION_ERROR(-20001, '์ ํจํ์ง ์์ ์ด๋ฉ์ผ ํ์์
๋๋ค.');
END IF;
END;
/
SQL
๋ณต์ฌ
ํจ๊ณผ: ์๋ชป๋ ๋ฐ์ดํฐ๊ฐ ํ
์ด๋ธ์ ์ฝ์
๋๊ธฐ ์ ์ ์ฐจ๋จํ์ฌ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ์ ์ง
ํธ๋ฆฌ๊ฑฐ์ ํน์ง
ํน์ง | ์ค๋ช
|
์๋ ์คํ | ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด ์ค๋ผํด์ด ์๋์ผ๋ก ์ํ |
DML ๊ธฐ๋ฐ ์คํ | INSERT, UPDATE, DELETE ๋์ ์ ์๋ |
BEFORE / AFTER ๊ตฌ๋ถ | ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์ ๋๋ ํ ์คํ ์ค์ |
ํ(ROW)
๋๋ ๋ฌธ์ฅ(STATEMENT) ๋จ์ | ๊ฐ ํ๋ง๋ค ์คํ๋๊ฑฐ๋ SQL ๋ฌธ ํ๋๋น ํ ๋ฒ ์คํ ๊ฐ๋ฅ |
ํธ๋ฆฌ๊ฑฐ ์ข ๋ฅ
ํธ๋ฆฌ๊ฑฐ ์ข
๋ฅ | ์คํ ์์ | ์ค๋ช
|
BEFORE Trigger | DML ์คํ ์ | ๋ฐ์ดํฐ ๊ฒ์ฆ ๋ฐ ์์ ๊ฐ๋ฅ |
AFTER Trigger | DML ์คํ ํ | ๋ก๊ทธ ๊ธฐ๋ก ๋ฑ์ ์ฌ์ฉ |
ROW Trigger(FOR EACH ROW) | ๊ฐ ํ๋ง๋ค | DML๋ก ์ํฅ์ ๋ฐ๋ ๊ฐ ํ๋ง๋ค ์คํ |
STATEMENT Trigger | SQL ๋ฌธ๋น 1ํ | SQL ๋ฌธ ํ๋๋น ๋ฑ ํ ๋ฒ ์คํ |
INSTEAD OF Trigger | VIEW DML ์ | VIEW์์ DML์ ๊ฐ๋ฅํ๊ฒ ํ๊ธฐ ์ํ ํธ๋ฆฌ๊ฑฐ |
ํธ๋ฆฌ๊ฑฐ ๊ตฌ์กฐ (๊ธฐ๋ณธ ๋ฌธ๋ฒ)
CREATE OR REPLACE TRIGGER ํธ๋ฆฌ๊ฑฐ์ด๋ฆ
BEFORE | AFTER INSERT OR UPDATE OR DELETE
ON ํ
์ด๋ธ๋ช
[FOR EACH ROW]
BEGIN
-- ์คํํ PL/SQL ์ฝ๋
END;
/
SQL
๋ณต์ฌ
:OLD / :NEW ๊ฐ์ ๋ ์ฝ๋
๊ฐ์ ๋ ์ฝ๋ | ์ค๋ช
| ์ฌ์ฉ ๊ฐ๋ฅํ DML |
:OLD | ๋ณ๊ฒฝ ์ด์ ๊ฐ | UPDATE, DELETE |
:NEW | ๋ณ๊ฒฝ ์ดํ ๊ฐ | INSERT, UPDATE |
:OLD / :NEW ์ฌ์ฉ ์์
์์ 1: INSERT ์
CREATE OR REPLACE TRIGGER trg_insert_example
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
-- INSERT ์์๋ :NEW๋ง ์ฌ์ฉ ๊ฐ๋ฅ
DBMS_OUTPUT.PUT_LINE('์๋ก ์
๋ ฅ๋ ์ฌ์๋ฒํธ: ' || :NEW.emp_id);
DBMS_OUTPUT.PUT_LINE('์๋ก ์
๋ ฅ๋ ์ด๋ฆ: ' || :NEW.name);
DBMS_OUTPUT.PUT_LINE('์๋ก ์
๋ ฅ๋ ๊ธ์ฌ: ' || :NEW.salary);
-- :OLD๋ NULL (์กด์ฌํ์ง ์์)
END;
/
SQL
๋ณต์ฌ
์์ 2: UPDATE ์
CREATE OR REPLACE TRIGGER trg_update_example
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
-- UPDATE ์์๋ :OLD์ :NEW ๋ชจ๋ ์ฌ์ฉ ๊ฐ๋ฅ
DBMS_OUTPUT.PUT_LINE('์ฌ์๋ฒํธ: ' || :OLD.emp_id);
DBMS_OUTPUT.PUT_LINE('๋ณ๊ฒฝ ์ ์ด๋ฆ: ' || :OLD.name);
DBMS_OUTPUT.PUT_LINE('๋ณ๊ฒฝ ํ ์ด๋ฆ: ' || :NEW.name);
DBMS_OUTPUT.PUT_LINE('๋ณ๊ฒฝ ์ ๊ธ์ฌ: ' || :OLD.salary);
DBMS_OUTPUT.PUT_LINE('๋ณ๊ฒฝ ํ ๊ธ์ฌ: ' || :NEW.salary);
DBMS_OUTPUT.PUT_LINE('๊ธ์ฌ ์ฐจ์ด: ' || (:NEW.salary - :OLD.salary));
END;
/
SQL
๋ณต์ฌ
์์ 3: DELETE ์
CREATE OR REPLACE TRIGGER trg_delete_example
AFTER DELETE ON employees
FOR EACH ROW
BEGIN
-- DELETE ์์๋ :OLD๋ง ์ฌ์ฉ ๊ฐ๋ฅ
DBMS_OUTPUT.PUT_LINE('์ญ์ ๋ ์ฌ์๋ฒํธ: ' || :OLD.emp_id);
DBMS_OUTPUT.PUT_LINE('์ญ์ ๋ ์ด๋ฆ: ' || :OLD.name);
DBMS_OUTPUT.PUT_LINE('์ญ์ ๋ ๊ธ์ฌ: ' || :OLD.salary);
-- :NEW๋ NULL (์กด์ฌํ์ง ์์)
END;
/
SQL
๋ณต์ฌ
์์ 4: ๊ธ์ฌ ๋ณ๊ฒฝ ์ด๋ ฅ ์์ธ ๊ธฐ๋ก
CREATE OR REPLACE TRIGGER trg_salary_change_detail
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
-- ๊ธ์ฌ๊ฐ ์ค์ ๋ก ๋ณ๊ฒฝ๋์์ ๋๋ง ๊ธฐ๋ก
IF :OLD.salary != :NEW.salary THEN
INSERT INTO salary_history (
emp_id,
emp_name,
old_salary,
new_salary,
salary_diff,
change_rate,
change_date
) VALUES (
:OLD.emp_id,
:NEW.name, -- ๋ณ๊ฒฝ ํ ์ด๋ฆ
:OLD.salary, -- ๋ณ๊ฒฝ ์ ๊ธ์ฌ
:NEW.salary, -- ๋ณ๊ฒฝ ํ ๊ธ์ฌ
:NEW.salary - :OLD.salary, -- ๊ธ์ฌ ์ฐจ์ด
ROUND((:NEW.salary - :OLD.salary) / :OLD.salary * 100, 2), -- ๋ณ๊ฒฝ๋ฅ (%)
SYSDATE
);
END IF;
END;
/
SQL
๋ณต์ฌ
์์ 5: ๋ฐ์ดํฐ ๊ฒ์ฆ ๋ฐ ์์
CREATE OR REPLACE TRIGGER trg_validate_and_modify
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW
BEGIN
-- ๊ธ์ฌ๊ฐ ์์๋ฉด 0์ผ๋ก ๋ณ๊ฒฝ
IF :NEW.salary < 0 THEN
:NEW.salary := 0;
END IF;
-- ์ด๋ฆ์ด NULL์ด๋ฉด '๋ฏธ์
๋ ฅ'์ผ๋ก ์ค์
IF :NEW.name IS NULL THEN
:NEW.name := '๋ฏธ์
๋ ฅ';
END IF;
-- ์ด๋ฉ์ผ์ ์๋ฌธ์๋ก ์๋ ๋ณํ
:NEW.email := LOWER(:NEW.email);
-- ์์ ์ผ ์๋ ๊ฐฑ์
:NEW.modified_date := SYSDATE;
END;
/
SQL
๋ณต์ฌ
์์ 6: ์กฐ๊ฑด๋ถ ์คํ (ํน์ ์ปฌ๋ผ ๊ฐ์ ๋ฐ๋ผ)
CREATE OR REPLACE TRIGGER trg_high_salary_alert
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
-- ๊ธ์ฌ๊ฐ 10000 ์ด์์ผ๋ก ์ธ์๋ ๊ฒฝ์ฐ์๋ง ์๋ฆผ
IF :NEW.salary >= 10000 AND :OLD.salary < 10000 THEN
INSERT INTO high_salary_alerts (
emp_id,
emp_name,
new_salary,
alert_date
) VALUES (
:NEW.emp_id,
:NEW.name,
:NEW.salary,
SYSDATE
);
END IF;
END;
/
SQL
๋ณต์ฌ
ํต์ฌ ์ ๋ฆฌ
โข
INSERT: :NEW๋ง ์ฌ์ฉ ๊ฐ๋ฅ (์๋ก ์
๋ ฅ๋๋ ๊ฐ)
โข
UPDATE: :OLD์ :NEW ๋ชจ๋ ์ฌ์ฉ ๊ฐ๋ฅ (๋ณ๊ฒฝ ์ /ํ ๋น๊ต ๊ฐ๋ฅ)
โข
DELETE: :OLD๋ง ์ฌ์ฉ ๊ฐ๋ฅ (์ญ์ ๋๋ ๊ธฐ์กด ๊ฐ)
โข
BEFORE ํธ๋ฆฌ๊ฑฐ์์๋ :NEW ๊ฐ์ ์์ ํ ์ ์์
ํธ๋ฆฌ๊ฑฐ ์์
(1) INSERT ์ ์๋ ๋ก๊ทธ ๊ธฐ๋ก
CREATE OR REPLACE TRIGGER trg_emp_log
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
INSERT INTO emp_log (emp_id, action_date, action_type)
VALUES (:NEW.emp_id, SYSDATE, 'INSERT');
END;
/
SQL
๋ณต์ฌ
(2) UPDATE ์ ๊ฐ ๋ณ๊ฒฝ(๊ธ์ฌ๊ฐ 0 ๋ฏธ๋ง์ด๋ฉด 0์ผ๋ก)
CREATE OR REPLACE TRIGGER trg_check_salary
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
IF :NEW.salary < 0 THEN
:NEW.salary := 0;
END IF;
END;
/
SQL
๋ณต์ฌ
(3) DELETE ์ ์ญ์ ๋ก๊ทธ ๊ธฐ๋ก
CREATE OR REPLACE TRIGGER trg_delete_log
AFTER DELETE ON employees
FOR EACH ROW
BEGIN
INSERT INTO emp_delete_log (emp_id, del_date)
VALUES (:OLD.emp_id, SYSDATE);
END;
/
SQL
๋ณต์ฌ
ํธ๋ฆฌ๊ฑฐ ํ์ธ ๋ฐ ์ญ์
ํธ๋ฆฌ๊ฑฐ ์กฐํ
SELECT trigger_name, status FROM user_triggers;
SQL
๋ณต์ฌ
ํธ๋ฆฌ๊ฑฐ ์ญ์
DROP TRIGGER ํธ๋ฆฌ๊ฑฐ์ด๋ฆ;
SQL
๋ณต์ฌ
ํธ๋ฆฌ๊ฑฐ ์ฌ์ฉ ์ ์ฃผ์์ฌํญ
โข
๊ณผ๋ํ ๋ก์ง โ ์ฑ๋ฅ ์ ํ ์์ธ
โข
๋๋ฒ๊น
์ด๋ ค์ โ ์์ธ ์ฒ๋ฆฌ ํ์
โข
์ํ ํธ์ถ ๋ฐ์ ๊ฐ๋ฅ โ ๋์ผ ํ
์ด๋ธ UPDATE ์ ์ฃผ์
ํธ๋ฆฌ๊ฑฐ ์ฌ์ฉ ๋ชฉ์ ์ ๋ฆฌ
โข
๊ฐ์ฌ ๋ก๊ทธ(Audit)
โข
๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๊ฐํ
โข
์๋ ๊ณ์ฐ ์ฒ๋ฆฌ
โข
๋ทฐ์์ DML ์ง์(INSTEAD OF)


