한국말로 '방아쇠'를 뜻하는 트리거는 SQL에서는 이벤트 발생했을때 전/후를 기준으로 같은 테이블 혹은 다른 테이블에 또 다른 이벤트를 발생시키는것을 말한다.
처음 트리거를 생각하게된 시점은 Questions(문의글)관련 API를 만들던 도중이였다.
우리가 만든 데이터스키마 구조에서는 qna_count라는 컬럼이 product테이블에 따로 존재했다.
그래서 questions 테이블의 row가 하나 생성될 때 마다 (문의글 1개가 추가될 때마다) 다른 테이블인 product 테이블에 qna_count컬럼을 하나 증가시켜줘야했다.
처음 이것을 파이썬 내에 로직처리를 했었는데, SQL에서도 자동으로 처리를 할 수 있는게 있지않나라는 호기심에 찾다 나온게 SQL의 트리거 기능이였다.
상단에 Aquery에서 모델링한 테이블을 보면 products 테이블을 questions 테이블이 1:M관계로 참조하고 있으며, qna_count라는 항목이 존재함을 알 수 있다.
def insert_question(self, params, db):
try :
with db.cursor() as cursor:
sql = """
INSERT INTO questions (
product_id,
user_id,
question_content,
question_type_id,
updated_at,
is_secreted
) VALUES (
%s,
%s,
%s,
%s,
now(),
%s
);
"""
q_info = params['q_info']
result = cursor.execute(sql, (
params["product_id"],
params["user_id"],
q_info["question_content"],
q_info["question_type_id"],
q_info["is_secreted"]
))
if not result:
raise Exception('Query failed')
count_up = """
UPDATE products
SET qna_count = qna_count+1
WHERE id = %s;
"""
count_result = cursor.execute(count_up, params["product_id"])
if not count_result:
raise Exception('Query failed')
except :
traceback.print_exc()
이것이 처음 구현했던 로직이다.
신규로 문의글을 등록하게되면 INSERT INTO로 questions에 새로운 row가 추가하라는 쿼리가 실행되며, 쿼리실행이 문제없이 이뤄진다면 count_up이라는 또다른 쿼리를 통해 products table에 qna_count를 +1 시켜주는 단순한 로직이다.
아래에 있는 count_up을 트리거를 이용해 sql내부에서 처리할 수 있게 할 수 있다. 말보다 작성된 SQL문을 보면서 차근차근 이해를 해보자.
DELIMITER $$ [1]
CREATE TRIGGER count_up [2]
AFTER INSERT ON questions FOR EACH ROW [3]
BEGIN [4]
IF NEW.id IS NOT NULL THEN [5]
UPDATE products SET qna_count = qna_count +1 WHERE id = NEW.product_id; [6]
END IF; [7]
END $$ [8]
DELIMITER ; [9]
[ 1 ] DELIMITER는 사실 써도 되고 안써도 되는 문자다. 트리거를 만들기 이전 TRIGGER 생성구문이라는 표시하기 위함이며, ;
라는 SQL마침문자를 파이썬 독스트링처럼 여러번 쓸 수있게 하기위해서 쓰는 문자라고 볼 수 있다.
[ 2 ] 트리거를 생성시켜준다. CREATE TRIGGER '생성하고싶은 트리거명'
으로 구문이 구성된다.
[ 3 ] AFTER
라는 것은 이벤트 이후에 트리거를 발동시킨다는 의미다. 이벤트 이전에 트리거를 발생시키고싶다면 BEFORE
를 써주면 된다.
그 다음으로 써야할 것은 이벤트 종류인데, INSERT
, UPDATE
, DELETE
중 기준으로 삼을 이벤트 종류를 선택하면 된다. 트리거라는 방아쇠를 당기기 위해서는 테이블의 내용이 '변경'된다는 점을 기준으로 깔고간다.
그러므로 SELECT
같이 테이블의 상태변경을 하지않은 단순조회는 트리거의 이벤트로 쓸 수 없게된다.
FOR EACH ROW
는 각각의 ROW를 모두 조회해 참조한다는 의미다.
[ 4 ] BEGIN
은 트리거를 통해 발생시키고싶은 이벤트 구문의 시작점을 의미한다.
[ 5 ] 트리거 구문은 다양한 형식으로 쓸 수 있는데, 여기서는 IF구문을 활용했다.
이벤트를 통해 변경/생성 된 데이터는 NEW
를 앞에 붙여서 구분 지어줘야하며 변경/삭제 이전 원 데이터는 반대로 OLD
를 붙여줘야한다.
내가 조건으로 삼은 것은 새로 등록된 questions의 PK ID가 NULL이 아니라면이다.
IF절이 끝나면 반드시 THEN
을 붙여 구분지어줘야한다.
[ 6 ] products의 qna카운트를 1올리는 쿼리문이다. questions 테이블 내에 이미 product_id가 존재하므로 NEW.product_id를 기준으로 qna_count를 올릴 products 테이블 row를 찾아내면 된다.
[ 7 ] ENDIF;
는 IF구문이 끝났음을 알려준다.
[ 8 ] END $$
는 전체 트리거 구문의 종료를 의미한다. 띄어쓰기에 유의하자.
[ 9 ] 마지막으로 DELIMETER ;
통해 전체 쿼리문을 완료시킨다.
이번 프로젝트에서는 SOFT DELETE
라는 개념을 써서 사실 작동방식은 DELELTE가 아니라 UPDATE하고 똑같다. 로직을 통해 is_deleted(삭제여부)
가 1(TRUE)
로 바뀌면(update되면) qna_count
를 트리거를 통해 -1 시키게 된다.
DELIMITER $$
CREATE TRIGGER count_down
AFTER UPDATE ON questions FOR EACH ROW
BEGIN
IF NEW.is_deleted = 1 THEN
UPDATE products SET qna_count = qna_count -1 WHERE id = OLD.product_id;
END IF;
END $$
DELIMITER ;