Question

Projectfundingdetail a une clé étrangère au projet.

La requête suivante me donne la liste de tous les projets qui ont tout projectfundingdetail sous 1000. Comment puis-je limiter à plus tard projectfundingdetail seulement.

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

I ai défini la fonction suivante,

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

Mais je ne peux pas utiliser ce qui suit comme latest_funding est pas un champ de base de données

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

Alors, quelle requête dois-je utiliser pour obtenir tous les projets qui ont seulement leur dernier projectfundingdetail en 1000.

Était-ce utile?

La solution

Cette requête est plus difficile qu'il n'y paraît au premier coup d'œil. Django ORM AFAIK ne fournit aucun moyen de générer du SQL efficace pour cette requête, car le SQL efficace nécessite une sous-requête corrélée. (J'aimerais corriger à ce sujet!) Vous pouvez générer des SQL laid avec cette requête:

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

Mais cela exige de se joindre de Projectfundingdetail au Projet et à nouveau, ce qui est inefficace (mais peut-être suffisant pour vos besoins).

L'autre façon de le faire est d'écrire SQL brute et encapsuler dans une méthode de gestion. Il semble un peu effrayant, mais fonctionne très bien. Si vous attribuez le gestionnaire comme des « objets » attribut Projectfundingdetail, vous pouvez l'utiliser comme ceci pour obtenir les derniers détails de financement pour chaque projet:

>>> Projectfundingdetail.objects.latest_by_project()

Et il retourne une QuerySet normale, de sorte que vous pouvez ajouter d'autres filtres:

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

Voici le code:

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

Environ la moitié de ce code (le « nom » dictionnaire) est seulement nécessaire d'être robuste contre la possibilité de la table de base de données non standard et les noms de colonnes. Vous pouvez aussi simplement hardcode la table et les noms de colonnes dans le SQL si vous êtes sûr qu'ils ne changera jamais.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top