2010-12-31 5 views
22

Ich versuche, die Zählung der Anzahl, wie oft ein Spieler gespielt jede Woche wie folgt zu erhalten:Verwendung von .aggregate() für einen Wert, der mit .extra (select = {...}) in einer Django-Abfrage eingeführt wurde?

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

Aber Django beschwert sich, dass

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

ich es in rohen SQL wie dies tun können

Gibt es einen guten Weg, dies zu tun, ohne rohe SQL in Django auszuführen?

+0

Sie sollten Ihre Modelle wahrscheinlich zeigen. Arbeitet das QS ohne die Aggregation? –

+1

Ja, 'player.game_objects.extra (select = {'woche': 'WEEK (games_game.date)'}) [0] .week' ergibt' 43L' wie erwartet. – Jake

+0

Meine Modelle sind ziemlich komplex, das ist eine Vereinfachung meines Problems. Wenn es hilft, könnte ich einen Testfall mit einfachen Modellen schreiben. – Jake

Antwort

15

Sie eine benutzerdefinierte Aggregatfunktion verwenden, könnte Ihre Abfrage zu produzieren:

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')) 

Da diese API jedoch nicht dokumentiert ist und bereits Bits von Raw SQL erfordert, ist es möglicherweise besser, eine raw query zu verwenden.

+0

Oh, ich mag den Klang davon. – Jake

+0

Verwenden Sie YEARWEEK statt WEEK, wenn Ihre Reichweite mehrere Jahre umfasst: – Xerion

3

Hier ist ein Beispiel für das Problem und eine ideale Lösung. Nehmen Sie dieses Beispiel Modell:

class Rating(models.Model): 
    RATING_CHOICES = (
     (1, '1'), 
     (2, '2'), 
     (3, '3'), 
     (4, '4'), 
     (5, '5'), 
    ) 
    rating = models.PositiveIntegerField(choices=RATING_CHOICES) 
    rater = models.ForeignKey('User', related_name='ratings_given') 
    ratee = models.ForeignKey('User', related_name='ratings_received') 

Dieses Beispiel Aggregat Abfrage wie bei Ihnen in der gleichen Art und Weise versagt, weil es ein nicht-Feldwert geschaffen zu verweisen versucht, mit .extra().

User.ratings_received.extra(
    select={'percent_positive': 'ratings > 3'} 
).aggregate(count=Avg('positive')) 

Eine Umgehungslösung

Das kann gewünschter Wert direkt unter Verwendung der aggregierten Datenbankfunktion (Avg in diesem Fall) in der Mehrwert-Definition gefunden:

User.ratings.extra(
    select={'percent_positive': 'AVG(rating >= 3)'} 
) 

Diese Abfrage generiert die folgende SQL-Abfrage:

SELECT (AVG(rating >= 3)) AS `percent_positive`, 
     `ratings_rating`.`id`, 
     `ratings_rating`.`rating`, 
     `ratings_rating`.`rater_id`, 
     `ratings_rating`.`ratee_id` 
FROM `ratings_rating` 
WHERE `ratings_rating`.`ratee_id` = 1 

Trotz der nicht benötigten Spalten in dieser Abfrage, können wir immer noch den gewünschten Wert von ihm erhalten, indem die percent_positive Wert zu isolieren:

User.ratings.extra(
    select={'percent_positive': 'AVG(rating >= 3)'} 
).values('percent_positive')[0]['percent_positive'] 
+0

Diese Problemumgehung ist genau, wie ich Dinge getan habe, aber darauf achten, eine leere Rückkehr zu erhalten (dh User.ratings ist leer), da es einen IndexError wirft – jelford

+0

@jelford: Ich denke, es gibt nur keine in diesem Fall zurück, da zumindest in SQLite AVG() einer leeren Menge gibt NULL zurück. –

Verwandte Themen