SqlAlchemy:检查对象是否在任何关系中 (or_(object.relationship1.contains(otherObject), object.relationship2.contains(otherObject))

6 投票
1 回答
7244 浏览
提问于 2025-04-16 18:17

假设我有一个这样的类:

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())

(每个关系都会给我一些“Bar”,并且有特定的条件)。在某个时刻,我想获取那些在任何关系中都有“bar”(Bar.Bar的实例)的“Foo”的实例。

如果我尝试这样做:

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中有更“干净”的方法来做到这一点。

谢谢!

1 个回答

4

你说得对,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 的一个 bug。不过,把 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))

撰写回答