Запрос Django ORM для ограничения для конкретного экземпляра ключа

StackOverflow https://stackoverflow.com/questions/929468

Вопрос

Projectfundingdetail имеет внешний ключ для project.

Следующий запрос выдает мне список всех проектов, которые имеют Любой объем финансирования проекта составляет менее 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()

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

Это было полезно?

Решение

Этот запрос сложнее, чем кажется на первый взгляд.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 и инкапсулировать его в метод manager.Это выглядит немного пугающе, но работает отлично.Если вы назначите менеджеру атрибут "объекты" в Projectfundingdetail, вы можете использовать его следующим образом, чтобы получать последние сведения о финансировании для каждого проекта:

>>> Projectfundingdetail.objects.latest_by_project()

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

>>> 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, если вы уверены, что они никогда не изменятся.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top