Как получить подсчеты на основе нескольких условий с группировкой в одном запросе в 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"))

Но я не предпочитаю такой подход по двум причинам:

  1. Это заставляет базу данных дважды просматривать один и тот же набор клиентов, в то время как это можно сделать только за один обход.
  2. Это приводит к созданию двух разных словарей, которые затем необходимо объединить в один словарь, что опять же является неэффективным процессом.

Я просмотрел этот вопрос, но он касается только множественных условий, но не того, как их группировать.

Есть ли способ сделать это в 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)
Вернуться на верх