Django сложные отношения с джойнами

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

class Position(BaseModel):
    name = models.CharField()

class Metric(BaseModel):
    name = models.CharField()

class PositionKPI(BaseModel):
    position = models.ForeignKey(Position)
    metric = models.ForeignKey(Metric)
    expectation = models.FloatField()

class Employee(BaseModel):
    position = models.ForeignKey(Position)

class EmployeeKPI(BaseModel):
    employee = models.ForeignKey(Employee)
    metric = models.ForeignKey(Metric)
    value = models.FloatField()

    def kpi(self):
        return PositionKPI.objects.filter(position=self.employee.position, metric=self.metric).first()

Я считаю, что метод kpi можно переписать в виде отношения. На языке SQL это выглядело бы примерно так:

select
  positionkpi.* 
from employeekpi 
  join employee on employee.id = employeekpi.employee_id
  join positionkpi on positionkpi.id = employee.position_id
      and positionkpi.metric_id = employeekpi.metric_id

Посоветуйте, пожалуйста, как это сделать

Вы можете получить такие записи с помощью F-объекта [Django-doc]:

from django.db.models import F

PositionKPI.objects.filter(employee__position__metric_id=F('metric_id'))

Вы можете попробовать что-то подобное, но в этом случае kpi свойство возвращает значение expectation поля, а не PositionKPI экземпляра

class EmployeeKPIQuerySet(models.QuerySet):
    def with_kpi(self):
        position_kpi_subquery = PositionKPI.objects.filter(
            position=OuterRef('employee__position'),
            metric=OuterRef('metric')
        ).values('expectation')[:1]

        return self.annotate(
            _kpi=Subquery(position_kpi_subquery, output_field=FloatField())
        )


class EmployeeKPI(BaseModel):
    employee = models.ForeignKey(Employee)
    metric = models.ForeignKey(Metric)
    value = models.FloatField()

    objects = models.Manager.from_queryset(EmployeeKPIQuerySet)()

    def kpi(self):
        if hasattr(self, '_kpi'):
            return self._kpi
        return PositionKPI.objects.filter(position=self.employee.position, metric=self.metric).first().expectation



PositionKPI.objects.with_kpi() # instead of PositionKPI.objects.all()
Вернуться на верх