PostgreSQL에서 트리거는 세 가지 주요 카테고리로 나뉘어:
BEFORE— 메인 작업(예:INSERT,UPDATE또는DELETE) 전에 실행돼. 이걸로 작업을 막거나, 저장 전에 데이터를 수정할 수 있어.AFTER— 메인 작업이 끝난 후에 실행돼. 주로 로그 남기기, 관련 레코드 만들기, 작업이 성공적으로 끝난 뒤에 뭔가 해야 할 때 써.INSTEAD OF— 실제 작업 대신 실행돼. 이건 오직 뷰(view)에서만 쓸 수 있어. 예를 들어, 사용자가 뷰에 데이터를 넣으려고 하면,INSTEAD OF트리거로 그 과정을 직접 컨트롤할 수 있지.
BEFORE 트리거
BEFORE 트리거는 PostgreSQL이 메인 작업을 실행하기 전에 동작해. 저장 직전에 데이터를 체크하거나 바꾸고 싶을 때 유용하지. 비행기 타기 전에 짐 검사하는 거랑 비슷하다고 생각하면 돼: 짐이 안 맞으면 바꾸거나 아예 막을 수 있지.
데이터를 삽입하기 전에 검증하는 예시를 들어볼게. 우리에겐 students라는 테이블이 있고, 학생 정보를 저장해. 학생 나이가 100살을 넘지 않게 하고 싶어(물론 거의 없겠지만 ㅎㅎ).
테이블을 만들어보자:
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
age INT NOT NULL
);
트리거용 함수를 만들어보자:
CREATE OR REPLACE FUNCTION validate_age()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.age > 100 THEN
RAISE EXCEPTION '학생 나이는 100살을 넘을 수 없어!';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
트리거를 만들어보자:
CREATE TRIGGER before_insert_students
BEFORE INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION validate_age();
이제 학생 나이가 100살 넘는 값을 넣으려고 하면 PostgreSQL이 에러를 뱉어:
INSERT INTO students (name, age) VALUES ('이반 이바노프', 120);
-- 에러: 학생 나이는 100살을 넘을 수 없어!
이렇게 체크하는 거야!
AFTER 트리거
AFTER 트리거는 메인 작업이 성공적으로 끝난 후에 실행돼. 작업 결과에 따라 뭔가 해야 할 때 좋아. 예를 들어 로그 남기거나, 관련 레코드 만들 때.
시나리오: students 테이블이 있고, 모든 변경사항을 별도의 로그 테이블에 기록하고 싶어.
로그 테이블을 만들어보자:
CREATE TABLE students_log (
id SERIAL PRIMARY KEY,
student_id INT,
operation TEXT,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
트리거용 함수를 만들어보자:
CREATE OR REPLACE FUNCTION log_student_changes()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO students_log (student_id, operation)
VALUES (NEW.id, TG_OP); -- TG_OP에는 작업 타입: INSERT, UPDATE, DELETE가 들어감
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
트리거를 만들어보자:
CREATE TRIGGER after_insert_students
AFTER INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION log_student_changes();
이제 새 학생을 추가하면 PostgreSQL이 자동으로 로그에 기록해:
INSERT INTO students (name, age) VALUES ('안나 링', 22);
SELECT * FROM students_log;
-- 결과:
-- id | student_id | operation | timestamp
-- 1 | 1 | INSERT | 2023-11-15 12:00:00
INSTEAD OF 트리거
INSTEAD OF 트리거는 작업 대신 실행돼. 이건 뷰(view)에서만 쓸 수 있는 유일한 트리거야. 뷰에서 직접 작업할 수 없는 경우, 이걸로 유연하게 처리할 수 있어.
시나리오: courses와 teachers라는 두 테이블이 있어. 이걸 합친 뷰를 만들고, 그 뷰에 INSERT할 때 동작하는 트리거를 만들어볼 거야.
테이블을 만들어보자:
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
teacher_id INT NOT NULL
);
CREATE TABLE teachers (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
뷰를 만들어보자:
CREATE VIEW course_details AS
SELECT
courses.id AS course_id,
courses.name AS course_name,
teachers.name AS teacher_name
FROM courses
JOIN teachers ON courses.teacher_id = teachers.id;
문제: 뷰는 두 테이블의 데이터를 합치니까, 그냥 INSERT할 수 없어. 해결책: INSTEAD OF 트리거를 쓰자.
트리거용 함수를 만들어보자:
CREATE OR REPLACE FUNCTION insert_course_details()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO teachers (name) VALUES (NEW.teacher_name) RETURNING id INTO NEW.teacher_id;
INSERT INTO courses (name, teacher_id) VALUES (NEW.course_name, NEW.teacher_id);
RETURN NULL; -- 뷰에는 데이터가 저장되지 않아
END;
$$ LANGUAGE plpgsql;
트리거를 만들어보자:
CREATE TRIGGER instead_of_insert_course_details
INSTEAD OF INSERT ON course_details
FOR EACH ROW
EXECUTE FUNCTION insert_course_details();
이제 뷰에 바로 데이터를 넣을 수 있어:
INSERT INTO course_details (course_name, teacher_name)
VALUES ('수학', '알렉스 밍');
SELECT * FROM courses;
-- 결과:
-- id | name | teacher_id
-- 1 | 수학 | 1
SELECT * FROM teachers;
-- 결과:
-- id | name
-- 1 | 알렉스 밍
트리거 타입 비교
| 트리거 타입 | 언제 실행됨 | 주요 용도 |
|---|---|---|
BEFORE |
작업 실행 전 | 검증, 데이터 준비 |
AFTER |
성공적으로 끝난 후 | 로그, 관련 데이터 업데이트 |
INSTEAD OF |
작업 대신 | 뷰에서 작업 처리 |
특징과 제한사항
BEFORE 트리거는 작업 전에 데이터를 바꿀 수 있어. 예를 들어, 이름을 자동으로 대문자로 바꿀 수도 있지.
AFTER 트리거는 이미 작업이 끝난 뒤라 데이터를 바꿀 수 없어. 오직 후처리용이야.
INSTEAD OF 트리거는 뷰에만 적용돼. 여러 관련 테이블에 복잡한 삽입/수정 로직을 구현할 수 있어.
오늘은 여기까지! BEFORE, AFTER, INSTEAD OF가 좀 헷갈릴 수도 있는데, 걱정하지 마. 핵심 원리랑 언제 쓰는지만 기억하면 돼. 직접 예제 몇 개 만들어보면서 연습해봐!
GO TO FULL VERSION