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

4 投票
2 回答
1569 浏览
提问于 2025-04-16 03:38

有没有办法在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.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

你仍然会得到一个子查询,但只有一层连接 - 外层查询只是给列重新命名而已。

撰写回答