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.ids, для которых B.x > 10, если этот A на самом деле является B, или где C.y == 'blah', если этот A на самом деле является C, все упорядочено по p.

Чтобы сделать это итеративно, я начинаю только с первой части - «получить все A.id, для которых B.x > 10, если этот A на самом деле является B». Поэтому я подумал, что начну с внешнего соединения:

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

... за исключением того, что, похоже, нет способа избежать того, чтобы это предложение externaljoin((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 на самом деле выполнять это внутреннее соединение или он его проигнорирует?


person Colin    schedule 02.09.2010    source источник


Ответы (2)


Вы можете попробовать построить запросы для каждого подкласса отдельно, а затем объединить их вместе. При запросе 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

Вы по-прежнему получаете подвыборку, но только один «слой» соединений — внешний выбор просто переименовывает столбец.

person dhaffey    schedule 03.09.2010
comment
Отличная идея! Быстрый тангенс, на который вы, возможно, знаете ответ: если я размещаю order_by(A.p) и limit(SOMENUM), он все еще работает и генерирует то, что я ожидаю. Но будет ли это супер неэффективно, если SOMENUM ‹‹ count(B)+count(C)? Если я упорядочиваю и ограничиваю выбор из одной таблицы, я ожидаю, что движок НЕ будет извлекать и сортировать все (правильно?), но если это объединение, которое упорядочивается и ограничивается? ОБЪЯСНЕНИЕ сгенерированного запроса, кажется, приводит к тому, что он сканирует каждую строку, которая соответствует фильтрам... как-нибудь обойти это? Может быть, это должен быть отдельный вопрос... - person Colin; 03.09.2010
comment
Вы можете попробовать повторить предложения limit и order_by для отдельных запросов подкласса. Это не должно изменить результат, так как при ограничении N вы будете использовать не более N верхних строк из каждой таблицы подклассов. - person dhaffey; 04.09.2010

Вы должны использовать with_polymorphic() вместо externaljoin(), который возвращает ожидаемые результаты:

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.

person hao    schedule 02.09.2010
comment
Есть ли способ вернуть только A.id, а не все в A и B? Когда я делаю session.query(A.id).with_polymorphic(B)... Я получаю, что первичный преобразователь не настроен для этого исключения запроса... - person Colin; 03.09.2010
comment
Я не мог понять, как вызвать with_polymorphic() без основного преобразователя запросов после того, как поигрался с ним и прочитал документацию/код, который очень запутан. Я думаю, что на этот вопрос лучше ответить в списке рассылки sqlalchemy. - person hao; 03.09.2010