I use the following bit of code in my Django app:
pictures = gallery.picture_set.annotate( score=models.Sum( \'picturevote__value\' ) ).order_by( \'-score\'
Prior to the introduction of annotations, you might have used extra to do something like this, which I think should return 0
in cases where there are no votes (if it doesn't for any particular database implementation, you can at least directly insert the necessary COALESCE
function call - COALESCE(SUM(value), 0)
- using this method):
pictures = gallery.picture_set.extra(
select={
'score': 'SELECT SUM(value) FROM yourapp_picturevote WHERE yourapp_picturevote.picture_id = yourapp_picture.id',
},
order_by=['-score']
)
I can't see any built-in way to add your own SQL to the new annotation stuff (which I haven't personally used yet), but it looks like you should be able to create a new annotation like so:
from django.db.models import aggregates
from django.db.models.sql import aggregates as sql_aggregates
class SumWithDefault(aggregates.Aggregate):
name = 'SumWithDefault'
class SQLSumWithDefault(sql_aggregates.Sum):
sql_template = 'COALESCE(%(function)s(%(field)s), %(default)s)'
setattr(sql_aggregates, 'SumWithDefault', SQLSumWithDefault)
This looks rather ugly as you need to monkeypatch the new aggregate into django.db.models.sql.aggregates
due to the way the SQL aggregate classes are looked up, but all we've done here is added a new aggregate which subclasses Sum
, hardcoding a call to the COALESCE
function and adding a placeholder for the default value, which you must supply as a keyword argument (in this very basic example implementation, at least).
This should let you do the following:
pictures = gallery.picture_set.annotate(score=SumWithDefault('picturevote__value', default=0).order_by('-score')
From Django 1.8, there is a Coalesce database function. Your query might look like this:
from django.db.models.functions import Coalesce
score = self.picturevote_set.aggregate(Coalesce(models.Sum('value'), 0))