Search code examples
pythonmysqldjangodjango-querysetdjango-aggregation

Using .aggregate() on a value introduced using .extra(select={...}) in a Django Query?


I'm trying to get the count of the number of times a player played each week like this:

player.game_objects.extra(
    select={'week': 'WEEK(`games_game`.`date`)'}
).aggregate(count=Count('week'))

But Django complains that

FieldError: Cannot resolve keyword 'week' into field. Choices are: <lists model fields>

I can do it in raw SQL like this

SELECT WEEK(date) as week, COUNT(WEEK(date)) as count FROM games_game
WHERE player_id = 3
GROUP BY week

Is there a good way to do this without executing raw SQL in Django?


Solution

  • You could use a custom aggregate function to produce your query:

    WEEK_FUNC = 'STRFTIME("%%%%W", %s)' # use 'WEEK(%s)' for mysql
    
    class WeekCountAggregate(models.sql.aggregates.Aggregate):
        is_ordinal = True
        sql_function = 'WEEK' # unused
        sql_template = "COUNT(%s)" % (WEEK_FUNC.replace('%%', '%%%%') % '%(field)s')
    
    class WeekCount(models.aggregates.Aggregate):
        name = 'Week'
        def add_to_query(self, query, alias, col, source, is_summary):
            query.aggregates[alias] = WeekCountAggregate(col, source=source, 
                is_summary=is_summary, **self.extra)
    
    
    >>> game_objects.extra(select={'week': WEEK_FUNC % '"games_game"."date"'}).values('week').annotate(count=WeekCount('pk'))
    

    But as this API is undocumented and already requires bits of raw SQL, you might be better off using a raw query.