sqlalchemy:如何关闭声明式多态连接?
有没有办法在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)
我想查询所有A的id,条件是如果A实际上是B的话,B.x要大于10;如果A实际上是C的话,C.y要等于'blah',并且结果要按p排序。
为了逐步实现这个目标,我先从第一部分开始——“获取所有A的id,条件是如果A实际上是B的话,B.x要大于10”。所以我想用外连接来开始:
session.query(A.id).outerjoin((B, B.id == A.id)).filter(B.x > 10)
... 但是似乎没有办法避免那个outerjoin((B, B.id == A.id))的条件在子查询中生成A和B的全连接。如果B没有从A继承,那么这种情况就不会发生,所以我在想是不是多态声明式的代码生成导致了这个问题。有没有办法关闭这个功能?或者强制外连接按照我想要的方式运行?
我想要的结果大概是这样的:
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 上运行的。
1
你可以尝试分别为每个子类构建查询,然后把它们合并在一起。当你查询 B.id
时,SQLAlchemy 会自动连接到父类,并返回 A.id
,所以如果你把 B.id
和 C.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
你仍然会得到一个子查询,但只有一层连接 - 外层查询只是给列重新命名而已。