【问题标题】:Using .aggregate() on a value introduced using .extra(select={...}) in a Django Query?在 Django 查询中使用 .extra(select={...}) 引入的值上使用 .aggregate()?
【发布时间】:2011-06-01 20:07:57
【问题描述】:

我正在尝试计算玩家每周玩的次数,如下所示:

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 的好方法?

【问题讨论】:

  • 你应该展示你的模型,也许。 QS 是否在没有聚合的情况下工作?
  • 是的,player.game_objects.extra(select={'week': 'WEEK(games_game.date)'})[0].week 按预期提供了43L
  • 我的模型相当复杂,这是我的问题的简化。如果有帮助,我可以用简单的模型编写一个测试用例。
  • 我对答案有几个想法,但它是否有用,以及具体实施它的适当方式取决于您的模型和/或数据库结构的一些细节尝试推断似乎很愚蠢,因为您可以提供它们实际上是什么(同上,Trey)。您能否发布它们(首选)或它们的省略/简化版本,以便我可以尝试一下?
  • 我刚刚发布了一个答案,其中包含一个示例场景,其中这是一个问题,以及一个比使用原始 SQL 更好但仍然不理想的变通解决方案。

标签: python mysql django django-queryset django-aggregation


【解决方案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,您最好使用 raw query

【讨论】:

  • 哦,我喜欢那个声音。
  • 如果您的范围跨越多年,请使用 YEARWEEK 而不是 WEEK:
【解决方案2】:

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

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

【讨论】:

  • 这个解决方法正是我所做的事情,但要小心得到一个空的回报(即 User.ratings 是空的),因为它会抛出一个 IndexError
  • @jelford:我认为在这种情况下它只会返回 None,因为至少在 SQLite AVG() 的空集返回 NULL。
猜你喜欢
  • 2015-10-06
  • 2011-07-08
  • 1970-01-01
  • 1970-01-01
  • 2018-10-24
  • 1970-01-01
  • 1970-01-01
  • 2012-01-25
  • 1970-01-01
相关资源
最近更新 更多