Как получить подсчеты на основе нескольких условий с группировкой в одном запросе в django orm?
Предположим, что у меня есть следующая модель клиента:
class Customer(models.Model):
name = models.CharField()
city = models.CharField()
first_joined = models.DateField(auto_now_add=True)
last_visited = models.DateField()
def __str__(self):
return self.name
Поле last_visited
обновляется кодом каждый раз, когда покупатель посещает магазин.
Теперь я хочу видеть, что за определенный период времени, сколько новых клиентов присоединились к магазину, и сколько всего клиентов посетили магазин. Мне нужно, чтобы эта информация была сгруппирована по city
.
Ожидается, что на выходе получится что-то вроде:
city | new | visitors
-------------------------------
A | 12 | 24
B | 34 | 43
C | 9 | 21
В настоящее время я делаю это в двух отдельных запросах следующим образом:
visited = Customer.objects.filter(last_visited__gte=from_date, last_visited__lte=to_date).values("city").order_by("city").annotate(count=Count("id"))
new_customers = Customer.objects.filter(first_joined__gte=from_date, first_joined__lte=to_date).values("city").order_by("city").annotate(count=Count("id"))
Но я не предпочитаю такой подход по двум причинам:
- Это заставляет базу данных дважды просматривать один и тот же набор клиентов, в то время как это можно сделать только за один обход.
- Это приводит к созданию двух разных словарей, которые затем необходимо объединить в один словарь, что опять же является неэффективным процессом.
Я просмотрел этот вопрос, но он касается только множественных условий, но не того, как их группировать.
Есть ли способ сделать это в Django в одном запросе?
Вы можете добиться этого в одном запросе, используя аннотации Case, When и Sum в Django вместе с объектами Q. Эти аннотации позволяют выполнять условную агрегацию, что дает возможность вычислять несколько агрегатов в одном запросе.
Вы можете сделать что-то вроде:
from django.db.models import Count, Case, When, IntegerField, Q
from_date = '2023-01-01'
to_date = '2023-01-31'
result = Customer.objects.filter(
Q(first_joined__gte=from_date, first_joined__lte=to_date) |
Q(last_visited__gte=from_date, last_visited__lte=to_date)
).values("city").annotate(
new=Count(Case(
When(first_joined__gte=from_date, first_joined__lte=to_date, then=1),
output_field=IntegerField()
)),
visitors=Count(Case(
When(last_visited__gte=from_date, last_visited__lte=to_date, then=1),
output_field=IntegerField()
))
).order_by("city")
for r in result:
print(r)