将 AND 和 OR 与 Q 对象组合的 Django 查询过滤器不会返回预期结果

发布于 2024-09-28 14:14:43 字数 2100 浏览 4 评论 0原文

我尝试使用 Q 对象在过滤器中组合 AND 和 OR。看起来像|表现得像一个 AND。这与先前在同一查询中运行而不是作为子查询运行的注释相关。

Django 处理这个问题的正确方法是什么?

models.py

class Type(models.Model):
    name = models.CharField(_('name'), max_length=100)
    stock = models.BooleanField(_('in stock'), default=True)
    hide = models.BooleanField(_('hide'), default=False)
    deleted = models.BooleanField(_('deleted'), default=False)

class Item(models.Model):
    barcode = models.CharField(_('barcode'), max_length=100, blank=True)
    quantity = models.IntegerField(_('quantity'), default=1)
    type = models.ForeignKey('Type', related_name='items', verbose_name=_('type'))

views.py

def hire(request):
    categories_list = Category.objects.all().order_by('sorting')
    types_list = Type.objects.annotate(quantity=Sum('items__quantity')).filter(
        Q(hide=False) & Q(deleted=False),
        Q(stock=False) | Q(quantity__gte=1))
    return render_to_response('equipment/hire.html', {
           'categories_list': categories_list,
           'types_list': types_list,
           }, context_instance=RequestContext(request))

结果 SQL 查询

SELECT "equipment_type"."id" [...] FROM "equipment_type" LEFT OUTER JOIN
    "equipment_subcategory" ON ("equipment_type"."subcategory_id" =
    "equipment_subcategory"."id") LEFT OUTER JOIN "equipment_item" ON
    ("equipment_type"."id" = "equipment_item"."type_id") WHERE 
    ("equipment_type"."hide" = False AND "equipment_type"."deleted" = False )
    AND ("equipment_type"."stock" = False )) GROUP BY "equipment_type"."id"
    [...] HAVING SUM("equipment_item"."quantity") >= 1

预期 SQL 查询

SELECT
    *
FROM
    equipment_type
LEFT JOIN (
    SELECT type_id, SUM(quantity) AS qty
    FROM equipment_item
    GROUP BY type_id
) T1
ON id = T1.type_id
WHERE hide=0 AND deleted=0 AND (T1.qty > 0 OR stock=0)

编辑: I添加了预期的 SQL 查询(没有对 Equipment_subcategory 进行连接)

I try to combine AND and OR in a filter using Q objects. It looks like that the | behave like an AND. This is related to the previous annotate which is run in the same query and not as a subquery.

What is the correct way to handle this with Django?

models.py

class Type(models.Model):
    name = models.CharField(_('name'), max_length=100)
    stock = models.BooleanField(_('in stock'), default=True)
    hide = models.BooleanField(_('hide'), default=False)
    deleted = models.BooleanField(_('deleted'), default=False)

class Item(models.Model):
    barcode = models.CharField(_('barcode'), max_length=100, blank=True)
    quantity = models.IntegerField(_('quantity'), default=1)
    type = models.ForeignKey('Type', related_name='items', verbose_name=_('type'))

views.py

def hire(request):
    categories_list = Category.objects.all().order_by('sorting')
    types_list = Type.objects.annotate(quantity=Sum('items__quantity')).filter(
        Q(hide=False) & Q(deleted=False),
        Q(stock=False) | Q(quantity__gte=1))
    return render_to_response('equipment/hire.html', {
           'categories_list': categories_list,
           'types_list': types_list,
           }, context_instance=RequestContext(request))

resulting SQL query

SELECT "equipment_type"."id" [...] FROM "equipment_type" LEFT OUTER JOIN
    "equipment_subcategory" ON ("equipment_type"."subcategory_id" =
    "equipment_subcategory"."id") LEFT OUTER JOIN "equipment_item" ON
    ("equipment_type"."id" = "equipment_item"."type_id") WHERE 
    ("equipment_type"."hide" = False AND "equipment_type"."deleted" = False )
    AND ("equipment_type"."stock" = False )) GROUP BY "equipment_type"."id"
    [...] HAVING SUM("equipment_item"."quantity") >= 1

expected SQL query

SELECT
    *
FROM
    equipment_type
LEFT JOIN (
    SELECT type_id, SUM(quantity) AS qty
    FROM equipment_item
    GROUP BY type_id
) T1
ON id = T1.type_id
WHERE hide=0 AND deleted=0 AND (T1.qty > 0 OR stock=0)

EDIT: I added the expected SQL query (without the join on equipment_subcategory)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

骄傲 2024-10-05 14:14:43

尝试添加括号来明确指定您的分组?正如您已经想到的,多个参数 filter() 只是通过底层 SQL 中的 AND 连接。

最初你有这个过滤器:

[...].filter(
    Q(hide=False) & Q(deleted=False),
    Q(stock=False) | Q(quantity__gte=1))

如果你想要 (A & B) & (C | D) 那么这应该有效:

[...].filter(
    Q(hide=False) & Q(deleted=False) &
    (Q(stock=False) | Q(quantity__gte=1)))

Try adding parentheses to explicitly specify your grouping? As you already figured out, multiple params to filter() are just joined via AND in the underlying SQL.

Originally you had this for the filter:

[...].filter(
    Q(hide=False) & Q(deleted=False),
    Q(stock=False) | Q(quantity__gte=1))

If you wanted (A & B) & (C | D) then this should work:

[...].filter(
    Q(hide=False) & Q(deleted=False) &
    (Q(stock=False) | Q(quantity__gte=1)))
鱼忆七猫命九 2024-10-05 14:14:43

这个答案来晚了,但可能对很多人有帮助。

[...].filter(hide=False & deleted=False)
.filter(Q(stock=False) | Q(quantity__gte=1))

这将生成类似的东西

WHERE (hide=0 AND deleted=0 AND (T1.qty > 0 OR stock=0))

This answer is late but could be helpful to a lot of people out there.

[...].filter(hide=False & deleted=False)
.filter(Q(stock=False) | Q(quantity__gte=1))

This will generate something similar to

WHERE (hide=0 AND deleted=0 AND (T1.qty > 0 OR stock=0))
阿楠 2024-10-05 14:14:43

这是我的复杂查询示例,希望对您有所帮助。

or_condition = Q()
and_condition = Q(company=request.user.profile.company)

for field in MyModel._meta.get_fields():
    if field.name != 'created_on' and field.name != 'company':
        or_condition.add(
            Q(**{"{}__icontains".format(field.name): query}), Q.OR)

and_condition.add(or_condition2, Q.AND)
MyModel.objects.filter(and_condition)

此方法的问题是您在 or_condition 中得到一个空 (AND: ) 情况。完全不影响查询,但是让我很烦!
我目前的解决方案如下

import operator
from functools import reduce

and_condition = Q(company=request.user.profile.company)
or_condition = reduce(operator.or_, (Q(**{"{}__icontains".format(field.name): query})
                                     for field in MyModel._meta.get_fields() if field.name != 'created_on' and field.name != 'company'))

and_condition.add(or_condition, Q.AND)
MyModel.objects.filter(and_condition)

Here's my example of complicated query, I hope you find it helpful

or_condition = Q()
and_condition = Q(company=request.user.profile.company)

for field in MyModel._meta.get_fields():
    if field.name != 'created_on' and field.name != 'company':
        or_condition.add(
            Q(**{"{}__icontains".format(field.name): query}), Q.OR)

and_condition.add(or_condition2, Q.AND)
MyModel.objects.filter(and_condition)

The problem with this method is that you get an empty (AND: ) case in your or_condition. It doesn't affect the query at all, but it annoys me!
My current solution is as follows

import operator
from functools import reduce

and_condition = Q(company=request.user.profile.company)
or_condition = reduce(operator.or_, (Q(**{"{}__icontains".format(field.name): query})
                                     for field in MyModel._meta.get_fields() if field.name != 'created_on' and field.name != 'company'))

and_condition.add(or_condition, Q.AND)
MyModel.objects.filter(and_condition)
2024-10-05 14:14:43

好的,这里或 #django 上都没有成功。所以我选择使用原始 SQL 查询来解决这个问题...

这里是工作代码:

types_list = Type.objects.raw('SELECT * FROM equipment_type
    LEFT JOIN (                                            
        SELECT type_id, SUM(quantity) AS qty               
        FROM equipment_item                                
        GROUP BY type_id                                   
    ) T1                                                   
    ON id = T1.type_id                                     
    WHERE hide=0 AND deleted=0 AND (T1.qty > 0 OR stock=0) 
    ')

OK, no success here or on #django. So I choose to use a raw SQL query to solve this problem...

Here the working code:

types_list = Type.objects.raw('SELECT * FROM equipment_type
    LEFT JOIN (                                            
        SELECT type_id, SUM(quantity) AS qty               
        FROM equipment_item                                
        GROUP BY type_id                                   
    ) T1                                                   
    ON id = T1.type_id                                     
    WHERE hide=0 AND deleted=0 AND (T1.qty > 0 OR stock=0) 
    ')
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文