2010-09-26 9 views
8

Tengo el siguiente modelo de Django (asignada a la tabla 'A'):Django consulta - "caso cuando" con función de agregación

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

quiero realizar la siguiente consulta sencilla en la parte superior:

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 ... 

Estoy tratando de evitar SQL sin procesar - ¿Cómo se puede implementar esto con django (en el ejemplo anterior estoy usando avg, pero la misma pregunta también es relevante para max, min, suma, etc.)?

He intentado utilizar agregado extra y:

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

y

aggregate(Avg('avg_field')), 

pero la función de agregado sólo funciona con campos del modelo de manera que el campo adicional no se puede utilizar aquí. ¿Cómo se puede hacer esto con django?

Gracias por la ayuda

Respuesta

2

lo que puede hacerse que todavía nos permitirá usar Django queryset es algo como esto:

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

utilizar el resultado:

qs[0]["avg_field"] 

Y esto permitiría a los necesitados funcionalidad.

2

Por lo que yo sé que es (desgraciadamente) no hay manera de hacer lo que usted describe, sin recurrir a SQL prima.

Dicho allí es una forma de calcular el promedio de la forma en que describes si quieres desnormalizar un poco tus datos. Por ejemplo, puede agregar una nueva columna llamada average_field que se establece automáticamente en el valor apropiado en save(). Puede anular save() o tocar una señal para hacer esto automáticamente. Por ej.

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) 

Una vez hecho esto, su consulta se vuelve muy fácil.

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

Gracias. Lamentablemente, no puedo almacenar estos valores en la tabla de la base de datos, ya que los valores de "case when" pueden variar en función del cálculo que deseo realizar. – Lin

+0

@Li: entendido. Puedo ver por qué esto no funcionaría en ese caso. –

+0

Gracias de nuevo por la ayuda. Finalmente, el uso de valores extra + me dio el resultado que quería. – Lin

Cuestiones relacionadas