SQLAlchemy 尝试两次删除多对多二级关系 PostgreSQL
SQLAlchemy Attempting to Twice Delete Many to Many Secondary Relationship PostgreSQL
中的相同问题报告。
但已接受的答案无效。
模型结构为
class Product(Base):
""" The SQLAlchemy declarative model class for a Product object. """
__tablename__ = 'products'
id = Column(Integer, primary_key=True)
part_number = Column(String(10), nullable=False, unique=True)
name = Column(String(80), nullable=False, unique=True)
description = Column(String(2000), nullable=False)
categories = relationship('Category', secondary=product_categories,
backref=backref('categories', lazy='dynamic'))
class Category(Base):
""" The SQLAlchemy declarative model class for a Category object. """
__tablename__ = 'categories'
id = Column(Integer, primary_key=True)
lft = Column(Integer, nullable=False)
rgt = Column(Integer, nullable=False)
name = Column(String(80), nullable=False)
description = Column(String(2000), nullable=False)
order = Column(Integer)
products = relationship('Product', secondary=product_categories,
backref=backref('products', lazy='dynamic', order_by=name))
product_categories = Table('product_categories', Base.metadata,
Column('products_id', Integer, ForeignKey('products.id')),
Column('categories_id', Integer, ForeignKey('categories.id'))
)
问题是当尝试删除 Product 实例时,SQLAlchemy returns 出错
Traceback (most recent call last):
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_debugtoolbar-2.3-py2.7.egg/pyramid_debugtoolbar/toolbar.py", line 178, in toolbar_tween
response = _handler(request)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_debugtoolbar-2.3-py2.7.egg/pyramid_debugtoolbar/panels/performance.py", line 57, in resource_timer_handler
result = handler(request)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid/tweens.py", line 21, in excview_tween
response = handler(request)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_tm-0.10-py2.7.egg/pyramid_tm/__init__.py", line 95, in tm_tween
reraise(*exc_info)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_tm-0.10-py2.7.egg/pyramid_tm/__init__.py", line 83, in tm_tween
manager.commit()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_manager.py", line 111, in commit
return self.get().commit()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 280, in commit
reraise(t, v, tb)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 271, in commit
self._commitResources()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 417, in _commitResources
reraise(t, v, tb)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 389, in _commitResources
rm.tpc_begin(self)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/zope.sqlalchemy-0.7.5-py2.7.egg/zope/sqlalchemy/datamanager.py", line 90, in tpc_begin
self.session.flush()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/session.py", line 1919, in flush
self._flush(objects)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/session.py", line 2037, in _flush
transaction.rollback(_capture_exception=True)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/util/langhelpers.py", line 60, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/session.py", line 2001, in _flush
flush_context.execute()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/unitofwork.py", line 372, in execute
rec.execute(self)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/unitofwork.py", line 479, in execute
self.dependency_processor.process_deletes(uow, states)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/dependency.py", line 1023, in process_deletes
secondary_update, secondary_delete)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/dependency.py", line 1111, in _run_crud
result.rowcount)
StaleDataError: DELETE statement on table 'product_categories' expected to delete 1 row(s); Only 0 were matched.
数据库是 Postgres,当我从一个模型中删除 backref 时,没有问题,但我需要在两个模型上使用 backref。
设置 supports_sane_rowcount 不工作 用于 postgres
engine.dialect.supports_sane_rowcount = engine.dialect.supports_sane_multi_rowcount = False
有什么可行的解决方案吗?
当我使用你的 类 时,我收到类似
的警告
SAWarning: relationship 'Product.products' will copy column products.id to column product_categories.products_id, which conflicts with relationship(s): 'Category.categories' (copies products.id to product_categories.products_id), 'Product.categories' (copies products.id to product_categories.products_id). If this is not the intention, consider if these relationships should be linked with back_populates, or if viewonly=True should be applied to one or more if they are read-only. For the less common case that foreign key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate the columns that should be written towards. To silence this warning, add the parameter 'overlaps="categories,categories"' to the 'Product.products' relationship. (Background on this error at: https://sqlalche.me/e/14/qzyx)
如果我使用 back_populates
而不是 backref
,问题就会消失:
class Product(Base):
# …
categories = relationship(
"Category",
secondary=product_categories,
back_populates="products",
lazy="dynamic",
order_by = "Category.name",
)
class Category(Base):
# …
products = relationship(
"Product",
secondary=product_categories,
back_populates="categories",
lazy="dynamic",
order_by="Product.name",
)
完整的工作示例:
from sqlalchemy import (
create_engine,
Column,
Integer,
String,
Table,
ForeignKey,
select,
)
from sqlalchemy.orm import declarative_base, relationship, Session
engine = create_engine(
"postgresql://scott:tiger@192.168.0.199/test",
)
Base = declarative_base()
product_categories = Table(
"product_categories",
Base.metadata,
Column("products_id", Integer, ForeignKey("products.id")),
Column("categories_id", Integer, ForeignKey("categories.id")),
)
class Product(Base):
__tablename__ = "products"
id = Column(Integer, primary_key=True)
part_number = Column(String(10), nullable=False, unique=True)
name = Column(String(80), nullable=False, unique=True)
description = Column(String(2000), nullable=False)
categories = relationship(
"Category",
secondary=product_categories,
back_populates="products",
lazy="dynamic",
order_by = "Category.name",
)
class Category(Base):
__tablename__ = "categories"
id = Column(Integer, primary_key=True)
lft = Column(Integer, nullable=False)
rgt = Column(Integer, nullable=False)
name = Column(String(80), nullable=False)
description = Column(String(2000), nullable=False)
order = Column(Integer)
products = relationship(
"Product",
secondary=product_categories,
back_populates="categories",
lazy="dynamic",
order_by="Product.name",
)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
# setup
with Session(engine) as sess:
category_a = Category(
lft=0, rgt=0, name="category_a", description="category_a"
)
product_a = Product(
part_number="product_a",
name="product_a",
description="product_a",
categories=[category_a],
)
sess.add_all([category_a, product_a])
sess.commit()
# test
engine.echo = True
with Session(engine) as sess:
p = sess.execute(
select(Product).where(Product.part_number == "product_a")
).scalar()
sess.delete(p)
sess.commit()
"""SQL emitted:
DELETE FROM product_categories WHERE product_categories.products_id = %(products_id)s AND product_categories.categories_id = %(categories_id)s
[generated in 0.00074s] {'products_id': 1, 'categories_id': 1}
DELETE FROM products WHERE products.id = %(id)s
[generated in 0.00069s] {'id': 1}
COMMIT
"""
但已接受的答案无效。
模型结构为
class Product(Base):
""" The SQLAlchemy declarative model class for a Product object. """
__tablename__ = 'products'
id = Column(Integer, primary_key=True)
part_number = Column(String(10), nullable=False, unique=True)
name = Column(String(80), nullable=False, unique=True)
description = Column(String(2000), nullable=False)
categories = relationship('Category', secondary=product_categories,
backref=backref('categories', lazy='dynamic'))
class Category(Base):
""" The SQLAlchemy declarative model class for a Category object. """
__tablename__ = 'categories'
id = Column(Integer, primary_key=True)
lft = Column(Integer, nullable=False)
rgt = Column(Integer, nullable=False)
name = Column(String(80), nullable=False)
description = Column(String(2000), nullable=False)
order = Column(Integer)
products = relationship('Product', secondary=product_categories,
backref=backref('products', lazy='dynamic', order_by=name))
product_categories = Table('product_categories', Base.metadata,
Column('products_id', Integer, ForeignKey('products.id')),
Column('categories_id', Integer, ForeignKey('categories.id'))
)
问题是当尝试删除 Product 实例时,SQLAlchemy returns 出错
Traceback (most recent call last):
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_debugtoolbar-2.3-py2.7.egg/pyramid_debugtoolbar/toolbar.py", line 178, in toolbar_tween
response = _handler(request)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_debugtoolbar-2.3-py2.7.egg/pyramid_debugtoolbar/panels/performance.py", line 57, in resource_timer_handler
result = handler(request)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid/tweens.py", line 21, in excview_tween
response = handler(request)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_tm-0.10-py2.7.egg/pyramid_tm/__init__.py", line 95, in tm_tween
reraise(*exc_info)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_tm-0.10-py2.7.egg/pyramid_tm/__init__.py", line 83, in tm_tween
manager.commit()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_manager.py", line 111, in commit
return self.get().commit()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 280, in commit
reraise(t, v, tb)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 271, in commit
self._commitResources()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 417, in _commitResources
reraise(t, v, tb)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 389, in _commitResources
rm.tpc_begin(self)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/zope.sqlalchemy-0.7.5-py2.7.egg/zope/sqlalchemy/datamanager.py", line 90, in tpc_begin
self.session.flush()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/session.py", line 1919, in flush
self._flush(objects)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/session.py", line 2037, in _flush
transaction.rollback(_capture_exception=True)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/util/langhelpers.py", line 60, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/session.py", line 2001, in _flush
flush_context.execute()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/unitofwork.py", line 372, in execute
rec.execute(self)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/unitofwork.py", line 479, in execute
self.dependency_processor.process_deletes(uow, states)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/dependency.py", line 1023, in process_deletes
secondary_update, secondary_delete)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/dependency.py", line 1111, in _run_crud
result.rowcount)
StaleDataError: DELETE statement on table 'product_categories' expected to delete 1 row(s); Only 0 were matched.
数据库是 Postgres,当我从一个模型中删除 backref 时,没有问题,但我需要在两个模型上使用 backref。
设置 supports_sane_rowcount 不工作 用于 postgres
engine.dialect.supports_sane_rowcount = engine.dialect.supports_sane_multi_rowcount = False
有什么可行的解决方案吗?
当我使用你的 类 时,我收到类似
的警告SAWarning: relationship 'Product.products' will copy column products.id to column product_categories.products_id, which conflicts with relationship(s): 'Category.categories' (copies products.id to product_categories.products_id), 'Product.categories' (copies products.id to product_categories.products_id). If this is not the intention, consider if these relationships should be linked with back_populates, or if viewonly=True should be applied to one or more if they are read-only. For the less common case that foreign key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate the columns that should be written towards. To silence this warning, add the parameter 'overlaps="categories,categories"' to the 'Product.products' relationship. (Background on this error at: https://sqlalche.me/e/14/qzyx)
如果我使用 back_populates
而不是 backref
,问题就会消失:
class Product(Base):
# …
categories = relationship(
"Category",
secondary=product_categories,
back_populates="products",
lazy="dynamic",
order_by = "Category.name",
)
class Category(Base):
# …
products = relationship(
"Product",
secondary=product_categories,
back_populates="categories",
lazy="dynamic",
order_by="Product.name",
)
完整的工作示例:
from sqlalchemy import (
create_engine,
Column,
Integer,
String,
Table,
ForeignKey,
select,
)
from sqlalchemy.orm import declarative_base, relationship, Session
engine = create_engine(
"postgresql://scott:tiger@192.168.0.199/test",
)
Base = declarative_base()
product_categories = Table(
"product_categories",
Base.metadata,
Column("products_id", Integer, ForeignKey("products.id")),
Column("categories_id", Integer, ForeignKey("categories.id")),
)
class Product(Base):
__tablename__ = "products"
id = Column(Integer, primary_key=True)
part_number = Column(String(10), nullable=False, unique=True)
name = Column(String(80), nullable=False, unique=True)
description = Column(String(2000), nullable=False)
categories = relationship(
"Category",
secondary=product_categories,
back_populates="products",
lazy="dynamic",
order_by = "Category.name",
)
class Category(Base):
__tablename__ = "categories"
id = Column(Integer, primary_key=True)
lft = Column(Integer, nullable=False)
rgt = Column(Integer, nullable=False)
name = Column(String(80), nullable=False)
description = Column(String(2000), nullable=False)
order = Column(Integer)
products = relationship(
"Product",
secondary=product_categories,
back_populates="categories",
lazy="dynamic",
order_by="Product.name",
)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
# setup
with Session(engine) as sess:
category_a = Category(
lft=0, rgt=0, name="category_a", description="category_a"
)
product_a = Product(
part_number="product_a",
name="product_a",
description="product_a",
categories=[category_a],
)
sess.add_all([category_a, product_a])
sess.commit()
# test
engine.echo = True
with Session(engine) as sess:
p = sess.execute(
select(Product).where(Product.part_number == "product_a")
).scalar()
sess.delete(p)
sess.commit()
"""SQL emitted:
DELETE FROM product_categories WHERE product_categories.products_id = %(products_id)s AND product_categories.categories_id = %(categories_id)s
[generated in 0.00074s] {'products_id': 1, 'categories_id': 1}
DELETE FROM products WHERE products.id = %(id)s
[generated in 0.00069s] {'id': 1}
COMMIT
"""