SqlAlchemy: проверьте, находится ли один объект в каком-либо отношении (или_(object.relationship1.contains(otherObject), object.relationship2.contains(otherObject))

Скажем, у меня есть такой класс:

class Foo(declarativeBase):
     bars1 = relationship(Bar.Bar, secondary=foos_to_bars1, collection_class=set())
     bars2 = relationship(Bar.Bar, secondary=foos_to_bars2, collection_class=list())

(Каждое из отношений дает мне «Бар» с определенными условиями). В определенный момент я хочу получить экземпляры «Foo», у которых есть «бар» (экземпляр Bar.Bar) в любом из отношений.

Если я попытаюсь сделать:

def inAnyBar(bar)
   query(Foo).filter(or_(Foo.bars1.contains(bar), Foo.bars2.contains(bar)).all()

Я получаю пустой результат.

Это выглядит (для меня), как будто я делаю что-то вроде:

query(Foo).join(Foo.bars1).filter(Foo.bars1.contains(bar)).\
join(Foo.bars2).filter(Foo.bars1.contains(bar))

Так как Foo.bars1 не содержит bar, второй фильтр дает пустые результаты.

Мне удалось найти обходной путь с подзапросами (каждое соединение + фильтр в подзапросе, затем or_ все подзапросы), но я хотел бы знать, есть ли лучший способ сделать это...

Я нашел это: http://techspot.zzzeek.org/2008/09/09/selecting-booleans/

Это делает то, что я хочу сделать, но это для SqlAlchemy 0.5, и я (почти) уверен, что есть «более чистый» способ сделать это с SqlAlchemy 0.6.6.

Благодарю вас!


person BorrajaX    schedule 25.05.2011    source источник


Ответы (1)


Вы правы, session.query(Foo).filter(Foo.bars1.contains(bar)|Foo.bars2.contains(bar)) выдает следующий SQL:

SELECT "Foo".id AS "Foo_id" 
FROM "Foo", foos_to_bars1 AS foos_to_bars1_1, foos_to_bars2 AS foos_to_bars2_1 
WHERE "Foo".id = foos_to_bars1_1.foo AND ? = foos_to_bars1_1.bar OR 
"Foo".id = foos_to_bars2_1.foo AND ? = foos_to_bars2_1.bar

который возвращает неверный результат, когда одна из secondary таблиц пуста. Похоже на ошибку в SQLAlchemy. Однако замена contains() на any() устранила проблему (используются подзапросы EXISTS):

session.query(Foo).filter(Foo.bars1.any(id=bar.id)|Foo.bars2.any(id=bar.id))

Также вы можете явно указать OUTER JOIN:

Bar1 = aliased(Bar)
Bar2 = aliased(Bar)
session.query(Foo).outerjoin((Bar1, Foo.bars1)).outerjoin((Bar2, Foo.bars2))\
    .filter((Bar1.id==bar.id)|(Bar2.id==bar.id))
person Denis Otkidach    schedule 26.05.2011
comment
SQLA мало что может сделать, кроме как резко увеличить сложность оператора contains() для использования EXISTS при наличии m2m, который работает ужасно. any() и has() почти бесполезны по этой причине. Не стесняйтесь повторно открывать тикет № 2177 с предложением, в противном случае он пока закрыт. - person zzzeek; 26.05.2011
comment
@zzzeek Просто использовать OUTER JOIN нельзя? - person Denis Otkidach; 26.05.2011
comment
Действительно, он работает с session.query(Foo).filter(Foo.bars1.any(id=bar.id)|Foo.bars2.any(id=bar.id)). Я проверю версию внешнего соединения. Большое спасибо - person BorrajaX; 27.05.2011