2014-09-25 97 views
1

我试图查询和筛选一对多的关系,似乎无法弄清楚如何做到这一点。过滤SQLAlchemy一对多与“不包含”

这里是我的映射(修剪为了简洁):

class Bug(Base): 
    __tablename__ = 'bug' 
    id = Column('bug_id', Integer, primary_key=True) 
    tags = relationship('Tag', backref='bug') 

class Tag(Base): 
    id = Column('tag_id', Integer, primary_key=True) 
    name = Column('tag_name', String) 
    bug_id = Column('bug_id', ForeignKey('bug.bug_id')) 

我希望能够找到没有与名称为“foo”标签的所有错误。

回答

3

我不确定Tag表应该代表什么,但奇怪的是,您的模式将每个Tag与恰好一个Bug关联起来。如果要使用同名标记标记多个Bug,您将在Tag类中使用相同的name创建多行。这似乎违反了3rd normal form

在数据库中描述标签云的标准方法是使用与关联(错误,标签)对的辅助“关联”表的多对多关系。 SQLAlchemy文档有一个very nice tutorial on this pattern

如果您坚持原样使用您的模式,有几种方法可以做到这一点。

客户端过滤

这显然效率低下,但很容易理解。你经历的错误一个接一个,通过他们的标签一个接一个,并消除缺陷,其中tag.name=="foo"

non_foo_bugs = [ bug for bug in session.query(Bug) 
       if not any(tag.name=="foo" for tag in bug.tag) ] 

两个查询

找到所有不同的bug 标记为“富”,然后找到该集合的补充。

这个版本使用的数据库正好有两个疑问:让foo_bugs子查询

foo_bugs = [t.bug_id for t in session.query(Tag).filter_by(name="foo").distinct()] 
session.query(Bug).filter(~Bug.id.in_(foo_bugs)) 

一个使用子查询

同上面的查询,但是,因为没有理由在获取其内容客户端:

foo_bugs = session.query(Tag.bug_id).filter_by(name="foo").distinct().subquery() 
session.query(Bug).filter(~Bug.id.in_(foo_bugs)) 

这将是一个不相关的子查询,所以从服务器的角度来看,应该只是对优化与两个单独的查询相同。

+1

您对数据如何映射为“错误”的评估是正确的。我实际上是映射一个现有的数据库结构,我有只读访问权限,所以我正在做我所拥有的。感谢您提供完整的答案和多种选择! – Jared 2014-09-26 18:15:34

4

您可以在关系上使用any()运算符。

bugs_without_foo = session.query(Bug).filter(
    db.not_(Bug.tags.any(Tag.name == 'foo')) 
).all() 

这是更好看,但它可能是在非常大的数据集比丹·伦斯基的答案子查询效率较低。

+0

有趣。 [根据手册](http://docs.sqlalchemy.org/en/rel_0_9/orm/internals.html#sqlalchemy.orm.properties.RelationshipProperty.Comparator。任何)使用'any()'会产生一个相关的子查询,这在大多数情况下确实效率较低。但我同意你的观点,代码更漂亮,更Pythonic :) – 2014-09-26 00:43:23