Django ORM Query для ограничения для конкретного экземпляра ключа - PullRequest
1 голос
/ 30 мая 2009

Projectfundingdetail имеет внешний ключ к проекту.

Следующий запрос дает мне список всех проектов, в которых any projectfundingdetail меньше 1000. Как ограничить его только самым последним projectfundingdetail.

projects_list.filter(projectfundingdetail__budget__lte=1000).distinct()

Я определил следующую функцию,

def latest_funding(self):
    return self.projectfundingdetail_set.latest(field_name='end_date')

Но я не могу использовать следующее, поскольку latest_funding не является полем базы данных

projects_list.filter(latest_funding__budget__lte=1000).distinct()

Итак, какой запрос я должен использовать, чтобы получить все проекты, в которых есть только их самый последний проектный ресурс менее 1000.

1 Ответ

3 голосов
/ 30 мая 2009

Этот запрос сложнее, чем кажется на первый взгляд. AFAIK Django ORM не предоставляет никакого способа для генерации эффективного SQL для этого запроса, потому что эффективный SQL требует коррелированного подзапроса. (Я бы хотел исправить это!) Вы можете создать некрасивый SQL с помощью этого запроса:

Projectfundingdetail.objects.annotate(latest=Max('project__projectfundingdetail__end_date')).filter(end_date=F('latest')).filter(budget__lte==1000).select_related()

Но для этого требуется присоединиться от Projectfundingdetail к Project и обратно, что неэффективно (хотя, возможно, и соответствует вашим потребностям).

Другой способ сделать это - написать сырой SQL и инкапсулировать его в методе менеджера. Это выглядит немного страшно, но прекрасно работает. Если вы назначите менеджера в качестве атрибута «объекты» в Projectfundingdetail, вы можете использовать его следующим образом, чтобы получить последние данные о финансировании для каждого проекта:

>>> Projectfundingdetail.objects.latest_by_project()

И он возвращает обычный QuerySet, так что вы можете добавить дополнительные фильтры:

>>> Projectfundingdetail.objects.latest_by_project().filter(budget__lte=1000)

Вот код:

from django.db import connection, models
qn = connection.ops.quote_name

class ProjectfundingdetailManager(models.Manager):
    def latest_by_project(self):
        project_model = self.model._meta.get_field('project').rel.to

        names = {'project': qn(project_model._meta.db_table),
                 'pfd': qn(self.model._meta.db_table),
                 'end_date': qn(self.model._meta.get_field('end_date').column),
                 'project_id': qn(self.model._meta.get_field('project').column),
                 'pk': qn(self.model._meta.pk.column),
                 'p_pk': qn(project_model._meta.pk.column)}

        sql = """SELECT pfd.%(pk)s FROM %(project)s AS p 
                 JOIN %(pfd)s AS pfd ON p.%(p_pk)s = pfd.%(project_id)s
                 WHERE pfd.%(end_date)s =
                     (SELECT MAX(%(end_date)s) FROM %(pfd)s 
                      WHERE %(project_id)s = p.%(p_pk)s)
              """ % names

        cursor = connection.cursor()
        cursor.execute(sql)
        return self.model.objects.filter(id__in=[r[0] for r
                                                 in cursor.fetchall()])

Около половины этого кода (словаря «имен») необходимо только для обеспечения устойчивости к нестандартным именам таблиц и столбцов базы данных. Вы также можете просто жестко закодировать имена таблиц и столбцов в SQL, если вы уверены, что они никогда не изменятся.

...