2010-09-26 1 views
8

J'ai le modèle suivant django (correspondance avec la table 'A'):requête Django - "cas où" avec la fonction d'agrégation

class A(models.Model): 
    name = models.CharField(max_length=64, null=False) 
    value = models.IntegerField() 
    ... 

Je veux effectuer la requête simple suivante au-dessus:

select avg(case 
     when (value > 0 and value <= 50) then 0 
     when (value > 50 and value < 70) then 50 
     else 100 end) 
from A 
where ... 

J'essaie d'éviter le SQL brut - Comment cela peut-il être implémenté avec django (dans l'exemple ci-dessus j'utilise avg, mais la même question est également pertinente pour max, min, sum etc.)?

J'ai essayé avec supplémentaire et global:

extra(select={'avg_field': case_when_query}) 

et

aggregate(Avg('avg_field')), 

mais la fonction globale fonctionne uniquement avec les champs de modèle de sorte que le champ supplémentaire ne peut être utilisé ici. Comment cela peut-il être fait avec django?

Merci pour l'aide

Répondre

2

Que peut-on faire, qui va encore nous permettre d'utiliser django queryset est quelque chose comme ceci:

qs = A.objects.extra(select={"avg_field": 
        "avg(case when...)"}).filter(...).values("avg_field") 

Pour utiliser le résultat:

qs[0]["avg_field"] 

Et cela permettrait au besoin fonctionnalité.

2

Pour autant que je sais qu'il est (malheureusement) aucun moyen de faire ce que vous avez décrit sans avoir recours à SQL brut. Cela dit, est un moyen de calculer la moyenne de la façon dont vous décrivez si vous êtes désireux de dénormaliser un peu vos données. Par exemple, vous pouvez ajouter une nouvelle colonne appelée average_field qui est automatiquement définie sur la valeur appropriée sur save(). Vous pouvez ignorer save() ou appuyer sur un signal pour le faire automatiquement. Par exemple

class A(models.Model): 
    name = models.CharField(max_length=64, null=False) 
    value = models.IntegerField() 
    average_field = models.IntegerField(default = 0) 

    def _get_average_field(self): 
     # Trying to match the case statement's syntax. 
     # You can also do 0 < self.value <= 50 
     if self.value > 0 and self.value <= 50: 
      return 0 
     elif self.value > 50 and self.value < 70: 
      return 50 
     else: 
      return 100 

    def save(self, *args, **kwargs): 
     if self.value: 
      self.average_field = self._get_average_field() 
     super(A, self).save(*args, **kwargs) 

Une fois que vous faites cela, votre requête devient très facile.

A.objects.filter(...).aggregate(avg = Avg('average_field')) 
+0

Merci. Malheureusement, je ne peux pas stocker ces valeurs dans la table de base de données, car les valeurs de "cas quand" peuvent varier en fonction du calcul que je veux effectuer. – Lin

+0

@Li: compris. Je peux voir pourquoi cela ne fonctionnerait pas dans ce cas. –

+0

Merci encore pour l'aide. Finalement, en utilisant des valeurs extra + m'a obtenu le résultat que je voulais. – Lin