Эффективный способ 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}")