【问题标题】:SQLAlchemy Joined Inheritance fast bulk deletion of Child objectsSQLAlchemy Joined Inheritance 快速批量删除子对象
【发布时间】:2017-11-13 06:33:11
【问题描述】:

考虑以下使用联合继承的 SQLAlchemy 映射:

from sqlalchemy import sa

class Location(Base):
    id = Column(Integer, primary_key=True)
    name = sa.Column(sa.String)
    type_ = sa.column(sa.String)

    __tablename__ = 'location'
    __mapper_args__ = {
        'polymorphic_identity': 'location',
        'polymorphic_on': type_,
    }

class Field(Location):
    id = Column(Integer, primary_key=True)
    size = sa.Column(sa.Float)

    __tablename__ = 'field'
    __mapper_args__ = {
        'polymorphic_identity': 'field',
    }
    __table_args__ = (
        sa.ForeignKeyConstraint(['id'], ['location.id']),
    )


session.query(Field).filter(Field.size < 5).delete()

其中 base 是一个适当的声明基础, session 是一个适当的会话对象。上面的实现将导致 Field 对象被删除而不删除父 Location 对象(正如文档清楚地解释,query.delete() 不支持继承)。我可以通过 session.delete(obj) 来解决这个问题,它使用 ORM 删除链上的对象。但是,这会导致在数据库上执行 n 个 SQL 删除语句(其中 n 是要删除的对象数)。我有一个案例,我可能一次删除大约 100,000 个子对象,所以这个操作非常慢(假设现在我不可能使用 ORM 和加入继承 - 我太深了,无法更改)。

SQLAlchemy 中是否有任何构造或合理的替代方法,可以让我传递一个查询对象,该查询对象查询 Field 类型的对象并适当地删除 Location 表中的项目,而无需制作 n SQL 删除语句?

注意,我目前使用的是 PostgreSQL,但想保留解决方案 db-agnostic。

编辑:根据请求添加了表元数据和有关环境的更多信息。

【问题讨论】:

  • 请包括您使用的数据库以及主键和外键定义。您可以为此使用 Core。

标签: python inheritance sqlalchemy


【解决方案1】:

尝试了一两个小时后,我找到了一个没有太多代码的解决方案。那我就复现一下吧。

1.我查看delete()的文档。有两句话:

此方法不适用于连接继承映射,因为 SQL 不支持删除多个表,并且继承映射器的连接条件不会自动呈现

但是上面的 SQL 不会从 Engineer 表中删除,除非在数据库中建立了 ON DELETE CASCADE 规则来处理它。

简而言之,不要将此方法用于连接继承映射,除非您已采取其他步骤使其可行。

所以,定义一个外键约束是很有必要的。像这样:

class Location(Base):
    __tablename__ = 'location'
    id = Column(INTEGER, primary_key=True)
    name = Column(VARCHAR(30))
    type = Column(VARCHAR(30))

    __mapper_args__ = {
        'polymorphic_identity': 'location',
        'polymorphic_on'      : type,
    }

class Field(Location):
    __tablename__ = 'field'
    id = Column(INTEGER, ForeignKey('location.id', ondelete='cascade'), primary_key=True)
    size = Column(DECIMAL(20, 2))

    __mapper_args__ = {
        'polymorphic_identity': 'field',
    }

2.现在,如果我们删除LocationField 中的行也会被删除。

session.query(Location).filter(Location.id == 1).delete()

3. 但是,发帖人想删除Field 而不是Location

session.query(Field).filter(Field.size < 5).delete()

这只会删除Field 中的行,而不会删除Location 中的行。因为Field是外表,所以不能级联主表。

所以,现在我们应该做的是根据Field.size &lt; 5Location中删除。

我试过了

session.query(Location).filter(Field.size < 5).delete()

session.query(Location).outerjoin(Field, Location.id == Field.id).filter(Field.size < 5).delete()

这两个都抛出异常。

经过多次尝试,我找到的解决方案是这样的:

statment = delete(Field, prefixes=[Location.__tablename__]).where(Field.size == 1)
session.execute(statment)

生成的sql为DELETE location FROM location JOIN field ON location.id = field.id WHERE field.size &lt; 5

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-17
    • 2023-01-26
    • 2018-12-27
    • 2018-05-24
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多