You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django技术问题:如何实现平均值的平均值注解?

问题:Session查询集注解计算谜题平均评分的平均值出错

模型定义

我有Puzzle和Session模型,用户在会话中游玩谜题并评分,结果通过Play模型记录:

class Play(models.Model):
    puzzle = models.ForeignKey(
            'puzzles.Puzzle', on_delete=models.CASCADE, related_name='plays')
    rating = models.IntegerField(default=-1)
    session = models.ForeignKey(Session, blank=True,
                                null=True, on_delete=models.SET_NULL, related_name='plays')

同一谜题可在同一会话中被多次游玩。

需求

我需要给Session.objects添加注解rating_avg_from_session_plays,计算逻辑分为两步:

  • 对会话中每个游玩的谜题,计算该谜题的平均评分(avg_pzl_rating)
  • 对每个会话,计算所有谜题平均评分的平均值,得到会话的最终平均评分

错误的实现代码

我尝试用Subquery和group_by实现,但结果不符合预期——只取了第一个评分的平均值(测试会话得到1.0,正确值应为2.1527777777777777),代码如下:

rating_average_by_puzzle_from_session_plays = Subquery(Play.objects.filter(session_id=OuterRef(
                'id')).values('puzzle_id').order_by('puzzle_id').annotate(
                puzzle_rating_average=Avg('rating')).values('puzzle_rating_average'))
    
Session.objects.annotate(rating_avg_from_session_plays=Avg(rating_average_by_puzzle_from_session_plays))

单个Session对象的正确实现

对单个Session对象执行以下代码能得到正确结果:

session_object.plays.values('puzzle_id').order_by('puzzle_id').annotate(
           puzzle_rating_average=Avg('rating')).aggregate(
           rating_average=Avg('puzzle_rating_average'))['rating_average'] or 0

生成的SQL语句

上述错误代码生成的SQL如下:

SELECT "games_session"."id", "games_session"."datetime", "games_session"."rating_average", 
"games_session"."user_id", AVG((SELECT AVG(U0."rating") AS "puzzle_rating_average"             
FROM "games_play" U0 WHERE U0."session_id" = "games_session"."id" 
GROUP BY U0."puzzle_id")) AS "rating_avg_from_session_plays" FROM "games_session" 
GROUP BY "games_session"."id", "games_session"."datetime", "games_session"."rating_average", 
"games_session"."user_id"

测试数据

测试会话包含31次游玩,涉及12个谜题,数据如下:

[{"model": "games.play","pk": 207,"fields": {"puzzle": 104,"rating": 1,"session": 2}},{"model": "games.play","pk": 208,"fields": {"puzzle": 104,"rating": 1,"session": 2}},{"model": "games.play","pk": 209,"fields": {"puzzle": 104,"rating": 1,"session": 2}},{"model": "games.play","pk": 210,"fields": {"puzzle": 104,"rating": 1,"session": 2}},{"model": "games.play","pk": 211,"fields": {"puzzle": 104,"rating": 1,"session": 2}},{"model": "games.play","pk": 212,"fields": {"puzzle": 104,"rating": 1,"session": 2}},{"model": "games.play","pk": 213,"fields": {"puzzle": 105,"rating": 1,"session": 2}},{"model": "games.play","pk": 214,"fields": {"puzzle": 105,"rating": 1,"session": 2}},{"model": "games.play","pk": 215,"fields": {"puzzle": 106,"rating": 2,"session": 2}},{"model": "games.play","pk": 218,"fields": {"puzzle": 107,"rating": 3,"session": 2}},{"model": "games.play","pk": 220,"fields": {"puzzle": 109,"rating": 3,"session": 2}},{"model": "games.play","pk": 224,"fields": {"puzzle": 113,"rating": 1,"session": 2}},{"model": "games.play","pk": 225,"fields": {"puzzle": 114,"rating": 1,"session": 2}},{"model": "games.play","pk": 232,"fields": {"puzzle": 108,"rating": 2,"session": 2}},{"model": "games.play","pk": 233,"fields": {"puzzle": 110,"rating": 3,"session": 2}},{"model": "games.play","pk": 246,"fields": {"puzzle": 111,"rating": 3,"session": 2}},{"model": "games.play","pk": 247,"fields": {"puzzle": 112,"rating": 2,"session": 2}},{"model": "games.play","pk": 248,"fields": {"puzzle": 115,"rating": 2,"session": 2}},{"model": "games.play","pk": 510,"fields": {"puzzle": 104,"rating": 1,"session": 2}},{"model": "games.play","pk": 511,"fields": {"puzzle": 105,"rating": 2,"session": 2}},{"model": "games.play","pk": 512,"fields": {"puzzle": 106,"rating": 2,"session": 2}},{"model": "games.play","pk": 513,"fields": {"puzzle": 107,"rating": 3,"session": 2}},{"model": "games.play","pk": 514,"fields": {"puzzle": 108,"rating": 3,"session": 2}},{"model": "games.play","pk": 515,"fields": {"puzzle": 109,"rating": 3,"session": 2}},{"model": "games.play","pk": 516,"fields": {"puzzle": 110,"rating": 3,"session": 2}},{"model": "games.play","pk": 517,"fields": {"puzzle": 111,"rating": 3,"session": 2}},{"model": "games.play","pk": 518,"fields": {"puzzle": 112,"rating": 3,"session": 2}},{"model": "games.play","pk": 519,"fields": {"puzzle": 113,"rating": 2,"session": 2}},{"model": "games.play","pk": 520,"fields": {"puzzle": 114,"rating": 1,"session": 2}},{"model": "games.play","pk": 521,"fields": {"puzzle": 115,"rating": 2,"session": 2}},{"model": "games.play","pk": 522,"fields": {"puzzle": 115,"rating": 2,"session": 2}},{"model": "games.session","pk": 2,"fields": {"datetime": "2020-10-28T20:26:52.023Z","rating_average": "2.2"}}]

请问我的代码哪里出错了?


内容的提问来源于stack exchange,提问作者Skratt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 22:09:59