SQLAlchemy已加入继承快速批量删除Child对象

时间:2017-11-10 06:13:38

标签: python inheritance sqlalchemy

考虑使用联接继承的以下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()不支持继承)。我可以通过使用ORM删除链上的对象session.delete(obj)来解决这个问题。但是,这会导致 n SQL删除语句在数据库上执行(其中 n 是要删除的对象数)。我有一个案例,我可能一次删除100,000个子对象的顺序,所以这个操作非常慢(假设我现在不可能使用ORM加入继承 - 我太深了,无法改变这一点。)

SQLAlchemy中是否有任何构造或合理的替代方法允许我传递查询类型为Field的对象的查询对象,并适当删除Location表中的项目而不进行< strong> n SQL删除语句?

注意,我目前正在使用PostgreSQL,但是希望保持解决方案与数据库无关。

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

1 个答案:

答案 0 :(得分:0)

尝试一两个小时后,我找到了一个没有太多代码的解决方案。然后我会重现它。

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。现在,如果我们删除Location Field中的行也会被删除。

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

3。但是,海报希望删除Field而不是Location

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

这仅删除Field中没有Location行的Field行。因为Location是外表,所以它不能级联主表。

所以,现在我们应该根据Field.size < 5session.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)

这些都会引发异常。

经过多次尝试,我发现的解决方案是:

DELETE location FROM location JOIN field ON location.id = field.id WHERE field.size < 5

生成的sql是{{1}}