SQLAlchemy 多对多 Postgresql 双重删除
SQLAlchemy many-to-many Postgresql double delete
我尝试删除数据,但是sqlalchemy尝试了两次,然后回滚
基础 Postgresql
版本 9.6
sqlalchemy 1.0.14
psycopg2 2.7.3.1
class IdentifiedObject(Base):
__tablename__ = 'identifiedobject'
mRID = Column(UUID, server_default=sqlalchemy.text("uuid_generate_v4()"), primary_key=True)
name = Column(String)
polymorphic_type = Column(String, nullable=False)
__mapper_args__={'polymorphic_identity':__tablename__,'polymorphic_on':polymorphic_type}
assoc_1 = Table("assoc_1", Base.metadata,
Column("cars_mRID", None, ForeignKey("cars.mRID")),
Column("games_mRID", None, ForeignKey("games.mRID")))
class Cars(IdentifiedObject):
__tablename__='cars'
mRID = Column(None, ForeignKey('identifiedobject.mRID'), primary_key=True)
polymorphic_type = Column(String, nullable=False)
__mapper_args__={'polymorphic_identity':__tablename__,'inherit_condition': mRID == IdentifiedObject.mRID,'polymorphic_on':polymorphic_type}
status = Column(String)
Games = relationship("Games", secondary = "assoc_1", back_populates="Cars", primaryjoin="(cars.c.mRID==assoc_1.c.cars_mRID)")
class Games(IdentifiedObject):
__tablename__='games'
mRID = Column(None, ForeignKey('identifiedobject.mRID'), primary_key=True)
polymorphic_type = Column(String, nullable=False)
__mapper_args__={'polymorphic_identity':__tablename__,'inherit_condition': mRID == IdentifiedObject.mRID,'polymorphic_on':polymorphic_type}
status = Column(String)
Cars = relationship("Cars", secondary = "assoc_1", back_populates="Games", primaryjoin="(games.c.mRID==assoc_1.c.games_mRID)")
Base.metadata.create_all(engine)
Base.prepare(engine, reflect=True)
session = Session(bind=engine)
session.add(IdentifiedObject())
games=Games(Cars=[Cars(),Cars()])
session.add (games)
session.commit()
session.close()
session.delete(games)
session.commit()
现在我们发现了一个例外
2017-09-29 09:17:44,996 INFO sqlalchemy.engine.base.Engine SELECT cars."mRID" AS "cars_mRID", identifiedobject."mRID" AS "identifiedobject_mRID", identifiedobject.name AS identifiedobject_name, cars.polymorphic_type AS cars_polymorphic_type, identifiedobject.polymorphic_type AS identifiedobject_polymorphic_type, cars.status AS cars_status
FROM assoc_1, identifiedobject JOIN cars ON cars."mRID" = identifiedobject."mRID"
WHERE %(param_1)s = assoc_1."games_mRID" AND cars."mRID" = assoc_1."cars_mRID"
2017-09-29 09:17:44,996 INFO sqlalchemy.engine.base.Engine {'param_1': UUID('7a960989-5e3e-45dc-87c1-1b62ffa3694a')}
2017-09-29 09:17:44,997 INFO sqlalchemy.engine.base.Engine DELETE FROM assoc_1 WHERE assoc_1."cars_mRID" = %(cars_mRID)s AND assoc_1."games_mRID" = %(games_mRID)s
2017-09-29 09:17:44,998 INFO sqlalchemy.engine.base.Engine ({'games_mRID': UUID('7a960989-5e3e-45dc-87c1-1b62ffa3694a'), 'cars_mRID': UUID('a3135561-e416-45c0-b9f8-aead59ef6b34')}, {'games_mRID': UUID('7a960989-5e3e-45dc-87c1-1b62ffa3694a'), 'cars_mRID': UUID('b77b9dc4-65da-45ea-be52-dc53e2bcd74b')})
2017-09-29 09:17:44,998 INFO sqlalchemy.engine.base.Engine DELETE FROM assoc_1 WHERE assoc_1."cars_mRID" = %(cars_mRID)s AND assoc_1."games_mRID" = %(games_mRID)s
2017-09-29 09:17:44,999 INFO sqlalchemy.engine.base.Engine ({'games_mRID': UUID('7a960989-5e3e-45dc-87c1-1b62ffa3694a'), 'cars_mRID': UUID('a3135561-e416-45c0-b9f8-aead59ef6b34')}, {'games_mRID': UUID('7a960989-5e3e-45dc-87c1-1b62ffa3694a'), 'cars_mRID': UUID('b77b9dc4-65da-45ea-be52-dc53e2bcd74b')})
2017-09-29 09:17:44,999 INFO sqlalchemy.engine.base.Engine ROLLBACK
Traceback (most recent call last):
File "testing.py", line 98, in <module>
session.commit()
异常数据
sqlalchemy.orm.exc.StaleDataError: DELETE statement on table 'assoc_1' expected to delete 2 row(s); Only 0 were matched.
没有找到其他答案
这是由 specifying parts of the Declarative classes explicitly 手动和对现有表使用自动映射器引起的。您手动定义的关系不使用自动映射器使用的默认命名约定,因此它创建了另一个版本的关系:
In [8]: list(inspect(Games).relationships)
Out[8]:
[<RelationshipProperty at 0x7f932dea8ac8; Cars>,
<RelationshipProperty at 0x7f932de6c2c8; cars_collection>]
因此,当您执行删除操作时,两种关系都会触发它们的级联,并且您会得到 2 个 DELETE 语句。文档中特别提到了覆盖关系:
Above, one of the more intricate details is that we illustrated overriding one of the relationship()
objects that automap would have created. To do this, we needed to make sure the names match up with what automap would normally generate, in that the relationship name would be User.address_collection
...
要解决此问题,要么恢复使用默认命名方案,要么 provide your own:
In [6]: def class_name_collection(base, local_cls, referred_cls, constraint):
...: return referred_cls.__name__
...:
然后
...: Base.prepare(engine, reflect=True,
...: name_for_collection_relationship=class_name_collection)
我尝试删除数据,但是sqlalchemy尝试了两次,然后回滚 基础 Postgresql 版本 9.6 sqlalchemy 1.0.14 psycopg2 2.7.3.1
class IdentifiedObject(Base):
__tablename__ = 'identifiedobject'
mRID = Column(UUID, server_default=sqlalchemy.text("uuid_generate_v4()"), primary_key=True)
name = Column(String)
polymorphic_type = Column(String, nullable=False)
__mapper_args__={'polymorphic_identity':__tablename__,'polymorphic_on':polymorphic_type}
assoc_1 = Table("assoc_1", Base.metadata,
Column("cars_mRID", None, ForeignKey("cars.mRID")),
Column("games_mRID", None, ForeignKey("games.mRID")))
class Cars(IdentifiedObject):
__tablename__='cars'
mRID = Column(None, ForeignKey('identifiedobject.mRID'), primary_key=True)
polymorphic_type = Column(String, nullable=False)
__mapper_args__={'polymorphic_identity':__tablename__,'inherit_condition': mRID == IdentifiedObject.mRID,'polymorphic_on':polymorphic_type}
status = Column(String)
Games = relationship("Games", secondary = "assoc_1", back_populates="Cars", primaryjoin="(cars.c.mRID==assoc_1.c.cars_mRID)")
class Games(IdentifiedObject):
__tablename__='games'
mRID = Column(None, ForeignKey('identifiedobject.mRID'), primary_key=True)
polymorphic_type = Column(String, nullable=False)
__mapper_args__={'polymorphic_identity':__tablename__,'inherit_condition': mRID == IdentifiedObject.mRID,'polymorphic_on':polymorphic_type}
status = Column(String)
Cars = relationship("Cars", secondary = "assoc_1", back_populates="Games", primaryjoin="(games.c.mRID==assoc_1.c.games_mRID)")
Base.metadata.create_all(engine)
Base.prepare(engine, reflect=True)
session = Session(bind=engine)
session.add(IdentifiedObject())
games=Games(Cars=[Cars(),Cars()])
session.add (games)
session.commit()
session.close()
session.delete(games)
session.commit()
现在我们发现了一个例外
2017-09-29 09:17:44,996 INFO sqlalchemy.engine.base.Engine SELECT cars."mRID" AS "cars_mRID", identifiedobject."mRID" AS "identifiedobject_mRID", identifiedobject.name AS identifiedobject_name, cars.polymorphic_type AS cars_polymorphic_type, identifiedobject.polymorphic_type AS identifiedobject_polymorphic_type, cars.status AS cars_status
FROM assoc_1, identifiedobject JOIN cars ON cars."mRID" = identifiedobject."mRID"
WHERE %(param_1)s = assoc_1."games_mRID" AND cars."mRID" = assoc_1."cars_mRID"
2017-09-29 09:17:44,996 INFO sqlalchemy.engine.base.Engine {'param_1': UUID('7a960989-5e3e-45dc-87c1-1b62ffa3694a')}
2017-09-29 09:17:44,997 INFO sqlalchemy.engine.base.Engine DELETE FROM assoc_1 WHERE assoc_1."cars_mRID" = %(cars_mRID)s AND assoc_1."games_mRID" = %(games_mRID)s
2017-09-29 09:17:44,998 INFO sqlalchemy.engine.base.Engine ({'games_mRID': UUID('7a960989-5e3e-45dc-87c1-1b62ffa3694a'), 'cars_mRID': UUID('a3135561-e416-45c0-b9f8-aead59ef6b34')}, {'games_mRID': UUID('7a960989-5e3e-45dc-87c1-1b62ffa3694a'), 'cars_mRID': UUID('b77b9dc4-65da-45ea-be52-dc53e2bcd74b')})
2017-09-29 09:17:44,998 INFO sqlalchemy.engine.base.Engine DELETE FROM assoc_1 WHERE assoc_1."cars_mRID" = %(cars_mRID)s AND assoc_1."games_mRID" = %(games_mRID)s
2017-09-29 09:17:44,999 INFO sqlalchemy.engine.base.Engine ({'games_mRID': UUID('7a960989-5e3e-45dc-87c1-1b62ffa3694a'), 'cars_mRID': UUID('a3135561-e416-45c0-b9f8-aead59ef6b34')}, {'games_mRID': UUID('7a960989-5e3e-45dc-87c1-1b62ffa3694a'), 'cars_mRID': UUID('b77b9dc4-65da-45ea-be52-dc53e2bcd74b')})
2017-09-29 09:17:44,999 INFO sqlalchemy.engine.base.Engine ROLLBACK
Traceback (most recent call last):
File "testing.py", line 98, in <module>
session.commit()
异常数据
sqlalchemy.orm.exc.StaleDataError: DELETE statement on table 'assoc_1' expected to delete 2 row(s); Only 0 were matched.
没有找到其他答案
这是由 specifying parts of the Declarative classes explicitly 手动和对现有表使用自动映射器引起的。您手动定义的关系不使用自动映射器使用的默认命名约定,因此它创建了另一个版本的关系:
In [8]: list(inspect(Games).relationships)
Out[8]:
[<RelationshipProperty at 0x7f932dea8ac8; Cars>,
<RelationshipProperty at 0x7f932de6c2c8; cars_collection>]
因此,当您执行删除操作时,两种关系都会触发它们的级联,并且您会得到 2 个 DELETE 语句。文档中特别提到了覆盖关系:
Above, one of the more intricate details is that we illustrated overriding one of the
relationship()
objects that automap would have created. To do this, we needed to make sure the names match up with what automap would normally generate, in that the relationship name would beUser.address_collection
...
要解决此问题,要么恢复使用默认命名方案,要么 provide your own:
In [6]: def class_name_collection(base, local_cls, referred_cls, constraint):
...: return referred_cls.__name__
...:
然后
...: Base.prepare(engine, reflect=True,
...: name_for_collection_relationship=class_name_collection)