We.TIL 44 : SQL트리거

김기욱·2020년 10월 7일
0

We.TIL

목록 보기
69/69

Trigger

한국말로 '방아쇠'를 뜻하는 트리거는 SQL에서는 이벤트 발생했을때 전/후를 기준으로 같은 테이블 혹은 다른 테이블에 또 다른 이벤트를 발생시키는것을 말한다.

처음 트리거를 생각하게된 시점은 Questions(문의글)관련 API를 만들던 도중이였다.
우리가 만든 데이터스키마 구조에서는 qna_count라는 컬럼이 product테이블에 따로 존재했다.

그래서 questions 테이블의 row가 하나 생성될 때 마다 (문의글 1개가 추가될 때마다) 다른 테이블인 product 테이블에 qna_count컬럼을 하나 증가시켜줘야했다.

처음 이것을 파이썬 내에 로직처리를 했었는데, SQL에서도 자동으로 처리를 할 수 있는게 있지않나라는 호기심에 찾다 나온게 SQL의 트리거 기능이였다.

구현과정(INSERT)

상단에 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 ; 통해 전체 쿼리문을 완료시킨다.

삭제?(DELETE?..No.. UPDATE)

이번 프로젝트에서는 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 ;
profile
어려운 것은 없다, 다만 아직 익숙치않을뿐이다.

0개의 댓글