SQLAlchemy:级联删除
我必须用SQLAlchemy的级联选项丢失一些微不足道的东西,因为我不能得到一个简单的级联删除来正确操作 – 如果一个父元素被删除,那么这个子元素将持续存在,并且为null
外键。
我在这里提供了一个简明的testing用例:
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
父母与子女之间有一个简单的一对多关系。 脚本创build一个父项,添加3个子项,然后提交。 接下来,它删除父,但孩子坚持。 为什么? 我如何让孩子级联删除?
问题是sqlalchemy认为Child
是父母,因为那是你定义你的关系的地方(当然,你不会把它称为“孩子”)。
如果您在Parent
类上定义关系,它将起作用:
children = relationship("Child", cascade="all,delete", backref="parent")
(注意"Child"
是一个string:当使用声明式样式时,这是允许的,这样你就可以引用一个尚未定义的类)
您可能还想添加delete-orphan
( delete
会导致删除父项时删除子项,即使父项未删除, delete-orphan
也会删除从父项中“删除”的任何子项)
编辑:只是发现:如果你真的想定义的关系上的Child
类,你可以这样做,但你将不得不在backref (通过显式创buildbackref)定义级联,如下所示:
parent = relationship(Parent, backref=backref("children", cascade="all,delete"))
( from sqlalchemy.orm import backref
)
@通过session.delete()
删除,史蒂文的asnwer是好的,这是从来没有发生在我的情况。 我注意到大多数时候我通过session.query().filter().delete()
(它不会将元素放入内存并直接从db中删除)删除。 使用这个方法sqlalchemy的cascade='all, delete'
不起作用。 有一个解决scheme,虽然: 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)
漂亮的老post,但我只花了一两个小时,所以我想分享我的发现,特别是因为列出的其他评论不完全正确。
TL; DR
给子表添加一个外键或修改现有的表,添加onedelete='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))
细节
首先,尽pipe接受的答案是说,父/子关系不是通过使用relationship
build立的,而是通过使用ForeignKey
build立的。 您可以将relationship
放在父表或子表上,并且工作正常。 尽pipe显然在子表上,除了关键字参数之外,还必须使用backref
函数。
备选案文1(首选)
其次,SqlAlchemy支持两种不同types的级联。 第一个和我推荐的是内置到你的数据库中,通常采用对外键声明的约束forms。 在PostgreSQL中,它看起来像这样:
CONSTRAINT child_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES parent_table(id) MATCH SIMPLE ON DELETE CASCADE
这意味着当你从parent_table
删除一条logging时,那么数据库中的所有相应的child_table
将被删除。 这是快速和可靠的,可能是你最好的select。 你可以像这样通过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'
是在表上创buildON DELETE CASCADE
的部分。
疑难杂症!
这里有一个重要的警告。 请注意,我是如何与passive_deletes=True
指定relationship
? 如果你没有这个,整个事情都不行。 这是因为默认情况下,当你删除一个父logging时,SqlAlchemy的确有些奇怪。 它将所有子行的外键设置为NULL
。 所以如果你从id
= 5的parent_table
中删除一行,那么它基本上会执行
UPDATE child_table SET parent_id = NULL WHERE parent_id = 5
为什么你会想这个我不知道。 如果许多数据库引擎甚至允许你设置一个有效的外键为NULL
,创build一个孤儿,我会感到惊讶。 似乎是一个坏主意,但也许有一个用例。 无论如何,如果你让SqlAlchemy这样做,你将会阻止数据库使用你设置的ON DELETE CASCADE
清理子项。 这是因为它依靠这些外键来知道要删除哪些子行。 一旦SqlAlchemy将它们全部设置为NULL
,数据库就不能删除它们。 设置passive_deletes=True
可以防止SqlAlchemy将外键清空。
您可以在SqlAlchemy文档中阅读有关被动删除的更多信息。
选项2
另一种方法是让SqlAlchemy为你做。 这是使用relationship
的cascade
参数设置的。 如果你有在父表上定义的关系,它看起来像这样:
children = relationship('Child', cascade='all,delete', backref='parent')
如果这种关系是在孩子身上,那么你就这样做:
parent = relationship('Parent', backref=backref('children', cascade='all,delete'))
再次,这是孩子,所以你必须调用一个叫做backref
的方法,并把级联数据放在那里。
有了这个,当你删除父行时,SqlAlchemy会实际运行删除语句来清理子行。 这可能不会像让你的数据库那样高效,所以我不推荐它。
以下是关于它支持的级联function的SqlAlchemy文档 。
Steven是正确的,因为你需要显式地创buildbackref,这会导致在父级上应用级联(而不是像testing场景中那样将其应用于子级)。
然而,定义儿童的关系并不会使sqlalchemy将Child视为父母。 关系定义的位置(子或父)无关紧要,链接两个表的外键确定哪个是父级,哪个是子级。
尽pipe遵循一个惯例是有道理的,并且基于Steven的回答,我将所有的孩子关系定义在父母身上。
我也和文档一起努力,但发现文档本身比手册更容易。 例如,如果您从sqlalchemy.orm导入关系并执行帮助(关系),它将为您提供可以为级联指定的所有选项。 “delete-orphan”的子弹说:“如果一个没有父类的子types的项目被检测到,将其标记为删除。注意,这个选项可以防止子类的未决项目在没有父项的情况下被持久化。
我意识到你的问题更多的是用于定义亲子关系的文档。 但似乎你可能也会遇到级联选项的问题,因为“全部”包括“删除”。 “删除孤儿”是唯一不包含在“全部”中的选项。
史蒂文的答案是坚实的。 我想指出一个额外的含义。
通过使用relationship
,您可以使应用层(Flask)负责参照完整性。 这意味着不通过Flask访问数据库的其他进程(如数据库实用程序或直接连接到数据库的人员)将不会遇到这些限制,并且可能会改变数据,从而破坏您所devise的逻辑数据模型,从而难以devise。
只要有可能,就使用d512和Alex所描述的ForeignKey
方法。 数据库引擎非常善于以一种不可避免的方式强制实施约束,所以这是维持数据完整性的最佳策略。 唯一需要依靠应用程序来处理数据完整性的时候是数据库无法处理它们,例如不支持外键的SQLite版本。