본문 바로가기
WIKI 기술 지식 베이스

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)