CREATE TRIGGER

CREATE TRIGGER

이 페이지에서는 SQLite에서 CREATE TRIGGER 문을 사용하여 트리거를 생성하는 방법을 설명합니다. 트리거는 특정 테이블에서 삽입, 갱신, 삭제 같은 이벤트가 발생할 때 자동으로 실행되는 동작을 정의합니다. 문법과 함께 동작 방식과 예시를 확인할 수 있습니다.

출처: 문서

본문

1. 구문

create-trigger-stmt:

CREATE TEMP TEMPORARY TRIGGER IF NOT EXISTS schema-name . trigger-name BEFORE AFTER INSTEAD OF DELETE INSERT UPDATE OF column-name , ON table-name FOR EACH ROW WHEN expr BEGIN update-stmt ; END insert-stmt delete-stmt select-stmt

delete-stmt:

WITH RECURSIVE common-table-expression , DELETE FROM qualified-table-name returning-clause expr WHERE

common-table-expression:

table-name ( column-name ) AS NOT MATERIALIZED ( select-stmt ) ,

qualified-table-name:

schema-name . table-name AS alias INDEXED BY index-name NOT INDEXED

returning-clause:

RETURNING expr AS column-alias * ,

expr:

literal-value bind-parameter schema-name . table-name . column-name unary-operator expr expr binary-operator expr function-name ( function-arguments ) filter-clause over-clause ( expr ) , CAST ( expr AS type-name ) expr COLLATE collation-name expr NOT LIKE GLOB REGEXP MATCH expr expr ESCAPE expr expr ISNULL NOTNULL NOT NULL expr IS NOT DISTINCT FROM expr expr NOT BETWEEN expr AND expr expr NOT IN ( select-stmt ) expr , schema-name . table-function ( expr ) table-name , NOT EXISTS ( select-stmt ) CASE expr WHEN expr THEN expr ELSE expr END raise-function

filter-clause:

FILTER ( WHERE expr )

function-arguments:

DISTINCT expr , * ORDER BY ordering-term ,

ordering-term:

expr COLLATE collation-name DESC ASC NULLS FIRST NULLS LAST

literal-value:

CURRENT_TIMESTAMP numeric-literal string-literal blob-literal NULL TRUE FALSE CURRENT_TIME CURRENT_DATE

over-clause:

OVER window-name ( base-window-name PARTITION BY expr , ORDER BY ordering-term , frame-spec )

frame-spec:

GROUPS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING RANGE ROWS UNBOUNDED PRECEDING expr PRECEDING CURRENT ROW expr PRECEDING CURRENT ROW expr FOLLOWING expr PRECEDING CURRENT ROW expr FOLLOWING EXCLUDE CURRENT ROW EXCLUDE GROUP EXCLUDE TIES EXCLUDE NO OTHERS

ordering-term:

expr COLLATE collation-name DESC ASC NULLS FIRST NULLS LAST

raise-function:

RAISE ( ROLLBACK , expr ) IGNORE ABORT FAIL

type-name:

name ( signed-number , signed-number ) ( signed-number )

signed-number:

+ numeric-literal -

insert-stmt:

WITH RECURSIVE common-table-expression , REPLACE INSERT OR ROLLBACK INTO ABORT FAIL IGNORE REPLACE schema-name . table-name AS alias ( column-name ) , VALUES ( expr ) , , upsert-clause select-stmt upsert-clause DEFAULT VALUES returning-clause

common-table-expression:

table-name ( column-name ) AS NOT MATERIALIZED ( select-stmt ) ,

returning-clause:

RETURNING expr AS column-alias * ,

upsert-clause:

ON CONFLICT ( indexed-column ) WHERE expr DO , conflict target UPDATE SET column-name-list = expr WHERE expr NOTHING , column-name

column-name-list:

( column-name ) ,

indexed-column:

column-name COLLATE collation-name DESC expr ASC

select-stmt:

WITH RECURSIVE common-table-expression , SELECT DISTINCT result-column , ALL FROM table-or-subquery join-clause , WHERE expr GROUP BY expr HAVING expr , WINDOW window-name AS window-defn , VALUES ( expr ) , , compound-operator select-core ORDER BY LIMIT expr ordering-term , OFFSET expr , expr

common-table-expression:

table-name ( column-name ) AS NOT MATERIALIZED ( select-stmt ) ,

compound-operator:

UNION UNION INTERSECT EXCEPT ALL

join-clause:

table-or-subquery join-operator table-or-subquery join-constraint

join-constraint:

USING ( column-name ) , ON expr

join-operator:

NATURAL LEFT OUTER JOIN , RIGHT FULL INNER CROSS

ordering-term:

expr COLLATE collation-name DESC ASC NULLS FIRST NULLS LAST

result-column:

expr AS column-alias * table-name . *

table-or-subquery:

schema-name . table-name AS table-alias INDEXED BY index-name NOT INDEXED table-function-name ( expr ) , AS table-alias ( select-stmt ) ( table-or-subquery ) , join-clause

window-defn:

( base-window-name PARTITION BY expr , ORDER BY ordering-term , frame-spec )

frame-spec:

GROUPS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING RANGE ROWS UNBOUNDED PRECEDING expr PRECEDING CURRENT ROW expr PRECEDING CURRENT ROW expr FOLLOWING expr PRECEDING CURRENT ROW expr FOLLOWING EXCLUDE CURRENT ROW EXCLUDE GROUP EXCLUDE TIES EXCLUDE NO OTHERS

update-stmt:

WITH RECURSIVE common-table-expression , UPDATE OR ROLLBACK qualified-table-name OR REPLACE OR IGNORE OR FAIL OR ABORT SET column-name-list = expr column-name , FROM table-or-subquery , join-clause WHERE expr returning-clause

column-name-list:

( column-name ) ,

common-table-expression:

table-name ( column-name ) AS NOT MATERIALIZED ( select-stmt ) ,

join-clause:

table-or-subquery join-operator table-or-subquery join-constraint

join-constraint:

USING ( column-name ) , ON expr

join-operator:

NATURAL LEFT OUTER JOIN , RIGHT FULL INNER CROSS

qualified-table-name:

schema-name . table-name AS alias INDEXED BY index-name NOT INDEXED

returning-clause:

RETURNING expr AS column-alias * ,

table-or-subquery:

schema-name . table-name AS table-alias INDEXED BY index-name NOT INDEXED table-function-name ( expr ) , AS table-alias ( select-stmt ) ( table-or-subquery ) , join-clause

2. 설명

CREATE TRIGGER 문은 데이터베이스 스키마에 트리거를 추가하는 데 사용해요. 트리거는 지정된 데이터베이스 이벤트가 발생할 때 자동으로 수행되는 데이터베이스 작업이에요.

각 트리거는 DELETE, INSERT, UPDATE 중 하나의 작업에 대해 실행되도록 지정해야 해요. 트리거는 삭제, 삽입, 또는 갱신되는 각 행마다 한 번씩 실행돼요. UPDATE OF column-name 구문을 사용하면, column-name이 UPDATE 문의 SET 절에 있는 항목 중 하나의 왼쪽에 나타날 때만 트리거가 실행돼요.

역사적인 불찰로 인해, "UPDATE OF" 절에 지정된 열은 실제로 갱신 대상 테이블에 존재하지 않아도 돼요. 인식되지 않는 열 이름은 조용히 무시돼요. 만약 SQLite가 "UPDATE OF" 절의 이름 중 하나라도 테이블의 열이 아니면 CREATE TRIGGER 문을 실패시킨다면 더 좋았을 거예요. 하지만 이 문제는 SQLite가 널리 배포된 지 여러 해 후에 발견되었기 때문에, 기존 애플리케이션을 깨뜨릴까 봐 문제를 고치지 않기로 했어요.

현재 SQLite는 FOR EACH ROW 트리거만 지원하고 FOR EACH STATEMENT 트리거는 지원하지 않아요. 따라서 FOR EACH ROW를 명시적으로 지정하는 것은 선택사항이에요. FOR EACH ROW는 트리거가 실행되게 하는 문장에 의해 삽입, 갱신, 또는 삭제되는 각 데이터베이스 행에 대해 (WHEN 절에 따라) 트리거에 지정된 SQL 문이 실행될 수 있음을 의미해요.

WHEN 절과 트리거 동작 모두 NEW.column-nameOLD.column-name 형식의 참조를 사용하여 삽입, 삭제 또는 갱신되는 행의 요소에 접근할 수 있어요. 여기서 column-name은 트리거와 연결된 테이블의 열 이름이에요. OLD 및 NEW 참조는 관련된 이벤트에 대한 트리거에서만 사용할 수 있어요. 다음과 같아요:

이벤트 유효한 참조
INSERT NEW 참조가 유효해요
UPDATE NEW 및 OLD 참조가 유효해요
DELETE OLD 참조가 유효해요

WHEN 절이 제공되면, 지정된 SQL 문은 WHEN 절이 참인 경우에만 실행돼요. WHEN 절이 제공되지 않으면, SQL 문은 트리거가 실행될 때마다 실행돼요.

BEFORE 또는 AFTER 키워드는 연결된 행의 삽입, 수정 또는 제거와 관련하여 트리거 동작이 언제 실행될지를 결정해요. 두 키워드가 모두 없으면 BEFORE가 기본값이에요.

ON CONFLICT 절은 트리거 본문 내의 UPDATE 또는 INSERT 동작의 일부로 지정될 수 있어요. 그러나 트리거를 실행하게 하는 문장의 일부로 ON CONFLICT 절이 지정되면, 외부 문장의 충돌 처리 정책이 대신 사용돼요.

트리거는 연결된 테이블(table-name 테이블)이 삭제될 때 자동으로 삭제돼요. 그러나 트리거 동작이 다른 테이블을 참조한다면, 그 다른 테이블이 삭제되거나 수정되어도 트리거는 삭제되거나 수정되지 않아요.

트리거는 DROP TRIGGER 문을 사용하여 제거해요.

2.1. 트리거 내 UPDATE, DELETE, INSERT 문의 구문 제한

트리거 내의 UPDATE, DELETE, INSERT 문은 UPDATE, DELETE, INSERT 문의 전체 구문을 지원하지 않아요. 다음 제한 사항이 적용돼요:

  • UPDATE, DELETE, INSERT 문에서 수정할 테이블의 이름은 스키마 한정 없는(unqualified) 테이블 이름이어야 해요. 즉, 테이블을 지정할 때 tablename만 사용해야 하고 database.tablename은 사용할 수 없어요.

  • TEMP가 아닌 트리거의 경우, 수정하거나 조회할 테이블은 트리거가 연결된 테이블 또는 뷰와 동일한 데이터베이스에 존재해야 해요. TEMP 트리거는 동일 데이터베이스 규칙을 적용받지 않아요. TEMP 트리거는 ATTACH된 모든 데이터베이스에 있는 모든 테이블을 조회하거나 수정할 수 있어요.

  • INSERT 문의 INSERT INTO table DEFAULT VALUES 형식은 지원되지 않아요.

  • INDEXED BY 및 NOT INDEXED 절은 UPDATE 및 DELETE 문에서 지원되지 않아요.

  • UPDATE 및 DELETE 문의 ORDER BY 및 LIMIT 절은 지원되지 않아요. ORDER BY와 LIMIT는 일반적으로 어떤 상황에서도 UPDATE나 DELETE에 대해 지원되지 않지만, SQLITE_ENABLE_UPDATE_DELETE_LIMIT 컴파일 타임 옵션을 사용하여 최상위 문에서는 활성화할 수 있어요. 그러나 그 컴파일 타임 옵션은 최상위 UPDATE 및 DELETE 문에만 적용되며 트리거 내의 UPDATE 및 DELETE 문에는 적용되지 않아요.

  • 공통 테이블 식은 트리거 내부의 문에서 직접 지원되지 않지만, 트리거 내부의 문에서 사용되는 하위 SELECT 내에 포함될 수는 있어요.

3. INSTEAD OF 트리거

BEFORE와 AFTER 트리거는 일반 테이블에서만 작동해요. INSTEAD OF 트리거는 뷰에서만 작동해요.

뷰에 INSTEAD OF INSERT 트리거가 있으면 그 뷰에 대해 INSERT 문을 실행할 수 있어요. 실제 삽입은 발생하지 않아요. 대신 트리거 안에 포함된 문들이 실행돼요. INSTEAD OF DELETE 및 INSTEAD OF UPDATE 트리거도 뷰에 대한 DELETE 및 UPDATE 문에서 동일한 방식으로 작동해요.

sqlite3_changes() 및 sqlite3_total_changes() 인터페이스는 INSTEAD OF 트리거 실행 횟수를 세지 않지만, count_changes pragma는 INSTEAD OF 트리거 실행을 세요.

4. 몇 가지 예제 트리거

고객 레코드가 "customers" 테이블에 저장되고 주문 레코드가 "orders" 테이블에 저장된다고 가정할 때, 다음 UPDATE 트리거는 고객이 주소를 변경하면 연결된 모든 주문이 새 주소로 리디렉션되도록 보장해요.

CREATE TRIGGER update_customer_address UPDATE OF address ON customers 
  BEGIN
    UPDATE orders SET address = new.address WHERE customer_name = old.name;
  END;

이 트리거가 설치된 상태에서 다음 문을 실행하면:

UPDATE customers SET address = '1 Main St.' WHERE name = 'Jack Jones';

다음 문이 자동으로 실행돼요:

UPDATE orders SET address = '1 Main St.' WHERE customer_name = 'Jack Jones';

INSTEAD OF 트리거의 예로 다음 스키마를 살펴봐요:

CREATE TABLE customer(
  cust_id INTEGER PRIMARY KEY,
  cust_name TEXT,
  cust_addr TEXT
);
CREATE VIEW customer_address AS
   SELECT cust_id, cust_addr FROM customer;
CREATE TRIGGER cust_addr_chng
INSTEAD OF UPDATE OF cust_addr ON customer_address
BEGIN
  UPDATE customer SET cust_addr=NEW.cust_addr
   WHERE cust_id=NEW.cust_id;
END;

위 스키마에서 다음 형식의 문은:

UPDATE customer_address SET cust_addr=$new_address WHERE cust_id=$cust_id;

customer.cust_id가 $cust_id 매개변수와 같은 특정 고객 항목에 대해 customer.cust_addr 필드를 갱신해요. 뷰에 할당된 값들이 트리거 본문의 특별한 "NEW" 테이블에서 필드로 어떻게 제공되는지 주목해요.

5. BEFORE 트리거 사용 시 주의사항

BEFORE UPDATE 또는 BEFORE DELETE 트리거가 갱신되거나 삭제될 예정이었던 행을 수정하거나 삭제하면, 이후의 갱신 또는 삭제 연산 결과는 정의되지 않아요. 게다가 BEFORE 트리거가 행을 수정하거나 삭제하면, 그 행들에 대해 그렇지 않으면 실행되었을 AFTER 트리거가 실제로 실행될지 여부도 정의되지 않아요.

rowid가 명시적으로 정수로 설정되지 않은 BEFORE INSERT 트리거에서는 NEW.rowid 값이 정의되지 않아요.

위에서 설명한 동작 때문에 프로그래머는 BEFORE 트리거보다 AFTER 트리거를 사용하는 것이 좋아요.

6. RAISE() 함수

특별한 SQL 함수 RAISE()는 트리거 프로그램 안에서 다음 구문으로 사용될 수 있어요.

raise-function:

RAISE ( ROLLBACK , expr ) IGNORE ABORT FAIL

트리거 프로그램 실행 중에 RAISE(ROLLBACK,...), RAISE(ABORT,...) 또는 RAISE(FAIL,...) 중 하나가 호출되면, 지정된 ON CONFLICT 처리가 수행되고 현재 쿼리가 종료돼요. 애플리케이션에는 지정된 오류 메시지와 함께 SQLITE_CONSTRAINT 오류 코드가 반환돼요.

RAISE(IGNORE)가 호출되면 현재 트리거 프로그램의 나머지 부분, 트리거 프로그램을 실행하게 한 문, 그리고 그 후에 실행되었을 모든 후속 트리거 프로그램은 모두 중단돼요. 데이터베이스 변경 사항은 롤백되지 않아요. 트리거 프로그램을 실행하게 한 문이 그 자체로 트리거 프로그램의 일부라면, 그 트리거 프로그램은 다음 단계의 시작부터 실행을 재개해요.

버전 3.47.0(2024-10-21)부터 오류 메시지는 SQL 표현식일 수 있어요. 이전 버전의 SQLite에서는 오류 메시지가 문자열 리터럴이어야 했어요.

7. 비-TEMP 테이블의 TEMP 트리거

트리거는 일반적으로 CREATE TRIGGER 문에서 "ON" 키워드 뒤에 오는 테이블과 같은 데이터베이스에 존재해요. 다만 다른 데이터베이스의 테이블에 TEMP TRIGGER를 만들 수도 있어요. 이러한 트리거는 트리거를 정의한 애플리케이션이 대상 테이블을 변경할 때만 실행돼요. 데이터베이스를 수정하는 다른 애플리케이션은 TEMP 트리거를 볼 수 없으므로 트리거를 실행할 수 없어요.

비-TEMP 테이블에 TEMP 트리거를 정의할 때는 비-TEMP 테이블이 있는 데이터베이스를 지정하는 것이 중요해요. 예를 들어 다음 문에서는 "tab1"만 쓰지 말고 "main.tab1"이라고 써야 해요.

CREATE TEMP TRIGGER ex1 AFTER INSERT ON main.tab1 BEGIN ...

대상 테이블에 스키마 이름을 지정하지 않으면 스키마 변경이 발생할 때마다 TEMP 트리거가 다른 데이터베이스에 있는 같은 이름의 테이블에 다시 연결될 수 있어요.

이 페이지는 2026-02-26 14:36:36Z에 마지막으로 갱신되었어요.

더 알아보기 (Learn more)