PostgreSQL에서 트리거는 어떤 동작에 반응해서 함수 실행만 하는 게 아니라, 그 함수에 신기한 변수들도 같이 넘겨줘. 이 변수들 덕분에 테이블 데이터가 작업 전엔 어땠고, 작업 후엔 어떻게 바뀌었는지, 그리고 무슨 작업이 일어났는지 알 수 있어.
OLD— 작업 실행 전 테이블 행의 예전 데이터를 담고 있어.UPDATE랑DELETE트리거에서 쓰이고,INSERT에선 "예전" 데이터가 없으니까 안 써.NEW— 작업 실행 후 테이블 행의 새 데이터를 담고 있어.INSERT랑UPDATE트리거에서 써.TG_OP— 지금 무슨 작업인지 텍스트로 알려줘:INSERT,UPDATE,DELETE중 하나야.
이 변수들은 트리거랑 연결된 함수 안에서 자동으로 쓸 수 있어.
이론만 있으면 SQL에 인덱스 없는 거랑 똑같이 느리고 답답하지. 그래서 바로 실전 예제로 가보자.
OLD로 예전 데이터 접근하기
예를 들어 students 테이블이 있다고 해보자. 누가 학생 나이를 수정했어 (갑자기 머릿속에 학생이 아직 20살이라고 착각해서, 사실은 25살인데).
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
age INT NOT NULL
);
무슨 변경이 있었는지 추적하려면 로그 테이블을 만들어보자:
CREATE TABLE student_changes (
change_id SERIAL PRIMARY KEY,
student_id INT NOT NULL,
old_value INT,
new_value INT,
change_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
이제 다음 단계: 변경사항을 기록하는 함수를 만들자. 여기서 OLD가 필요해:
CREATE OR REPLACE FUNCTION log_student_changes()
RETURNS TRIGGER AS $$
BEGIN
-- 나이 변경 로그 남기기
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (OLD.id, OLD.age, NEW.age);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
이제 트리거를 만들어보자:
CREATE TRIGGER student_age_update
AFTER UPDATE OF age ON students
FOR EACH ROW
WHEN (OLD.age IS DISTINCT FROM NEW.age) -- 나이가 바뀌었을 때만 실행
EXECUTE FUNCTION log_student_changes();
학생을 추가하고, 그 다음에 값을 바꿔보자:
INSERT INTO students (name, age) VALUES ('알리사', 20);
UPDATE students
SET age = 25
WHERE name = '알리사';
-- 변경 로그 확인:
SELECT * FROM student_changes;
로그 테이블에 나이가 20에서 25로 바뀐 게 기록된 걸 볼 수 있을 거야. 마법 같지? 사실 OLD 덕분이야.
NEW로 새 데이터 다루기
이번엔 새 학생이 추가될 때마다 자동으로 그 학생의 ID랑 이름을 로그 테이블에 남기고 싶다고 해보자 (좀 오버 같지만, 가끔은 이런 게 필요해):
CREATE OR REPLACE FUNCTION log_new_student()
RETURNS TRIGGER AS $$
BEGIN
-- 새 학생 데이터 로그 남기기
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (NEW.id, NULL, NEW.age); -- INSERT라서 예전 값은 없음
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER student_insert_log
AFTER INSERT ON students
FOR EACH ROW
EXECUTE FUNCTION log_new_student();
다시, 새 학생을 추가하고 확인해보자:
INSERT INTO students (name, age) VALUES ('밥', 22);
-- 로그 확인:
SELECT * FROM student_changes;
로그에 새 학생이 추가된 걸 볼 수 있을 거야. 이 정도면 데이터 관리 제대로 하는 거지!
TG_OP로 작업 종류 구분하기
근데 만약 INSERT, UPDATE, DELETE까지 다 처리하는 만능 로그 트리거를 만들고 싶으면? 여기서 TG_OP 변수가 빛을 발해.
만능 함수를 만들어보자:
CREATE OR REPLACE FUNCTION log_all_operations()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (NEW.id, NULL, NEW.age);
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (OLD.id, OLD.age, NEW.age);
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO student_changes (student_id, old_value, new_value)
VALUES (OLD.id, OLD.age, NULL);
END IF;
RETURN NULL; -- AFTER 트리거에서 DELETE는 NULL 반환
END;
$$ LANGUAGE plpgsql;
이제 세 가지 작업 모두에 반응하는 트리거를 만들자:
CREATE TRIGGER universal_student_log
AFTER INSERT OR UPDATE OR DELETE ON students
FOR EACH ROW
EXECUTE FUNCTION log_all_operations();
학생을 추가, 수정, 삭제해보자:
INSERT INTO students (name, age) VALUES ('차를리', 30);
UPDATE students SET age = 31 WHERE name = '차를리';
DELETE FROM students WHERE name = '차를리';
-- 로그 확인:
SELECT * FROM student_changes;
모든 작업 로그가 다 남아있는 걸 볼 수 있을 거야 — 하나의 트리거로 다 관리하는 거지!
OLD, NEW, TG_OP 쓸 때 흔한 실수
트리거 쓰다 보면 이런 실수들 자주 해:
"OLD가 INSERT에서 왜 안 되지?" 이건 원래 그런 거야: INSERT엔 예전 데이터가 없어. NEW를 써야 해.
"DELETE에서 NEW가 왜 안 되지?" 이것도 마찬가지: DELETE엔 새 데이터가 없어. OLD를 써야 해.
트리거 논리가 무한 재귀를 일으킴. 트리거가 자기 자신을 계속 호출하지 않게 조심해야 해. WHEN 블록에 조건을 명확히 쓰거나 TG_OP로 체크하는 게 좋아.
GO TO FULL VERSION