使用 Q 对象过滤多个 ForeignKey 匹配

Posted

技术标签:

【中文标题】使用 Q 对象过滤多个 ForeignKey 匹配【英文标题】:Filtering for multiple ForeignKey matches using Q objects 【发布时间】:2012-09-09 20:25:47 【问题描述】:

我已经在 Django 1.3 下名为 main 的应用程序中使用以下数据初始化了这些模型:

from django.db.models import Model, FloatField, CharField, ForeignKey, Q

class Customer(Model):
    name = CharField(max_length=64)

class Order(Model):
    customer = ForeignKey(Customer)
    order_total = FloatField()
c = Customer(name="John Smith")
c.save()

Order(customer=c, order_id=9).save()
Order(customer=c, order_id=13).save()

如何使用Q() 对象构造查询以查找拥有订单 9 和订单 13 的客户?

不使用Q() 对象,我可以使用.filter() 方法两次来获得我想要的结果。如您所见,它使用两个JOINs 来查找两个外键:

queryset = Customer.objects.filter(order__order_id=9).filter(order__order_id=13)

return HttpResponse("%s\n\n%s" % (queryset, queryset.query), content_type="text/plain")
[<Customer: Customer object>]

SELECT "main_customer"."id", "main_customer"."name"
FROM "main_customer"
INNER JOIN "main_order" ON ("main_customer"."id" = "main_order"."customer_id")
INNER JOIN "main_order" T3 ON ("main_customer"."id" = T3."customer_id")
WHERE ("main_order"."order_id" = 9  AND T3."order_id" = 13 )

我尝试使用Q() 对象做同样的事情,如下所示。它没有理解我指的是两个不同的订单,一个 ID 为 9,一个 ID 为 13,而是认为我正在寻找一个 ID 为 9 和 13 的订单。这显然是不可能的,所以它不返回任何结果:

q = Q(order__order_id=9) & Q(order__order_id=13)
queryset = Customer.objects.filter(q)

return HttpResponse("%s\n\n%s" % (queryset, queryset.query), content_type="text/plain")
[]

SELECT "main_customer"."id", "main_customer"."name"
FROM "main_customer"
INNER JOIN "main_order" ON ("main_customer"."id" = "main_order"."customer_id")
WHERE ("main_order"."order_id" = 9  AND "main_order"."order_id" = 13 )

我希望 Django 的引擎能够等效地解释这两个查询,但显然 Q() 对象的处理方式不同。如何使用Q() 对象通过多个外键引用过滤对象,而不是多次调用.filter()

【问题讨论】:

我不确定如何最好地描述我正在尝试做的事情。欢迎提出新的标题建议。 【参考方案1】:

我找到了an explanation for this in the Django documentation。我观察到的行为是预期的行为;如果将多值关系的查询词应用于对 .filter()/.exclude() 的同一调用中,则它们的处理方式与应用于不同调用时不同。

如果我在同一个 .filter() 调用中应用这两个查询词...

queryset = Customer.objects.filter(Q(order__order_id=9) & Q(order__order_id=13))
# or equivalently: .objects.filter(Q(order__order_id=9), Q(order__order_id=13))

...那么它只返回Customers 拥有满足两个约束order_id=9 AND order_id=13 的任何订单。这些条款在任何给定时间都必须引用相同的Order

另一方面,如果我使用两个不同的.filter() 调用来应用查询词...

queryset = Customer.objects.filter(order__order_id=9).filter(order__order_id=13)

...他们不需要引用相同的Order。这可以看作是一个两步操作:所有Customers 的集合被过滤到拥有满足order_id=9 的任何Orders 的那些。然后这个结果集被进一步过滤到那些Customers 也拥有任何满足order_id=13Order。在这两种情况下,它可能是相同的Order。没关系;这些步骤是相互隔离的。

可能有一些方法可以通过单个 .filter() 调用从 Q() 对象中获取此行为,但看起来这不是 Django 的 ORM 的使用方式。

【讨论】:

如果有人可以通过一个.filter()/.exclude() 电话找到某种方法来完成这项工作,我会非常感兴趣,如果它甚至可以远程使用你'有权获得实际回答问题的接受答案标记。【参考方案2】:

我想我找到了解决这种情况的方法:

使用filter( ~(~Q(A) | ~Q(B)) ) 而不是filter(Q(A) &amp; Q(B)) 似乎会产生预期的结果。

这是基于 A and B 等效于 not ( not(A) or not(B) ) 并且仅使用 Q 对象(最终传递给单个 filter)启用对多个 ForeignKey 的过滤这一事实。比较难看,但最后还是只用了一个数据库查询。

如上所述,最初的问题来自.filter(A).filter(B)filter(A &amp; B) 的不同行为(参见the doc 中的示例),而filter(Q(A) &amp; Q(B)) 仅复制了其中一种用途。

在我的案例中生成的 SQL 查询过于复杂,无法确保此解决方法完美运行,但我想我会分享这个想法,因为我的测试证明它有效。如果你确定证明/打破它,请告诉我。

【讨论】:

感谢您的建议。这些天我并没有真正使用 Django,所以我不能自己测试它,但希望谁会在某个时间看到这篇文章,发现它很有用,并且可以发表评论让我们知道它是否适用他们。

以上是关于使用 Q 对象过滤多个 ForeignKey 匹配的主要内容,如果未能解决你的问题,请参考以下文章

多个字段上的Django查询对象Q?

如何在 django 的单个视图中显示多个 ForeignKey 过滤项目?

Laravel 关系:一个foreignKey 对应多个localKey 关系

使用多个术语和 Q 过滤器对 reduce 查询进行排序

如何为 django Q 对象动态加入多个参数

按用户过滤不是在另一个模型查询集中使用 Q 的员工