Фильтр запросов Django, сочетающий AND и OR с объектами Q, не возвращает ожидаемых результатов
Я пытаюсь объединить AND и OR в фильтре, используя объекты Q. Похоже, что | вести себя как AND. Это связано с предыдущей аннотацией, которая выполняется в том же запросе, а не как подзапрос.
Как правильно справиться с этим с Джанго?
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)
РЕДАКТИРОВАТЬ: я добавил ожидаемый запрос SQL (без объединения на equipment_subcategory)
3 ответа
ОК, безуспешно здесь или на #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)
')
Попробуйте добавить скобки, чтобы явно указать свою группу? Как вы уже поняли, несколько параметров для filter() просто объединяются через AND в базовом SQL.
Первоначально у вас было это для фильтра:
[...].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)))
Этот ответ запоздал, но может быть полезным для многих ребят.
[...].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))
Вот мой пример сложного запроса, надеюсь, вы найдете его полезным
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)
Проблема с этим методом в том, что вы получаете empty (AND: )
дело в вашем or_condition
. На запрос это никак не влияет, но меня раздражает! Мое текущее решение выглядит следующим образом
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)