2010-12-31 7 views
22

Próbuję uzyskać rachubę, ile razy gracz grał w każdym tygodniu tak:Użycie .aggregate() dla wartości wprowadzonej przy użyciu .extra (select = {...}) w zapytaniu Django?

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

Ale Django narzeka, że ​​

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

mogę zrobić w surowym jak to SQL

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

Czy jest to dobry sposób, aby to zrobić bez wykonywania surowego SQL w Django?

+0

Powinieneś prawdopodobnie pokazać swoje modele. Czy QS działa bez agregacji? –

+1

Tak 'player.game_objects.extra (select = { 'tydzień' 'tydzień (games_game.date)'}) [0] .week' daje' 43L' prawidłowo. – Jake

+0

Moje modele są dość złożone, to jest uproszczenie mojego problemu. Jeśli to pomoże, mogę napisać test z prostymi modelami. – Jake

Odpowiedz

15

można użyć niestandardowego łączną funkcję produkować zapytanie:

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

Ale jak to jest API nieudokumentowane i już wymaga kawałki surowego SQL, może być lepiej wyłączyć za pomocą raw query.

+0

Och, podoba mi się ten dźwięk. – Jake

+0

Użyj YEARWEEK zamiast TYGODNIA, jeśli zakres obejmuje wiele lat: – Xerion

3

Oto przykład problemu i unideal obejście problemu. Weźmy ten przykład modelu:

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

Ten przykład kwerenda agregat nie w taki sam sposób jak ty, ponieważ próbuje odwoływać się do wartości spoza pola tworzone przy użyciu .extra().

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

Jeden Obejście Rozwiązanie

Żądaną wartość można znaleźć bezpośrednio za pomocą funkcji zagregowanego bazy danych (średnio w tym przypadku) w ramach definicji dodatkową wartość w:

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

To zapytanie będzie generować następujące zapytanie SQL:

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 

Pomimo niepotrzebnych kolumn w tym zapytaniu, nadal możemy uzyskać żądaną wartość z niej przez wyodrębnienie wartości percent_positive:

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

To rozwiązanie jest dokładnie tak, jak robiłem rzeczy, ale należy uważać na uzyskanie pusty zwrot (tj User.ratings jest pusta), ponieważ będzie on rzucić IndexError – jelford

+0

@jelford: Myślę, że po prostu zwraca None, w tym przypadku, ponieważ przynajmniej w SQLite AVG() z pustego zestawu zwraca NULL. –