SQLAlchemy:如何关闭声明式多态连接?

4
在SQLAlchemy中,是否有一种方法可以在单个查询中关闭声明式的多态联接加载?大多数情况下,它很好用,但我有以下问题:
class A(Base) : 
   discriminator = Column('type', mysql.INTEGER(1), index=True, nullable=False)
   __mapper_args__ = { 'polymorphic_on' : discriminator }
   id = Column(Integer, primary_key=True)
   p = Column(Integer)

class B(A) : 
   __mapper_args__ = { 'polymorphic_identity' : 0 }
   id = Column(Integer, primary_key=True)
   x = Column(Integer)

class C(A) : 
   __mapper_args__ = { 'polymorphic_identity' : 1 }
   id = Column(Integer, primary_key=True)
   y = Column(String)

我希望查询所有B.x大于10的A.id,如果这个A实际上是B,或者C.y等于'blah',如果这个A实际上是C,所有结果按照p排序。

为了迭代解决问题,我先从第一部分开始 - “查询所有B.x大于10的A.id,如果这个A实际上是B”。所以我想从一个外连接开始:

session.query(A.id).outerjoin((B, B.id == A.id)).filter(B.x > 10)

除非使用outerjoin((B, B.id == A.id)),否则没办法避免在子查询中生成A和B中所有内容的全连接。如果B没有继承自A,则不会发生这种情况,因此我认为是多态声明性代码生成导致了这种情况。有没有方法可以关闭它?或者强制outerjoin执行我想要的操作?
我想要的是这样的东西:
select a.id from A a left outer join B b on b.id == a.id where b.x > 10

但是我得到的却是这样的东西:
select a.id from A a left outer join (select B.id, B.x, A.id from B inner join A on B.id == A.id)

作为一个旁注,如果不可能实现这样的方式,那么后者是否比前者效率更低? SQL引擎是否会实际执行内部连接,还是会忽略它?
2个回答

1

你应该使用with_polymorphic()而不是outerjoin(),因为它似乎可以返回预期的结果:

session.query(A).with_polymorphic(B).filter(B.x > 10).all()
# BEGIN
# SELECT "A".type AS "A_type", "A".id AS "A_id", "A".p AS "A_p", "B".id AS "B_id", "B".x AS "B_x" 
# FROM "A" LEFT OUTER JOIN "B" ON "A".id = "B".id 
# WHERE "B".x > ?
# (10,)
# Col ('A_type', 'A_id', 'A_p', 'B_id', 'B_x')

相对于:

session.query(A.id).outerjoin((B, B.id == A.id)).filter(B.x > 10)
# BEGIN
# SELECT "A".id AS "A_id" 
# FROM "A" LEFT OUTER JOIN (SELECT "A".type AS "A_type", "A".id AS "A_id", "A".p AS "A_p", "B".id AS "B_id", "B".x AS "B_x" 
# FROM "A" JOIN "B" ON "A".id = "B".id) AS anon_1 ON anon_1."A_id" = "A".id 
# WHERE anon_1."B_x" > ?
# (10,)
# Col ('A_id',)

以下是我用来测试这个 SQLAlchemy 的代码,如果有人想要尝试一下:

#!/usr/bin/env python
import logging
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class A(Base) :
   __mapper_args__ = { 'polymorphic_on' : discriminator }
   __tablename__ = 'A'

   id = Column(Integer, primary_key=True)
   discriminator = Column('type', Integer, index=True, nullable=False)
   p = Column(Integer)

class B(A) :
   __mapper_args__ = { 'polymorphic_identity' : 0 }
   __tablename__ = 'B'

   id = Column(Integer, ForeignKey('A.id'), primary_key=True)
   x = Column(Integer)

class C(A) :
   __mapper_args__ = { 'polymorphic_identity' : 1 }
   __tablename__ = 'C'

   id = Column(Integer, ForeignKey('A.id'), primary_key=True)
   y = Column(String)

meta = Base.metadata
meta.bind = create_engine('sqlite://')
meta.create_all()

Session = sessionmaker()
Session.configure(bind=meta.bind)
session = Session()

log = logging.getLogger('sqlalchemy')
log.addHandler(logging.StreamHandler())
log.setLevel(logging.DEBUG)

session.query(A.id).outerjoin((B, B.id == A.id)).filter(B.x > 10).all()
session.query(A).with_polymorphic(B).filter(B.x > 10).all()

我在Python 2.7上使用SQLAlchemy 0.6.4运行了这个程序。


有没有办法只返回A.id,而不是A和B中的所有内容? 当我执行session.query(A.id).with_polymorphic(B)时... 我会得到“此查询未设置主映射器”的异常... - Colin
在尝试并阅读文档/代码后,我无法弄清如何在没有主查询映射器的情况下调用with_polymorphic(),这非常复杂。我认为这可能是一个更适合由SQLAlchemy邮件列表回答的问题。 - hao

1
你可以尝试分别为每个子类构建查询,然后将它们联合在一起。当查询B.id时,SQLAlchemy会隐式地连接超类并返回A.id,因此仅对B.idC.id进行选择的联合只返回单个列。
>>> b_query = session.query(B.id).filter(B.x > 10)
>>> c_query = session.query(C.id).filter(C.y == 'foo')
>>> print b_query.union(c_query)
SELECT anon_1."A_id" AS "anon_1_A_id" 
FROM (SELECT "A".id AS "A_id" 
FROM "A" JOIN "B" ON "A".id = "B".id 
WHERE "B".x > ? UNION SELECT "A".id AS "A_id" 
FROM "A" JOIN "C" ON "A".id = "C".id 
WHERE "C".y = ?) AS anon_1

你仍然会得到一个子查询,但只有一个“层级”的连接 - 外部查询只是重命名列。


好主意!一个快速的离题问题,你可能知道答案:如果我使用order_by(A.p)和limit(SOMENUM),它仍然可以工作,并生成我期望的结果。但是,如果SOMENUM << count(B)+count(C),那么效率会非常低吗?如果我从单个表中进行order_by和limit选择,我希望引擎不会提取和排序所有内容(对吧?),但如果是一个被排序和限制的union呢?解释生成的查询似乎导致它扫描与过滤器匹配的每一行...有什么解决办法吗?也许这应该是一个单独的问题... - Colin
你可以尝试在各个子类查询中重复使用 limitorder_by 子句。这不应该改变结果,因为对于限制为 N 的情况,你最多只会使用每个子类表中的前 N 行。 - dhaffey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接