2010-12-31 2 views
22

Я пытаюсь получить счетчик числа раз игрок играл каждую неделю, как это:Использование .aggregate() для значения, введенного с использованием .extra (select = {...}) в Django Query?

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

Но Джанго жалуется, что

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

я могу сделать это в необработанном SQL, как это

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

Есть ли хороший способ сделать это без выполнения необработанного SQL в Django?

+0

Вы должны показать свои модели, возможно. Работает ли QS без агрегации? –

+1

Да, 'player.game_objects.extra (select = {'week': 'WEEK (games_game.date)'}) [0] .week' дает' 43L', как ожидалось. – Jake

+0

Мои модели довольно сложны, это упрощение моей проблемы. Если это поможет, я могу написать тестовый пример с простыми моделями. – Jake

ответ

15

Вы можете использовать пользовательские агрегатную функцию, чтобы произвести запрос:

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

Но поскольку этот API недокументирован и уже требует бит необработанного SQL, вам может быть лучше использовать raw query.

+0

О, мне нравится звук этого. – Jake

+0

Используйте YEARWEEK вместо WEEK, если ваш диапазон охватывает несколько лет: – Xerion

3

Вот пример проблемы и решение для решения Unideal. Возьмите этот пример модели:

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

В этом примере совокупный запрос не таким же образом, как ваша, потому что он пытается сослаться на значение без поля, создаваемого с помощью .extra().

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

Один из способов решения

Требуемое значение можно найти непосредственно с помощью агрегатной функции базы данных (Avg в данном случае) в определении дополнительную ценность в:

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

Запрос по этому запросу будет следующим 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 

Несмотря на ненужные столбцы в этом запросе, мы все еще можем получить нужное значение из него путем выделения percent_positive значение:

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

Это обходное решение именно так, как я делал, но будьте осторожны с получением пустого возврата (т. Е. User.ratings пуст), поскольку он будет вызывать IndexError – jelford

+0

@ jelford: я думаю, что он просто возвращает None в этом случае, поскольку по крайней мере, в SQLite AVG() пустого набора возвращает NULL. –

 Смежные вопросы

  • Нет связанных вопросов^_^