Использование .aggregate() для значения, введенного с использованием .extra(select = {...}) в Django Query?
Я пытаюсь подсчитать количество раз, когда игрок играл каждую неделю следующим образом:
player.game_objects.extra(
select={'week': 'WEEK(`games_game`.`date`)'}
).aggregate(count=Count('week'))
Но Django жалуется, что
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?
Ответы
Ответ 1
Вы можете использовать настраиваемую функцию агрегации для создания запроса:
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, вам может быть лучше использовать необработанный запрос.
Ответ 2
Вот пример проблемы и решение 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']