web-dev-qa-db-ja.com

Django ANDとORを組み合わせたクエリフィルターは、Qオブジェクトで期待どおりの結果を返しません

ANDとORをQオブジェクトを使用してフィルターで組み合わせようとします。|は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)

[〜#〜] edit [〜#〜]:予想されるSQLクエリを追加しました(equipment_subcategoryの結合なし)

29
cgaspoz

OK、#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) 
    ')
6
cgaspoz

括弧を追加して、グループを明示的に指定してみますか?すでに理解しているように、 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)))
31
istruble

この答えは遅いですが、多くの人にとって役立つかもしれません。

[...].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))
6
Paullo

これが私の複雑なクエリの例です、あなたがそれが役に立てば幸いです

_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_でempty (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)
_
1
Rami Alloush