Функции окна LEAD и LAG в Django orm, как применить к одному объекту? - PullRequest
0 голосов
/ 01 августа 2020

Вопрос касается использования Window функций в Django.

У меня следующая модель:

class EntriesChangeLog(models.Model):
    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.CASCADE,
    )
    object_id = models.PositiveIntegerField(
    )
    content_object = GenericForeignKey(
        'content_type',
        'object_id',
    )
    user = models.ForeignKey(
        get_user_model(),
        verbose_name='user',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='access_logs',
    )
    access_time = models.DateTimeField(
        verbose_name='access_time',
        auto_now_add=True,
    )
    as_who = models.CharField(
        verbose_name='Status of the accessed user.',
        choices=UserStatusChoices.choices,
        max_length=7,
    )
    operation_type = models.CharField(
        verbose_name='Type of the access operation.',
        choices=OperationTypeChoices.choices,
        max_length=6,
    )
    state = JSONField(
        verbose_name='Model state before save or delete.',
        encoder=CustomEncoder,
    )

Моя цель здесь - аннотировать каждый объект в наборе запросов этой модели с полем state из предыдущего и следующего объекта в наборе запросов с одинаковыми object_id и content_type_id. Это необходимо, чтобы позже получить эту аннотацию в подробном представлении и вычислить разницу между предыдущим, текущим и будущим состоянием.

Мой набор запросов в get_object:

def get_queryset(self):
    model = self.kwargs['model_name']
    instance_pk = self.kwargs['instance_pk']

    self.queryset = self.model.objects.filter(
        object_id=instance_pk,
        content_type__model=model.__name__.lower(),
        content_type__app_label=model._meta.app_label.lower(),
    ).select_related('user', )

    return super().get_queryset()


def get_object(self):
    queryset = self.filter_queryset(self.get_queryset())

    q = queryset.annotate(
        next_val=Window(
            expression=Lead('state'),
            order_by=F('id').asc()
        ),
        prev_val=Window(
                expression=Lag('state'),
                order_by=F('id').asc(),
        ),
    )
    obj = q.filter(pk=self.kwargs['pk']).first()

    self.check_object_permissions(self.request, obj)

    return obj

аналог в RAW SQL

SELECT
       id,
       state,
       LEAD("state") OVER(ORDER BY "id" ) AS "next_val",
       LAG("state") OVER(ORDER BY "id") AS "prev_val"
        
FROM "administration_entrieschangelog"
where object_id =158 and content_type_id=7

Он отлично работает, когда в наборе запросов есть более одного объекта, то есть в ListView. Но при детальном просмотре кажется, что WHERE работает до SELECT, и обе аннотации возвращают NULL. Похоже, что функция WINDOW в наборе запросов с одним единственным объектом в ней ограничена только этим объектом, а LEAD и LAG не могут видеть за пределами своих соседей.

Вопрос - возможно ли иметь эти 2 аннотации для одного объекта каким-то образом?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...