Я управляю собачьим салоном, где собаки подстригаются нечасто.Чтобы поощрить владельцев, я хотел бы разослать ваучеры на их следующий визит.Ваучер будет основан на том, была ли у собаки стрижка в течение последних 2 месяцев или 2 лет.Спустя 2 года мы можем предположить, что клиент потерян, и менее 2 месяцев назад слишком близко к своей предыдущей стрижке.Сначала мы нацелимся на владельцев, которые недавно посетили.
Моя базовая база данных - PostgreSQL.
from datetime import timedelta
from django.db import models
from django.db.models import Max, OuterRef, Subquery
from django.utils import timezone
# Dogs have one owner, owners can have many dogs, dogs can have many haircuts
class Owner(models.model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=255)
class Dog(models.model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
owner = models.ForeignKey(Owner, on_delete=models.CASCADE, related_name="dogs")
name = models.CharField(max_length=255)
class Haircut(models.model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
dog = models.ForeignKey(Dog, on_delete=models.CASCADE, related_name="haircuts")
at = models.DateField()
today = timezone.now().date()
start = today - timedelta(years=2)
end = today - timedelta(months=2)
Мне кажется, что проблему можно разбить на два запроса.Во-первых, это то, что объединяет собак владельца, которые в последний раз резали за последние 2 месяца до 2 лет.
dog_aggregate = Haircut.objects.annotate(Max("at")).filter(at__range=(start, end))
И затем объединяет результат этого с таблицей владельцев.
owners_by_shaggiest_dog_1 = Owner.objects # what's the rest of this?
В результате SQL похож на:
select
owner.id,
owner.name
from
(
select
dog.owner_id,
max(haircut.at) last_haircut
from haircut
left join dog on haircut.dog_id = dog.id
where
haircut.at
between current_date - interval '2' year
and current_date - interval '2' month
group by
dog.owner_id
) dog_aggregate
left join owner on dog_aggregate.owner_id = owner.id
order by
dog_aggregate.last_haircut asc,
owner.name;
Благодаря некоторой тренировке мне удалось получить правильный результат с помощью:
haircut_annotation = Subquery(
Haircut.objects
.filter(dog__owner=OuterRef("pk"), at__range=(start, end))
.order_by("-at")
.values("at")[:1]
)
owners_by_shaggiest_dog_2 = (
Owner.objects
.annotate(last_haircut=haircut_annotation)
.order_by("-last_haircut", "name")
)
Однако полученный SQL кажется неэффективным как новыйзапрос выполняется для каждой строки:
select
owner.id,
owner.name,
(
select
from haircut
inner join dog on haircut.dog_id = dog.id
where haircut.at
between current_date - interval '2' year
and current_date - interval '2' month
and dog.owner_id = (owner.id)
order by
haircut.at asc
limit 1
) last_haircut
from
owner
order by
last_haircut asc,
owner.name;
PS На самом деле я не управляю салоном для собак, поэтому не могу дать вам ваучер.Извините!