2009-05-30 7 views
1

Projectfundingdetail dispose d'une clé étrangère à projeter.Django ORM Requête à limiter pour l'occurrence de clé spécifique

La requête suivante me donne la liste de tous les projets qui ont projectfundingdetail sous 1000. Comment puis-je limiter à la dernière version de projet de financement seulement.

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

J'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 projets qui n'ont que leur dernier projet de financement de moins de 1000.

Répondre

3

y est plus difficile qu'il n'y paraît à première vue. AFAIK l'ORM de Django ne fournit aucun moyen de générer un 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 oblige à se joindre au projet de Projectfundingdetail et à nouveau, ce qui est inefficace (mais peut-être suffisant pour vos besoins). L'autre façon de procéder consiste à écrire du code SQL brut et à l'encapsuler dans une méthode de gestion. Ça a l'air un peu effrayant mais ça marche très bien. Si vous attribuez le gestionnaire comme attribut « objets » sur 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 dictionnaire « noms ») est seulement nécessaire d'être robuste contre la possibilité de non-standard table de base de données et noms de colonnes. Vous pouvez également coder en dur les noms des tables et des colonnes dans le SQL si vous êtes sûr qu'ils ne changeront jamais.

+0

Merci beaucoup. Je savais que ça allait être compliqué, merci de le valider. Même pour la requête de jointure multiple initiale, la fonction annotate() n'est disponible que dans la jonction. Pas dans une version marquée. –

+0

Sinon, je pourrais simplement ajouter le champ is_latest dans le modèle qui stocke si le projet est le plus récent et inclure is_latest = true comme autre condition de filtre db. Quelle est la qualité (ou la mauvaise) de cette approche? –

+0

annotate() sera disponible dans la version 1.1 (déjà disponible dans la version 1.1beta). L'approche raw-SQL est, je crois, totalement compatible avec la version 1.0. Et oui, l'approche de dénormalisation serait une autre bonne option à considérer, en fonction de votre balance des écritures vs lectures et vos besoins de performance dans chaque cas. Pas moyen de prendre une bonne décision sans réellement comparer vos cas d'utilisation. –

Questions connexes