在 Django 查询中使用 .extra(select={...}) 引入的值上使用 .aggregate() ?

发布于 2024-10-09 19:11:16 字数 506 浏览 5 评论 0原文

我试图像这样获取玩家每周玩的次数:

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

有没有一个好的方法可以在 Django 中执行原始 SQL 而不执行此操作?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

暗恋未遂 2024-10-16 19:11:16

您可以使用自定义聚合函数来生成查询:

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,因此您最好使用 原始查询

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.

请别遗忘我 2024-10-16 19:11:16

这是问题的示例和不理想的解决方案。以这个示例模型为例:

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

Here is an example of the problem and an unideal workaround solution. Take this example model:

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

This example aggregate query fails in the same way as yours because it attempts to reference a non-field value created using .extra().

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

One Workaround Solution

The desired value can be found directly by using the aggregate database function (Avg in this case) within the extra value's definition:

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

This query will generate the following SQL query:

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

Despite the unneeded columns in this query, we can still obtain the desired value from it by isolating the percent_positive value:

User.ratings.extra(
    select={'percent_positive': 'AVG(rating >= 3)'}
).values('percent_positive')[0]['percent_positive']
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文