Django ORM фильтрует несколько полей, используя оператор IN - PullRequest
0 голосов
/ 21 февраля 2020

Итак, у меня есть следующая модель в Django:

class MemberLoyalty(models.Model):
    date_time = models.DateField(primary_key=True)
    member = models.ForeignKey(Member, models.DO_NOTHING)
    loyalty_value = models.IntegerField()

Моя цель состоит в том, чтобы все кортежи были сгруппированы по участнику с самой последней датой. Есть много способов сделать это, один из них использует подзапрос, который группирует участника по max date_time и фильтрует member_loyalty с его результатами. Рабочая sql для этого решения выглядит следующим образом:

SELECT 
    *
FROM
    member_loyalty
WHERE
    (date_time , member_id) IN (SELECT 
            max(date_time), member_id
        FROM
            member_loyalty
        GROUP BY member_id);

Еще один способ сделать это - присоединиться к подзапросу.

Как я могу перевести это на django запрос? Я не смог найти ни способа фильтрации по двум полям с использованием IN, ни способа объединения с подзапросом с помощью оператора c ON, заданного спецификацией.

Я пробовал:

cls.objects.values('member_id', 'loyalty_value').annotate(latest_date=Max('date_time'))

Но он начинает группироваться по значению loyalty_value.

Также попытался создать подзапрос, но не может найти способ присоединить его или использовать его на фильтре:

subquery = cls.objects.values('member_id').annotate(max_date=Max('date_time'))

Также я использую Mysql, поэтому я не могу использовать метод .distinct ('param').

1 Ответ

1 голос
/ 21 февраля 2020

Это типичный запрос на группу . Переполнение стека даже имеет тег .

Я считаю, что наиболее эффективный способ сделать это с последними версиями Django - это запрос окна , Что-то в этом роде должно сработать.

    MemberLoyalty.objects.all().annotate(my_max=Window(
        expression=Max('date_time'),
        partition_by=F('member')
    )).filter(my_max=F('date_time'))

Обновление : Это на самом деле не сработает, поскольку аннотации Window не являются filterable. Я думаю, что для того, чтобы отфильтровать аннотацию окна, вам нужно обернуть ее внутри Subquery, но с Subquery вы на самом деле не обязаны использовать функцию Window, есть другой способ сделать это, мой следующий пример.

Если MySQL или Django не поддерживает оконные запросы, то в игру вступает подзапрос .

MemberLoyalty.objects.filter(
    date_time=Subquery(
        (MemberLoyalty.objects
            .filter(member=OuterRef('member'))
            .values('member')
            .annotate(max_date=Max('date_time'))
            .values('max_date')[:1]
        )
    )
)

Если событие Subqueries недоступны (до Django 1.11), тогда это также должно работать:

MemberLoyalty.objects.annotate(
    max_date=Max('member__memberloyalty_set__date_time')
).filter(max_date=F('date_time'))
...