[python] SQLAlchemy : 계단식 삭제

SQLAlchemy의 캐스케이드 옵션으로 사소한 것이 누락되어 있어야합니다. 왜냐하면 간단한 캐스케이드 삭제를 올바르게 작동 할 수 없기 때문입니다. 상위 요소가 삭제되면 하위 요소는 null외래 키 와 함께 유지됩니다 .

여기에 간결한 테스트 케이스를 넣었습니다.

from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key = True)

class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key = True)
    parentid = Column(Integer, ForeignKey(Parent.id))
    parent = relationship(Parent, cascade = "all,delete", backref = "children")

engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)

session = Session()

parent = Parent()
parent.children.append(Child())
parent.children.append(Child())
parent.children.append(Child())

session.add(parent)
session.commit()

print "Before delete, children = {0}".format(session.query(Child).count())
print "Before delete, parent = {0}".format(session.query(Parent).count())

session.delete(parent)
session.commit()

print "After delete, children = {0}".format(session.query(Child).count())
print "After delete parent = {0}".format(session.query(Parent).count())

session.close()

산출:

Before delete, children = 3
Before delete, parent = 1
After delete, children = 3
After delete parent = 0

부모와 자식 간에는 단순한 일대 다 관계가 있습니다. 스크립트는 부모를 만들고 3 개의 자식을 추가 한 다음 커밋합니다. 다음으로 부모를 삭제하지만 자식은 유지됩니다. 왜? 하위 항목을 계단식으로 삭제하려면 어떻게해야합니까?



답변

문제는 sqlalchemy Child가 부모로 간주 한다는 것입니다. 그 이유는 관계를 정의한 곳이기 때문입니다 (물론 “자식”이라고 부르는 것은 상관 없습니다).

Parent대신 클래스 에서 관계를 정의하면 다음과 같이 작동합니다.

children = relationship("Child", cascade="all,delete", backref="parent")

( "Child"문자열 참고 : 선언적 스타일을 사용할 때 허용되므로 아직 정의되지 않은 클래스를 참조 할 수 있습니다.)

추가 할 수도 delete-orphan있습니다 ( delete상위 항목이 삭제되면 하위 항목이 삭제되고 상위 항목이 삭제 delete-orphan되지 않은 경우에도 상위 항목에서 “제거 된”하위 항목도 삭제됨).

편집 : 그냥 발견 다음과 같은 경우 정말 상의 관계를 정의 할 Child클래스를, 당신이 그렇게 할 수 있습니다,하지만 당신은 캐스케이드 정의해야합니다 역 참조에를 (명시 적으로 역 참조를 작성하여), 다음과 같이 :

parent = relationship(Parent, backref=backref("children", cascade="all,delete"))

(함축 from sqlalchemy.orm import backref)


답변

@Steven의 asnwer는 session.delete()제 경우에는 결코 발생하지 않는 삭제를 할 때 좋습니다 . 나는 대부분의 시간을 통해 삭제한다는 것을 알았습니다 session.query().filter().delete()(메모리에 요소를 넣지 않고 db에서 직접 삭제합니다). 이 방법을 사용하면 sqlalchemy cascade='all, delete'가 작동하지 않습니다. 하지만 해결책이 있습니다 : ON DELETE CASCADEdb를 통해 (참고 : 모든 데이터베이스가 지원하는 것은 아닙니다).

class Child(Base):
    __tablename__ = "children"

    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey("parents.id", ondelete='CASCADE'))

class Parent(Base):
    __tablename__ = "parents"

    id = Column(Integer, primary_key=True)
    child = relationship(Child, backref="parent", passive_deletes=True)


답변

꽤 오래된 게시물이지만 한두 시간을 보냈으므로 특히 나열된 다른 댓글 중 일부가 옳지 않기 때문에 내 결과를 공유하고 싶었습니다.

TL; DR

자식 테이블에 외부 테이블을 지정하거나 기존 테이블을 수정하여 ondelete='CASCADE'다음을 추가합니다 .

parent_id = db.Column(db.Integer, db.ForeignKey('parent.id', ondelete='CASCADE'))

그리고 다음 관계 중 하나 :

a) 상위 테이블에 다음이 있습니다.

children = db.relationship('Child', backref='parent', passive_deletes=True)

b) 또는 자식 테이블에서 다음을 수행하십시오.

parent = db.relationship('Parent', backref=backref('children', passive_deletes=True))

세부

우선, 받아 들여진 대답에 따르면 부모 / 자녀 관계는를 사용하여 설정되지 않고을 사용하여 relationship설정됩니다 ForeignKey. relationship부모 또는 자식 테이블에를 넣을 수 있으며 제대로 작동합니다. 분명히 자식 테이블에서는 backref키워드 인수 외에 함수 를 사용해야합니다 .

옵션 1 (권장)

둘째, SqlAlchemy는 서로 다른 두 종류의 계단식을 지원합니다. 첫 번째와 내가 권장하는 것은 데이터베이스에 내장되어 있으며 일반적으로 외래 키 선언에 대한 제약의 형태를 취합니다. PostgreSQL에서는 다음과 같습니다.

CONSTRAINT child_parent_id_fkey FOREIGN KEY (parent_id)
REFERENCES parent_table(id) MATCH SIMPLE
ON DELETE CASCADE

parent_table, 에서 레코드를 삭제하면의 모든 해당 행 child_table이 데이터베이스에 의해 삭제됩니다. 빠르고 신뢰할 수 있으며 아마도 최선의 방법입니다. 다음 ForeignKey과 같이 SqlAlchemy에서 설정합니다 (하위 테이블 정의의 일부).

parent_id = db.Column(db.Integer, db.ForeignKey('parent.id', ondelete='CASCADE'))
parent = db.relationship('Parent', backref=backref('children', passive_deletes=True))

ondelete='CASCADE'를 생성하는 부분입니다 ON DELETE CASCADE테이블에.

잡았다!

여기에 중요한 경고가 있습니다. 내가 어떻게 relationship지정 했는지 주목하십시오 passive_deletes=True. 그것이 없으면 모든 것이 작동하지 않습니다. 이것은 기본적으로 부모 레코드를 삭제할 때 SqlAlchemy가 정말 이상한 일을하기 때문입니다. 모든 자식 행의 외래 키를로 설정합니다 NULL. 당신의 행 삭제한다면 parent_table어디에 id= 5, 그것은 기본적으로 실행됩니다

UPDATE child_table SET parent_id = NULL WHERE parent_id = 5

왜 당신이 이것을 원하는지 모르겠습니다. 많은 데이터베이스 엔진이 유효한 외래 키를로 설정 NULL하여 고아를 생성 하도록 허용했다면 놀랄 것 입니다. 나쁜 생각처럼 보이지만 아마도 사용 사례가있을 수 있습니다. 어쨌든 SqlAlchemy가이 작업을 수행하도록하면 데이터베이스가 ON DELETE CASCADE설정 한 을 사용하여 자식을 정리할 수 없게 됩니다. 이는 삭제할 하위 행을 알기 위해 해당 외래 키에 의존하기 때문입니다. SqlAlchemy가 모두로 설정 NULL하면 데이터베이스에서 삭제할 수 없습니다. 을 설정 passive_deletes=True하면 SqlAlchemy NULL가 외래 키를 출력 하지 못합니다 .

SqlAlchemy 문서 에서 수동 삭제에 대한 자세한 내용을 읽을 수 있습니다 .

옵션 2

당신이 할 수있는 다른 방법은 SqlAlchemy가 당신을 위해 그것을하도록하는 것입니다. 이것은의 cascade인수를 사용하여 설정됩니다 relationship. 상위 테이블에 정의 된 관계가있는 경우 다음과 같습니다.

children = relationship('Child', cascade='all,delete', backref='parent')

자녀와의 관계인 경우 다음과 같이합니다.

parent = relationship('Parent', backref=backref('children', cascade='all,delete'))

다시 말하지만, 이것은 자식이므로 호출 된 메서드를 호출 backref하고 거기에 캐스케이드 데이터를 넣어야합니다.

이를 통해 부모 행을 삭제할 때 SqlAlchemy는 실제로 자식 행을 정리하기 위해 delete 문을 실행합니다. 이 데이터베이스를 처리하는 것만 큼 효율적이지 않을 수 있으므로 권장하지 않습니다.

다음은 지원하는 계단식 기능에 대한 SqlAlchemy 문서 입니다.


답변

스티븐은 명시 적으로 역 참조를 생성해야한다는 점에서 정확합니다. 이로 인해 계단식이 부모에 적용됩니다 (테스트 시나리오에서와 같이 자식에 적용되는 것과 반대).

그러나 Child에 대한 관계를 정의한다고해서 sqlalchemy가 Child를 부모로 간주하지는 않습니다. 관계가 정의 된 위치 (하위 또는 상위), 상위 및 하위를 결정하는 두 테이블을 연결하는 외래 키는 중요하지 않습니다.

그러나 하나의 관습을 고수하는 것이 합리적이며 Steven의 응답에 따라 모든 자녀 관계를 부모에 대해 정의하고 있습니다.


답변

문서화도 힘들었지 만 독 스트링 자체가 매뉴얼보다 쉬운 경향이 있다는 것을 알았습니다. 예를 들어, sqlalchemy.orm에서 관계를 가져오고 help (relationship)을 수행하면 cascade에 대해 지정할 수있는 모든 옵션이 제공됩니다. 글 머리 기호 delete-orphan:

부모가없는 자식 유형의 항목이 감지되면 삭제 표시를합니다.
이 옵션은 부모가없는 상태에서 자식 클래스의 보류중인 항목이 유지되는 것을 방지합니다.

나는 당신의 문제가 부모-자녀 관계를 정의하는 문서 방식에 더 많은 것을 알고 있습니다. 그러나 "all"포함 하기 때문에 캐스케이드 옵션에 문제가있는 것 같습니다 "delete". "delete-orphan"에 포함되지 않은 유일한 옵션입니다 "all".


답변

스티븐의 대답은 확실합니다. 추가적인 의미를 지적하고 싶습니다.

를 사용 relationship하면 참조 무결성을 담당하는 앱 레이어 (Flask)를 만들 수 있습니다. 즉, 데이터베이스 유틸리티 또는 데이터베이스에 직접 연결하는 사람과 같이 Flask를 통하지 않고 데이터베이스에 액세스하는 다른 프로세스는 이러한 제약을 경험하지 않으며 설계하기 위해 열심히 작업 한 논리적 데이터 모델을 깨는 방식으로 데이터를 변경할 수 있습니다. .

가능하면 ForeignKeyd512와 Alex가 설명 하는 접근 방식을 사용하십시오 . DB 엔진은 (피할 수없는 방식으로) 제약을 진정으로 적용하는 데 매우 뛰어나므로 이것이 데이터 무결성을 유지하기위한 최고의 전략입니다. 데이터 무결성을 처리하기 위해 앱에 의존해야하는 유일한 경우는 데이터베이스가이를 처리 할 수없는 경우입니다 (예 : 외래 키를 지원하지 않는 SQLite 버전).

당신은 부모 – 자식 객체 관계, 사용, 탐색 등의 행동 응용 프로그램 수 있도록 개체 간의 추가 연결을 작성해야하는 경우 backref와 관련을 ForeignKey.


답변

Stevan의 답변은 완벽합니다. 그러나 여전히 오류가 발생하는 경우. 그 위에 다른 가능한 시도는-

http://vincentaudebert.github.io/python/sql/2015/10/09/cascade-delete-sqlalchemy/

링크에서 복사-

모델에서 계단식 삭제를 지정한 경우에도 외래 키 종속성에 문제가있는 경우 빠른 팁.

SQLAlchemy를 사용 cascade='all, delete'하여 상위 테이블에 있어야하는 계단식 삭제를 지정 합니다. 좋아, 그러면 다음과 같은 것을 실행할 때 :

session.query(models.yourmodule.YourParentTable).filter(conditions).delete()

실제로 자식 테이블에 사용 된 외래 키에 대한 오류를 트리거합니다.

객체를 쿼리 한 다음 삭제하는 데 사용한 솔루션 :

session = models.DBSession()
your_db_object = session.query(models.yourmodule.YourParentTable).filter(conditions).first()
if your_db_object is not None:
    session.delete(your_db_object)

이렇게하면 상위 레코드 및 이와 관련된 모든 하위 레코드가 삭제됩니다.