본문 바로가기
개발

[sql, sqlalchemy] 연결된 두 테이블 cascade 설정하기

by 박영귤 2023. 8. 7.

유저테이블이랑, 게시글 테이블이 있다고 가정해보자. 

게시글 테이블은 id, user_id, content로 이루어져있다고 가정하자.

아무 설정도 하지 않는다면 user를 삭제하려고 한다면 아마 삭제되지 않을 것이다. 이는 user테이블과 연결된  게시글 테이블에 아직 데이터가 남아있기 때문이다. 이럴 때 어떻게 해라 라고 제약조건을 걸 수 있다. CASCADE 제약조건에 대해 알아보자.

on delete, on update에 설정할 수 있는 제약조건이 여럿 있다. 이 중 restrict, set null, set default는 이름만 봐도 대충 알 수 있을 것이다. 하지만 우리는 cascade를 사용할 것이다. 이게 뭘까?

 

1. RESTRICT : 개체를 변경/삭제할 때 다른 개체가 변경/삭제할 개체를 참조하고 있을 경우 변경/삭제가 취소됩니다.(제한)

2. CASCADE : 개체를 변경/삭제할 때 다른 개체가 변경/삭제할 개체를 참조하고 있을 경우 함께 변경/삭제됩니다.

3. NO ACTION : MYSQL에서는 RESTRICT와 동일합니다.

4. SET NULL : 개체를 변경/삭제할 때 다른 개체가 변경/삭제할 개체를 참조하고 있을 경우 참조하고 있는 값은 NULL로 세팅됩니다.

 

라고 한다.

즉 cascade는 삭제하면 같이 삭제하고, 업데이트하면 같이 업데이트하게끔 설정해주는 것이다. 사실 이것은 이미 오래전에 알고 있었다. 하지만, sqlalchemy에서 이 설정을 해놔도, 전혀 제대로 삭제되지 않았었다. 이번 기회에 왜 이런것인지 한번 찾아보았다. 며칠 전에 짠 공지부분으로 설명하겠다.

 

class Announcement(db.Model):
  id = db.Column(db.Integer, primary_key=True, autoincrement=True)
  title = db.Column(db.String(50), nullable=False)
  content = db.Column(db.Text, nullable=True)
  
class AnnouncementUrl(db.Model):
  id = db.Column(db.Integer, primary_key=True, autoincrement=True)
  announcement_id = db.Column(db.Integer, db.ForeignKey('announcement.id', ondelete='CASCADE'), nullable=False)
  announcement = db.relationship('Announcement', backref=db.backref('urls'))
  url = db.Column(db.Text, nullable=False)

Announcement : 공지, AnnouncementUrl : 공지에 첨부할 url

원래는 모델을 이렇게 작성했었다. ondelete='CASCADE'로 작성하여 설정을 제대로 해준 줄 알았다. 하지만 데이터를 삭제하려고 하면 아래와 같은 에러가 뜨곤 했다.

AssertionError: Dependency rule tried to blank-out primary key column 'announcement_url.announcement_id' on instance '<AnnouncementUrl at xxx>'

 

즉, announcement를 삭제하려니까 announcement_url의 announcement_id 열이 자꾸 null(blank-out)된다는 의미이다. 제대로 삭제가 안되는  것이다.

이에 대한 해결책은 간단하다. 모델의 relationship에도 cascade라고 명시해주면 된다.

class Announcement(db.Model):
  id = db.Column(db.Integer, primary_key=True, autoincrement=True)
  title = db.Column(db.String(50), nullable=False)
  content = db.Column(db.Text, nullable=True)
  
class AnnouncementUrl(db.Model):
  id = db.Column(db.Integer, primary_key=True, autoincrement=True)
  announcement_id = db.Column(db.Integer, db.ForeignKey('announcement.id', ondelete='CASCADE'), nullable=False)
  announcement = db.relationship('Announcement', backref=db.backref('urls', cascade='delete'))
  url = db.Column(db.Text, nullable=False)

이렇게 해주면 잘 작동이 된다.