简体   繁体   English

查询Q()不能作为Django中相关字段的“设置包含”吗?

[英]Query Q() is not working as 'set contain' for related field in Django?

Let's say we have baskets of fruits. 假设我们有水果篮。 How to filter out those baskets that contains all fruits in a given basket? 如何过滤出包含给定篮子中所有水果的那些篮子?

In this document https://docs.djangoproject.com/en/dev/ref/models/querysets/#in 'in' method seems will return any basket which contains any of the given fruit. 在本文档中, https://docs.djangoproject.com/en/dev/ref/models/querysets/#in'in '方法似乎将返回包含任何给定水果的任何篮子。 Is there any "set_contains" method to be used for filtering ? 是否有任何用于过滤的“ set_contains”方法? Such as Basket.objects.filter(fruit_set_containsAll=fruitSet) ? 例如Basket.objects.filter(fruit_set_containsAll = fruitSet)吗?

Edit: I found that Q() is not working as expected. 编辑:我发现Q() 不能按预期工作 I'll post the test here: 我将在这里发布测试:

class Basket(models.Model):
    weight = models.FloatField(default=1)
class Fruitname(models.Model):
    name = models.CharField(max_length=32)
class Fruit(models.Model):
    ofkind = models.ForeignKey(Fruitname, on_delete=models.CASCADE)  
    inbasket = models.ForeignKey(Basket, on_delete=models.CASCADE)  
    weight = models.FloatField(default=1)

the database is set as 'apple' only in 1 basket, 'pear' in 2 baskets, and 'banana' in 3 basket: 数据库仅在1个篮子中设置为“ apple”,在2个篮子中设置为“ pear”,在3个篮子中设置为“ banana”:

print Basket.objects.all()
print Fruitname.objects.all()
[<Basket: id 31 has 4 fruits 
    1. id - 53 apple(28), weight 1.00
    2. id - 54 apple(28), weight 2.00
    3. id - 55 apple(28), weight 3.00
    4. id - 62 banana(30), weight 10.00
>, <Basket: id 32 has 2 fruits 
    1. id - 56 pear(29), weight 4.00
    2. id - 57 banana(30), weight 5.00
>, <Basket: id 33 has 4 fruits 
    1. id - 58 pear(29), weight 6.00
    2. id - 59 banana(30), weight 7.00
    3. id - 60 pear(29), weight 8.00
    4. id - 61 pear(29), weight 9.00
>]
[<Fruitname: apple(28)>, <Fruitname: pear(29)>, <Fruitname: banana(30)>]

If I try to query with 'apple' and 'banana', it gives empty set !! 如果我尝试使用“苹果”和“香蕉”进行查询,则会给出空集!

print Basket.objects.filter(Q(fruit__ofkind__name__in=['apple'])&Q(fruit__ofkind__name__in=['banana'])).distinct()
[]

similarly, 同样

print Basket.objects.filter(Q(fruit__ofkind__name__in=['banana'])&Q(fruit__ofkind__name__in=['pear'])).distinct()
[]

Here's how I use 'in' to filter, it is not what I need which is supposed to be an empty set. 这是我使用'in'进行过滤的方式,这不是我所需要的,它应该是一个空集。

print Basket.objects.filter(Q(fruit__ofkind__name__in=['apple','pear'])).distinct()
[<Basket: id 31 has 4 fruits 
    1. id - 53 apple(28), weight 1.00
    2. id - 54 apple(28), weight 2.00
    3. id - 55 apple(28), weight 3.00
    4. id - 62 banana(30), weight 10.00
>, <Basket: id 32 has 2 fruits 
    1. id - 56 pear(29), weight 4.00
    2. id - 57 banana(30), weight 5.00
>, <Basket: id 33 has 4 fruits 
    1. id - 58 pear(29), weight 6.00
    2. id - 59 banana(30), weight 7.00
    3. id - 60 pear(29), weight 8.00
    4. id - 61 pear(29), weight 9.00
>]

The only way that is working properly is chaining with filter: 正常工作的唯一方法是与过滤器链接:

Basket.objects.filter(fruit__ofkind__name__in=['apple']).filter(fruit__ofkind__name__in=['banana']).distinct()
[<Basket: id 31 has 4 fruits 
    1. id - 53 apple(28), weight 1.00
    2. id - 54 apple(28), weight 2.00
    3. id - 55 apple(28), weight 3.00
    4. id - 62 banana(30), weight 10.00
>]

These code is tested with Django 1.9.4 Any explaining? 这些代码已经在Django 1.9.4上进行了测试,有什么解释吗? I would undo the accepted answer for the moment. 我现在暂时撤消已接受的答案。

Not sure if this is the most performant way to go, but at least it should translate to one (big, ugly, nested) sql transaction (once the final baskets queryset is evaluated): 不知道这是否是最高效的方法,但是至少它应该转换为一个(大,丑陋,嵌套的)sql事务(一旦评估了最后一baskets ):

baskets = Basket.objects.all()
for fruit in fruits:
    baskets = baskets.filter(id__in=fruit.basket.all())

A more elegant (and possibly more performant) way could be tried as follows composing a query using Q objects (building on Dave Webb's answer to another question ): 可以尝试使用一种更优雅(可能更高效)的方法,如下所述,使用Q对象(基于Dave Webb 对另一个问题回答 )构建查询:

queries = [Q(id__in=fruit.basket.all()) for fruit in fruits]

query = Q()

# AND the Q object with the ones in the list
for item in queries:
    query &= item

baskets = Basket.objects.filter(query)

By printing the raw SQL of the Q & operations, I've found the reason why Q is working as this. 通过打印Q&操作的原始SQL,我找到了Q如此工作的原因。

from django.db import connection
print connection.queries
u'SELECT DISTINCT "market_basket"."id", "market_basket"."weight" FROM "market_basket" INNER JOIN "market_fruit" ON ("market_basket"."id" = "market_fruit"."inbasket_id") INNER JOIN "market_fruitname" ON ("market_fruit"."ofkind_id" = "market_fruitname"."id") WHERE ("market_fruitname"."name" IN (\'apple\') AND "market_fruitname"."name" IN (\'banana\')) LIMIT 21'

the key problem is WHERE clause will not be satisfied in a single condition when queries is used in a single filter. 关键问题是,在单个过滤器中使用查询时,无法在单个条件下满足WHERE子句。 It is actually looking for a fruitname is both in ['apple'] and ['banana'] which is not possible. 它实际上是在['apple']和['banana']中寻找一个水果名称,这是不可能的。 What is needed is to find (those fruits that have fruitname of 'apple') or (those fruits that have fruitname of 'banana') 需要找到(水果名称为“苹果”的那些水果)或(水果名称为“香蕉”的那些水果)

Currently the only viable solution is to chain filters. 当前唯一可行的解​​决方案是链接过滤器。

To get a Basket with all available Fruit instances in it, you can do something like this: 要获得一个包含所有可用Fruit实例的Basket ,您可以执行以下操作:

from django.db.models import Count

# first get all PKs of fruits
fruit_pk_list = Fruit.objects.value_list('id', flat=True)

# Then get filter the basket with all fruits using annotate and Count
baskets = Basket.objects.annotate(
    num_fruit=Count('fruit')).filter(num_fruit=len(fruit_pk_list))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM