CREATE TRIGGER
CREATE TRIGGER는 테이블에 행이 삽입되거나, 갱신되거나, 삭제될 때 SQL 문을 자동으로 실행하는 트리거를 만들어요.
출처: 문서
본문
행이 테이블에 삽입되거나, 갱신되거나, 삭제될 때 하나 이상의 SQL 문을 자동으로 실행하는 트리거를 만들어요.
문법
CREATE [TEMPORARY] TRIGGER [IF NOT EXISTS] [schema-name.]trigger-name
[BEFORE | AFTER | INSTEAD OF]
{INSERT | UPDATE [OF column-name, ...] | DELETE}
ON table-name
[FOR EACH ROW]
[WHEN expression]
BEGIN
statement;
[statement; ...]
END;
설명
트리거는 테이블이나 뷰에서 지정한 데이터 수정 이벤트가 일어날 때 자동으로 실행되는 SQL 문 집합을 정의해요. 트리거는 자기를 발동시킨 문과 같은 트랜잭션 안에서 실행돼요 — 트랜잭션이 롤백되면 트리거의 효과도 함께 롤백돼요.
매개변수
| 매개변수 | 설명 |
|---|---|
TEMPORARY |
트리거를 임시 데이터베이스에 만들어요. 트리거는 현재 연결에서만 보이고, 연결이 닫히면 사라져요. TEMP도 동의어로 받아들여요. |
IF NOT EXISTS |
같은 이름의 트리거가 이미 있을 때 오류를 막아줘요. |
schema-name |
트리거가 들어 있는 붙은(attached) 데이터베이스 이름. 생략하면 main 데이터베이스. TEMPORARY와 함께 쓸 수 없어요. |
trigger-name |
데이터베이스 안에서 고유한 트리거 이름. |
BEFORE / AFTER / INSTEAD OF |
트리거가 트리거링 이벤트를 기준으로 언제 발동할지. 기본값은 BEFORE. INSTEAD OF 트리거는 아직 지원되지 않아요. |
INSERT / UPDATE / DELETE |
트리거를 발동시키는 데이터 수정 이벤트. |
OF column-name, ... |
UPDATE 트리거 전용. 지정한 컬럼이 수정될 때만 트리거가 발동되도록 제한해요. |
table-name |
트리거가 감시하는 테이블 (INSTEAD OF 트리거는 뷰). |
FOR EACH ROW |
수정된 행마다 트리거가 한 번씩 발동돼요. 유일하게 지원되는 모드이고, 생략해도 기본으로 그래요. |
WHEN expression |
선택적 조건. 표현식이 참으로 평가되는 행에만 트리거 본문이 실행돼요. |
트리거 타이밍
BEFORE 트리거
BEFORE 트리거는 트리거링 문이 행을 수정하기 전에 발동돼요. 값이 기록되기 전에 검증하거나 변형할 때 BEFORE 트리거를 쓰세요.
NEW행 참조는 이제 막 쓰이려는 값을 담고 있어요. INSERT 트리거에서는NEW가 삽입되는 행이고, UPDATE 트리거에서는NEW가 갱신된 값을 담아요.OLD행 참조는 UPDATE와 DELETE 트리거에서 사용할 수 있고, 수정 전의 현재 값을 담아요.- BEFORE 트리거가 오류를 일으키면, 그 행에 대한 트리거링 연산은 중단돼요.
AFTER 트리거
AFTER 트리거는 트리거링 문이 행을 수정한 뒤에 발동돼요. 로깅, 감사, 다른 테이블로의 변경 전파에는 AFTER 트리거를 쓰세요.
NEW와OLD행 참조는 BEFORE 트리거와 같은 의미로 사용할 수 있어요.- 트리거 본문이 실행될 때 행은 이미 기록된 뒤예요.
INSTEAD OF 트리거
`INSTEAD OF` 트리거는 아직 Turso에서 지원되지 않아요. `CREATE TRIGGER ... INSTEAD OF ...`는 오류를 반환해요.SQLite에서 INSTEAD OF 트리거는 뷰에만 만들 수 있고, 트리거링 INSERT, UPDATE, DELETE를 대신해서 발동되어 뷰를 쓰기 가능하게 만들어줘요. Turso는 이 문법을 파싱하지만 아직 실행하지는 않아요.
행 참조
트리거 본문 안에서 NEW와 OLD는 컬럼 값에 접근하게 해주는 특별한 행 참조예요.
| 참조 | INSERT | UPDATE | DELETE |
|---|---|---|---|
NEW.column |
삽입되는 값 | 갱신된 값 | 사용 불가 |
OLD.column |
사용 불가 | 갱신 전 값 | 삭제되는 값 |
-- Access individual columns
NEW.email
OLD.status
WHEN 절
선택적인 WHEN 절은 어떤 행이 트리거 본문을 실행시키는지 걸러줘요. 표현식은 NEW와 OLD 컬럼을 참조할 수 있어요.
-- Only fire when the status column actually changes
CREATE TRIGGER log_status_change
AFTER UPDATE OF status ON orders
WHEN OLD.status != NEW.status
BEGIN
INSERT INTO order_log (order_id, old_status, new_status, changed_at)
VALUES (NEW.id, OLD.status, NEW.status, datetime('now'));
END;
UPDATE OF 컬럼
UPDATE 트리거에서는 지정한 컬럼이 수정될 때만 트리거가 발동되도록 제한할 수 있어요. OF 절이 없으면 테이블에 대한 어떤 UPDATE에도 트리거가 발동돼요.
-- Only fires when price or quantity changes, not when name changes
CREATE TRIGGER recalc_total
BEFORE UPDATE OF price, quantity ON line_items
BEGIN
UPDATE line_items SET total = NEW.price * NEW.quantity WHERE id = NEW.id;
END;
RAISE 함수
RAISE 함수는 트리거 본문 안(그리고 다른 문맥)에서 실행을 중단하고 오류를 알릴 때 써요. 네 가지 형태가 있어요:
| 형태 | 동작 |
|---|---|
RAISE(IGNORE) |
현재 행에 대해 트리거 본문의 나머지와 트리거링 문을 건너뛰어요. 처리는 다음 행으로 계속돼요. |
RAISE(ABORT, message) |
현재 문을 중단하고 그 문이 만든 변경을 롤백하지만, 트랜잭션 안의 이전 변경은 유지해요. 기본 오류 처리 동작이에요. |
RAISE(ROLLBACK, message) |
현재 문을 중단하고 트랜잭션 전체를 롤백해요. |
RAISE(FAIL, message) |
현재 문을 중단해요. 그 문이 이미 만든 변경(이전 행들)은 유지되지만, 현재 행과 이후 행은 처리되지 않아요. |
CREATE TRIGGER validate_age
BEFORE INSERT ON users
BEGIN
SELECT RAISE(ABORT, 'age must be positive')
WHERE NEW.age <= 0;
END;
여러 문장
트리거 본문에는 세미콜론으로 구분된 여러 SQL 문이 들어갈 수 있어요. 문들은 같은 트랜잭션 안에서 순서대로 실행돼요.
CREATE TRIGGER on_user_delete
AFTER DELETE ON users
BEGIN
DELETE FROM user_preferences WHERE user_id = OLD.id;
DELETE FROM user_sessions WHERE user_id = OLD.id;
INSERT INTO audit_log (action, entity, entity_id, performed_at)
VALUES ('delete', 'user', OLD.id, datetime('now'));
END;
예제
감사 로깅 트리거
감사 로그로 테이블의 모든 변경을 추적해요.
CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
owner TEXT NOT NULL,
balance REAL NOT NULL DEFAULT 0
);
CREATE TABLE account_audit (
id INTEGER PRIMARY KEY,
account_id INTEGER NOT NULL,
action TEXT NOT NULL,
old_balance REAL,
new_balance REAL,
changed_at TEXT NOT NULL
);
CREATE TRIGGER audit_balance_change
AFTER UPDATE OF balance ON accounts
BEGIN
INSERT INTO account_audit (account_id, action, old_balance, new_balance, changed_at)
VALUES (NEW.id, 'update', OLD.balance, NEW.balance, datetime('now'));
END;
-- This UPDATE automatically creates an audit row
UPDATE accounts SET balance = balance + 100 WHERE id = 1;
검증 트리거
데이터가 기록되기 전에 비즈니스 규칙을 강제해요.
CREATE TABLE reservations (
id INTEGER PRIMARY KEY,
room_id INTEGER NOT NULL,
check_in TEXT NOT NULL,
check_out TEXT NOT NULL
);
CREATE TRIGGER validate_reservation
BEFORE INSERT ON reservations
BEGIN
SELECT RAISE(ABORT, 'check_out must be after check_in')
WHERE NEW.check_out <= NEW.check_in;
END;
연쇄 갱신 트리거
관련 테이블로 변경을 전파해요.
CREATE TABLE products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
category_id INTEGER
);
CREATE TABLE categories (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
-- When a category is deleted, unset the category on all associated products
CREATE TRIGGER clear_product_category
BEFORE DELETE ON categories
BEGIN
UPDATE products SET category_id = NULL WHERE category_id = OLD.id;
END;
더 알아보기 (Learn more)
- DROP TRIGGER - 트리거 제거하기
- CREATE VIEW - INSTEAD OF 트리거를 쓸 수 있는 뷰
- INSERT, UPDATE, DELETE - 트리거를 발동시키는 문들