Эффективный способ Django ORM для аннотирования True в первых вхождениях поля

У меня есть ситуация, когда у нас есть некоторая логика, которая сортирует набор запросов (таблицу) patient_journey по некоторой эвристике. Путешествие_пациента имеет FK к пациенту.

Теперь мне нужен эффективный способ установки True для первого появления patient_journey для данного patient, false в противном случае.

Первым эвристиком является patient_id, поэтому набор запросов уже будет сгруппирован по пациентам.

Алгоритм очень простой и должен быть быстрым, но я застрял на попытке получить что-то <1s.

Я пробовал использовать distinct и проверять существование, но это добавляет 1-2 секунды.

Я пробовал использовать подзапрос с [:1] & test по id, но это еще хуже, около 3-5 секунд дополнительно.

def annotate_primary(
*, qs: 'PatientJourneyQuerySet'  # noqa
) -> 'PatientJourneyQuerySet':  # noqa
    """
    Constraints:
    --------------
    Exactly One non-global primary journey per patient

    Annotations:
    ------------
    is_primary_:Bool, is the primary_journey for a patient
    """
    from patient_journey.models import PatientJourney
    qs = get_sorted_for_primary_journey_qs(qs=qs)
    # cost until now is around 0.17s
    # using distinct on patient_id & checking if id is in there adds around 1.5-2s
    # looking for something faster, i.e. this shoiuld be a straight forward scan.
    qs = qs.annotate(
        # True for first occurrence of a `patient_id` false otherwise
        primary=
    )
    return qs

Предполагая, что модели выглядят следующим образом

from django.db import models

class Patient(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
  
class PatientJourney(models.Model):
    patient = models.ForeignKey(Patient, on_delete=models.CASCADE, related_name='journeys')
    visit_date = models.DateTimeField()
    diagnosis = models.TextField(blank=True, null=True)
    treatment = models.TextField(blank=True, null=True)
    

Используя Window funstions, вы можете получить только первые путешествия

from django.db.models import Window, F
from django.db.models.functions import RowNumber
from .models import PatientJourney

# Annotate each journey with its row number partitioned by patient and ordered by visit_date in ascendin order
patient_journeys_with_row_number = PatientJourney.objects.annotate(
    row_number=Window(
        expression=RowNumber(),
        partition_by=[F('patient')],
        order_by=F('visit_date').asc()
    )
).filter(row_number=1)

for journey in patient_journeys_with_row_number:
    print(f"{journey.patient.first_name} {journey.patient.last_name} - First Journey on {journey.visit_date}")
Вернуться на верх