Pergunta

Projectfundingdetail tem uma chave estrangeira para projeto.

A consulta a seguir dá-me a lista de todos os projetos que têm qualquer projectfundingdetail sob 1000. Como faço para limitá-lo a única última projectfundingdetail.

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

Eu tenho definido a seguinte função,

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

Mas eu não posso usar o seguinte como latest_funding não é um campo de banco de dados

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

Então, o que consulta devo usar para obter todos os projetos que têm apenas o seu mais recente projectfundingdetail sob 1000.

Foi útil?

Solução

Esta consulta é mais difícil do que parece à primeira vista. AFAIK o Django ORM não fornece nenhuma maneira de gerar SQL eficiente para esta consulta, porque o SQL eficiente requer uma subconsulta correlacionada. (Eu adoraria ser corrigido sobre isso!) Você pode gerar algum SQL feio com esta consulta:

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

Mas isso requer a aderir a partir Projectfundingdetail de Projeto e de volta, o que é ineficiente (embora talvez adequada para suas necessidades).

A outra maneira de fazer isso é escrever SQL cru e encapsulá-lo em um método gerente. Parece um pouco assustador, mas funciona muito bem. Se você atribuir o gerente como "objetos" atributo em Projectfundingdetail, você pode usá-lo como este para obter os detalhes mais recentes de financiamento para cada projeto:

>>> Projectfundingdetail.objects.latest_by_project()

E ele retorna um QuerySet normal, para que você possa adicionar mais filtros:

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

Aqui está o código:

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()])

Cerca de metade desse código (o "nomes" dicionário) só é necessário para ser robusto contra a possibilidade de nomes de tabela de banco de dados e coluna fora do padrão. Você também pode apenas codificar os nomes de tabelas e colunas no SQL Se você está confiante de que nunca vai mudar.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top