<질문>
간단한 계단식 삭제를 올바르게 작동할 수 없기 때문에 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명의 자식을 추가한 다음 커밋합니다. 다음으로 부모는 삭제되지만 자식은 유지됩니다. 왜요? 자식을 계단식으로 삭제하려면 어떻게 해야 합니까?
<답변1>
문제는 sqlalchemy가Child
그것이 당신이 당신의 관계를 정의한 곳이기 때문입니다(당신이 그것을 "자식"이라고 부르는 것은 상관없습니다).
관계를 정의하면Parent
대신 클래스가 작동합니다.
children = relationship("Child", cascade="all,delete", backref="parent")
(노트"Child"
as string: 선언적 스타일을 사용할 때 허용되므로 아직 정의되지 않은 클래스를 참조할 수 있습니다.
추가하고 싶을 수도 있습니다.delete-orphan
또한 (delete
부모가 삭제되면 자식도 삭제됩니다.delete-orphan
상위 항목이 삭제되지 않은 경우에도 상위 항목에서 "제거된" 모든 하위 항목도 삭제됨)
편집: 방금 알아냈습니다: 만약 당신이진짜에 대한 관계를 정의하고 싶습니다.Child
클래스에서는 그렇게 할 수 있지만 캐스케이드를 정의해야 합니다.역 참조에(명시적으로 backref를 생성함으로써), 다음과 같이:
parent = relationship(Parent, backref=backref("children", cascade="all,delete"))
(을(를) 암시하는from sqlalchemy.orm import backref
)
<답변2>
@Steven의 asnwer는 당신이 통해 삭제할 때 좋습니다.session.delete()
내 경우에는 절대 발생하지 않습니다. 나는 대부분의 시간을 통해 삭제한다는 것을 알았습니다.session.query().filter().delete()
(메모리에 요소를 넣지 않고 db에서 직접 삭제). 이 방법을 사용하여 sqlalchemy의cascade='all, delete'
작동하지 않습니다. 하지만 해결책이 있습니다.ON DELETE CASCADE
db를 통해(참고: 모든 데이터베이스가 지원하지 않음).
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)
<답변3>
꽤 오래된 게시물이지만 이것에 대해 1-2 시간을 보냈습니다. 그래서 특히 나열된 다른 의견 중 일부가 정확하지 않기 때문에 내 발견을 공유하고 싶었습니다.
자식 테이블을 외래 테이블로 지정하거나 기존 테이블을 수정하여 다음을 추가합니다.ondelete='CASCADE'
:
parent_id = db.Column(db.Integer, db.ForeignKey('parent.id', ondelete='CASCADE'))
그리고하나다음 관계 중:
a) 이것은 부모 테이블에서:
children = db.relationship('Child', backref='parent', passive_deletes=True)
비)또는이것은 자식 테이블에서 :
parent = db.relationship('Parent', backref=backref('children', passive_deletes=True))
첫째, 허용 된 답변이 말했음에도 불구하고 부모 / 자녀 관계는 다음을 사용하여 설정되지 않습니다.relationship
, 사용하여 설정됩니다.ForeignKey
. 당신은 넣을 수 있습니다relationship
부모 또는 자식 테이블에서 잘 작동합니다. 분명히 자식 테이블에 있지만backref
키워드 인수 외에 기능.
둘째, SqlAlchemy는 두 가지 다른 종류의 계단식 배열을 지원합니다. 첫 번째, 그리고 내가 권장하는 것은 데이터베이스에 내장되어 있으며 일반적으로 외래 키 선언에 대한 제약 조건의 형태를 취합니다. PostgreSQL에서는 다음과 같이 보입니다.
CONSTRAINT child_parent_id_fkey FOREIGN KEY (parent_id)
REFERENCES parent_table(id) MATCH SIMPLE
ON DELETE CASCADE
즉, 다음에서 레코드를 삭제할 때parent_table
, 다음의 모든 해당 행child_table
데이터베이스에서 삭제됩니다. 빠르고 안정적이며 아마도 최선의 선택일 것입니다. 다음을 통해 SqlAlchemy에서 설정합니다.ForeignKey
다음과 같이(자식 테이블 정의의 일부):
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 docs.
당신이 할 수 있는 다른 방법은 SqlAlchemy가 당신을 위해 그것을 하도록 하는 것입니다. 이것은 다음을 사용하여 설정됩니다.cascade
의 주장relationship
. 상위 테이블에 정의된 관계가 있는 경우 다음과 같습니다.
children = relationship('Child', cascade='all,delete', backref='parent')
자녀와 관계가 있는 경우 다음과 같이 하십시오.
parent = relationship('Parent', backref=backref('children', cascade='all,delete'))
다시 말하지만 이것은 자식이므로 다음과 같은 메서드를 호출해야 합니다.backref
캐스케이드 데이터를 거기에 넣습니다.
이것을 설정하면 부모 행을 삭제할 때 SqlAlchemy는 실제로 자식 행을 정리하기 위해 삭제 문을 실행합니다. 이것은 당신을 위해 이 데이터베이스를 처리하도록 하는 것만큼 효율적이지 않을 것이므로 권장하지 않습니다.
여기SqlAlchemy docs지원하는 계단식 기능.
<답변4>
Alex Okrushko 답변이 거의 저에게 가장 잘 맞았습니다. ondelete='CASCADE' 및 passive_deletes=True 결합 사용. 그러나 sqlite에서 작동하도록 추가 작업을 수행해야 했습니다.
Base = declarative_base()
ROOM_TABLE = "roomdata"
FURNITURE_TABLE = "furnituredata"
class DBFurniture(Base):
__tablename__ = FURNITURE_TABLE
id = Column(Integer, primary_key=True)
room_id = Column(Integer, ForeignKey('roomdata.id', ondelete='CASCADE'))
class DBRoom(Base):
__tablename__ = ROOM_TABLE
id = Column(Integer, primary_key=True)
furniture = relationship("DBFurniture", backref="room", passive_deletes=True)
이 코드를 추가하여 sqlite에서 작동하는지 확인하십시오.
from sqlalchemy import event
from sqlalchemy.engine import Engine
from sqlite3 import Connection as SQLite3Connection
@event.listens_for(Engine, "connect")
def _set_sqlite_pragma(dbapi_connection, connection_record):
if isinstance(dbapi_connection, SQLite3Connection):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON;")
cursor.close()
여기에서 도난:SQLAlchemy expression language and SQLite's on delete cascade
<답변5>
Steven은 명시적으로 backref를 생성해야 한다는 점에서 정확합니다. 이는 결과적으로 상위에 캐스케이드가 적용되는 결과를 낳습니다(테스트 시나리오에서와 같이 하위에 적용되는 것과 반대).
그러나 Child에 대한 관계를 정의한다고 해서 sqlalchemy가 Child를 부모로 간주하게 만들지는 않습니다. 관계가 정의된 위치(하위 또는 상위)는 중요하지 않으며, 어느 것이 부모이고 어느 것이 자식인지를 결정하는 두 테이블을 연결하는 외래 키입니다.
그러나 하나의 규칙을 고수하는 것이 합리적이며 Steven의 응답에 따라 부모에 대한 모든 자녀 관계를 정의하고 있습니다.
<답변6>
스티븐의 대답은 확고하다. 추가 의미를 지적하고 싶습니다.
사용하여relationship
, 참조 무결성을 담당하는 앱 계층(Flask)을 만들고 있습니다. 즉, 데이터베이스 유틸리티나 데이터베이스에 직접 연결하는 사람과 같이 Flask를 통하지 않고 데이터베이스에 액세스하는 다른 프로세스는 이러한 제약을 경험하지 않으며 설계에 열심히 노력한 논리적 데이터 모델을 깨뜨리는 방식으로 데이터를 변경할 수 있습니다. .
가능하면 언제든지 사용ForeignKey
d512와 Alex가 설명한 접근 방식. DB 엔진은 (피할 수 없는 방식으로) 제약 조건을 실제로 적용하는 데 매우 능숙하므로 데이터 무결성을 유지하기 위한 단연 최고의 전략입니다. 데이터 무결성을 처리하기 위해 앱에 의존해야 하는 유일한 경우는 데이터베이스가 이를 처리할 수 없는 경우입니다(예: 외래 키를 지원하지 않는 SQLite 버전).
상위-하위 개체 관계 탐색과 같은 앱 동작을 활성화하기 위해 엔터티 간에 추가 연결을 생성해야 하는 경우 다음을 사용합니다.backref
와 함께ForeignKey
.
<답변7>
나는 문서화에도 어려움을 겪었지만 독스트링 자체가 매뉴얼보다 쉬운 경향이 있음을 발견했습니다. 예를 들어, sqlalchemy.orm에서 관계를 가져오고 help(relationship)를 수행하면 캐스케이드에 대해 지정할 수 있는 모든 옵션이 제공됩니다. 총알delete-orphan
말한다:
부모가 없는 자식 유형의 항목이 감지되면 삭제 표시를 합니다. 이 옵션은 자식 클래스의 보류 중인 항목이 부모 없이 유지되는 것을 방지합니다.
귀하의 문제는 부모-자식 관계를 정의하는 문서 방식에 더 가깝다는 것을 알고 있습니다. 그러나 계단식 옵션에도 문제가 있는 것 같았습니다."all"
포함"delete"
."delete-orphan"
에 포함되지 않은 유일한 옵션입니다."all"
.
<답변8>
이 질문은 매우 오래되었지만 Google에서 검색할 때 가장 먼저 표시되므로 다른 사람들이 말한 내용에 추가하기 위해 내 솔루션을 게시할 것입니다(여기에 있는 모든 답변을 읽은 후에도 몇 시간을 보냈습니다).
d512에서 설명했듯이 외래 키에 관한 것입니다. 나에게는 꽤 놀랐지만 모든 데이터베이스/엔진이 외래 키를 지원하는 것은 아닙니다. MySQL 데이터베이스를 실행하고 있습니다. 오랜 조사 끝에 새 테이블을 만들 때 기본적으로 외래 키를 지원하지 않는 엔진(MyISAM)으로 설정된다는 사실을 알게 되었습니다. 내가해야 할 일은 다음을 추가하여 InnoDB로 설정하는 것이 었습니다.mysql_engine='InnoDB'
테이블을 정의할 때 내 프로젝트에서 명령형 매핑을 사용하고 있으며 다음과 같습니다.
db.Table('child',
Column('id', Integer, primary_key=True),
# other columns
Column('parent_id',
ForeignKey('parent.id', ondelete="CASCADE")),
mysql_engine='InnoDB')
<답변9>
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)
이렇게 하면 부모 레코드와 그와 관련된 모든 자식이 삭제됩니다.
<답변10>
TLDR:위의 솔루션이 작동하지 않으면 열에 nullable=False를 추가해 보십시오.
캐스케이드 기능이 기존 솔루션(훌륭함)과 함께 작동하지 않을 수 있는 일부 사람들을 위해 여기에 작은 요점을 추가하고 싶습니다. 내 작업과 예제의 주요 차이점은 automap을 사용했다는 것입니다. 그것이 어떻게 캐스케이드 설정을 방해할 수 있는지 정확히 알지 못하지만, 나는 그것을 사용했다는 점에 주목하고 싶습니다. 또한 SQLite 데이터베이스로 작업하고 있습니다.
여기에 설명된 모든 솔루션을 시도했지만 부모 행이 삭제되었을 때 자식 테이블의 행은 계속해서 외래 키를 null로 설정했습니다. 나는 여기에서 모든 솔루션을 시도했지만 아무 소용이 없었습니다. 그러나 외래 키가 있는 자식 열을 nullable = False로 설정하면 캐스케이드가 작동했습니다.
자식 테이블에 다음을 추가했습니다.
Column('parent_id', Integer(), ForeignKey('parent.id', ondelete="CASCADE"), nullable=False)
Child.parent = relationship("parent", backref=backref("children", passive_deletes=True)
이 설정으로 캐스케이드가 예상대로 작동했습니다.
'개발 > Python' 카테고리의 다른 글
[파이썬] 모듈의 별칭을 정의할 수 있습니까? (0) | 2022.10.23 |
---|---|
[파이썬] numpy 배열에 단일 요소 추가 (0) | 2022.10.23 |
[파이썬] SQLAlchemy - cascade 삭제 (0) | 2022.10.22 |
[파이썬] 문자열 split 첫 번째 것만 분할 (0) | 2022.10.22 |