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

时间:2012-09-17 15:21:00

标签: python django django-orm django-1.3 django-q

我在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()方法两次来获得我想要的结果。如您所见,它使用两个JOIN来查找两个外键:

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

2 个答案:

答案 0 :(得分:2)

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

使用filter( ~(~Q(A) | ~Q(B)) )代替filter(Q(A) & Q(B))似乎会产生预期的结果。

这是基于以下事实:A and B等同于not ( not(A) or not(B) ),并且仅使用ForeignKey个对象启用对多个Q的过滤(最终传递给单个{{1} }})。它相当丑陋,但最后仍然只使用一个数据库查询。

如上所述,原始问题来自filter.filter(A).filter(B)的不同行为(请参阅the doc中的示例),filter(A & B)只复制其中一个这些用途。

在我的情况下生成的SQL查询太复杂,无法确保此解决方法完全正常工作,但我想我会分享这个想法,因为我的测试证明它可以工作。如果你肯定证明它/打破它,请告诉我。

答案 1 :(得分: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))

...然后它只返回拥有满足两个约束Customer的任何订单的order_id=9 AND order_id=13个。这些术语必须在任何给定时间引用相同的Order

另一方面,如果我使用两个不同的.filter()来电...

来应用查询字词
queryset = Customer.objects.filter(order__order_id=9).filter(order__order_id=13)

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

可能可以通过Q()调用从.filter()个对象获取此行为,但看起来不像Django的ORM的意思使用。