Django:计数相关模型,其中相关注释具有特定值并在注释中存储计数(或简称:计数子查询)
Django: Count related model where an annotation on the related has a specific value and store count in an annotation (or simply: count subquery)
我有两个模型 Pick
和 GamePick
。 GamePick
与 Pick
有 ForeignKey
关系,可在 Pick.game_picks
.
上访问
我已经使用自定义查询集和管理器设置了 GamePick
,这样每当我使用管理器 GamePick
检索到 GamePick
时,都会用字段 is_correct
进行注释基于其他字段的值。
现在我想做的是计算有多少正确的 GamePicks
指向特定的 Pick
。
一种简单的方法是使用 Python:
中的方法执行此操作
class Pick(models.Model):
...
def count_correct(self):
return self.game_picks.filter(is_correct=True).count()
到目前为止一切顺利。
但是现在,我想用那个计数来注释每个 Pick
,比如 correct_count
。这样我就可以用 Pick.objects.all().order_by("correct_count")
.
之类的东西来订购 Pick
现在我该怎么做?
我在这里:
correct_game_picks = GamePick.objects.filter(
pick=models.OuterRef("pk"),
is_correct=True
)
picks = Pick.objects.annotate(
correct_count=models.Count(correct_game_picks.values("pk"))
)
这就是 pick.query
给我的:
SELECT
"picks_pick"."id",
"picks_pick"."picker",
"picks_pick"."pot_id",
COUNT((
SELECT U0."id" FROM "picks_gamepick" U0
INNER JOIN "games_game" U1 ON (U0."game_id" = U1."id")
WHERE ((U0."picked_team_id" = U1."winning_team_id") AND U0."pick_id" = "picks_pick"."id")
)) AS "correct_count"
FROM "picks_pick"
GROUP BY "picks_pick"."id", "picks_pick"."picker", "picks_pick"."pot_id"
我不擅长SQL,但看起来应该是正确的
在我的测试中,它 returns 1 应该是 2 正确的 GamePick
属于一个 Pick
.
有人有指点吗?
顺便说一句,如果我删除 .values("pk")
,我会得到这个错误:
E django.db.utils.OperationalError: sub-select returns 5 columns - expected 1
我不确定为什么在我想计算行数时我有多少列很重要。
由于反馈表明在不知道模型的情况下很难调试,这里是:
class Pot(models.Model):
name = models.CharField(max_length=250, null=False, blank=False)
class Team(models.Model):
name = models.CharField(max_length=250, null=False, blank=False)
class Game(models.Model):
teams = models.ManyToManyField(
Team,
related_name="+",
)
winning_team = models.ForeignKey(
Team,
on_delete=models.CASCADE,
related_name="+",
blank=True,
null=True,
)
class Pick(models.Model):
picker = models.CharField(max_length=100, help_text="Name of the person picking")
# This is the method is would like to replace with an annotation
def count_correct_method(self):
return self.game_picks.filter(is_correct=True).count()
class GamePickQueryset(models.QuerySet):
def annotate_is_correct(self):
return self.annotate(
is_correct=models.ExpressionWrapper(
models.Q(picked_team=models.F("game__winning_team")),
output_field=models.BooleanField(),
)
)
class GamePickManager(models.Manager):
def get_queryset(self):
queryset = GamePickQueryset(self.model, using=self._db)
queryset = queryset.annotate_is_correct()
return queryset
GamePickMangerFromQueryset = GamePickManager.from_queryset(GamePickQueryset)
class GamePick(models.Model):
pick = models.ForeignKey(
Pick, on_delete=models.CASCADE, related_name="game_picks", null=True, blank=True
)
game = models.ForeignKey(Game, on_delete=models.CASCADE,
related_name="game_picks")
picked_team = models.ForeignKey(
Team, on_delete=models.CASCADE, related_name="+", null=True, blank=False
)
objects = GamePickMangerFromQueryset()
使用这些模型,我 运行 这是我试图让注释正常工作的测试
team_1 = Team(name="Test Team 1")
team_1.save()
team_2 = Team(name="Test Team 2")
team_2.save()
team_3 = Team(name="Test Team 3")
team_3.save()
team_4 = Team(name="Test Team 4")
team_4.save()
team_5 = Team(name="Test Team 5")
team_5.save()
team_6 = Team(name="Test Team 6")
team_6.save()
assert Team.objects.count() == 6
pot = Pot(name="Test Pot")
pot.save()
assert Pot.objects.count() == 1
assert Pot.objects.first() == pot
game_1 = Game(pot=pot)
game_1.save()
game_1.teams.add(team_1, team_2)
game_1.winning_team = team_1
game_1.save()
game_2 = Game(pot=pot)
game_2.save()
game_2.teams.add(team_3, team_4)
game_2.winning_team = team_3
game_2.save()
game_3 = Game(pot=pot)
game_3.save()
game_3.teams.add(team_5, team_6)
game_3.winning_team = team_5
game_3.save()
assert Game.objects.count() == 3
assert pot.games.count() == 3
assert pot.games.all()[0].winning_team == team_1
assert pot.games.all()[1].winning_team == team_3
assert pot.games.all()[2].winning_team == team_5
pick = Pick(picker="Tester", pot=pot)
pick.save()
assert Pick.objects.count() == 1
game_pick_1 = GamePick(pick=pick, game=game_1, picked_team=team_1)
game_pick_1.save()
game_pick_2 = GamePick(pick=pick, game=game_2, picked_team=team_3)
game_pick_2.save()
game_pick_3 = GamePick(pick=pick, game=game_3, picked_team=team_6)
game_pick_3.save()
assert GamePick.objects.count() == 3
assert pick.game_picks.count() == 3
assert pick.game_picks.all()[0].is_correct == True
assert pick.game_picks.all()[1].is_correct == True
assert pick.game_picks.all()[2].is_correct == False
assert pick.count_correct() == 2
from django.db import models
correct_game_picks = GamePick.objects.filter(
pick=models.OuterRef("pk"),
is_correct=True,
)
pick = Pick.objects.all().annotate(
correct_count=models.Count(
# models.Q(game_picks__in=correct_game_picks)
models.Q(game_picks__picked_team=models.F("game_picks__game__winning_team"))
)
)[0]
assert pick.correct_count == 2
在这个测试中我得到 3 == 2
。出于某种原因,它正在计算所有 game_picks
而不仅仅是满足表达式的
真的不知道该怎么办了...
我刚刚意识到(感谢@BradMeinsberger),因为我正在做那个 __in
表达式,所以我真的不需要 OuterRef
.
所以注释可以是这样的:
correct_game_picks = GamePick.objects.filter(
is_correct=True,
)
pick = Pick.objects.all().annotate(
correct_count=models.Count(
models.Q(game_picks__in=correct_game_picks)
)
)[0]
但现在更重要的是:没有 OuterRef
我可以单独评估正确的游戏选择:
assert correct_game_picks.count() == 2
assert pick.correct_count == 2
第一个断言通过,但第二个断言没有 3 == 2
2个列表中怎么可能有2个以上?
是否发生了某种重复?
现在我可以通过 distinct=True
进入 Count
并通过
让我们测试另一个组合,例如只有 1 个正确的游戏选择:
game_pick_1 = GamePick(pick=pick, game=game_1, picked_team=team_1)
game_pick_1.save()
game_pick_2 = GamePick(pick=pick, game=game_2, picked_team=team_4)
game_pick_2.save()
game_pick_3 = GamePick(pick=pick, game=game_3, picked_team=team_6)
game_pick_3.save()
assert GamePick.objects.count() == 3
assert pick.game_picks.count() == 3
assert pick.game_picks.all()[0].is_correct == True
assert pick.game_picks.all()[1].is_correct == False
assert pick.game_picks.all()[2].is_correct == False
assert pick.count_correct() == 1
from django.db import models
correct_game_picks = GamePick.objects.filter(
is_correct=True,
)
pick = Pick.objects.all().annotate(
correct_count=models.Count(
models.Q(game_picks__in=correct_game_picks),
distinct=True
)
)[0]
assert correct_game_picks.count() == 1
assert pick.correct_count == 1
2 == 1
刚刚收到!
我想我把它弄得比需要的更复杂了。
correct_game_picks = GamePick.objects.filter(
pick=models.OuterRef("pk"),
is_correct=True
)
picks = Pick.objects.annotate(
correct_count=models.Count(
models.Q(game_picks__in=correct_game_picks)
)
)
和结果 SQL:
SELECT
"picks_pick"."id",
"picks_pick"."picker",
"picks_pick"."pot_id",
COUNT(
"picks_gamepick"."id" IN (
SELECT U0."id" FROM "picks_gamepick" U0
INNER JOIN "games_game" U1 ON (U0."game_id" = U1."id")
WHERE ((U0."picked_team_id" = U1."winning_team_id") AND U0."pick_id" = "picks_pick"."id"))
) AS "correct_count"
FROM "picks_pick"
LEFT OUTER JOIN "picks_gamepick" ON ("picks_pick"."id" = "picks_gamepick"."pick_id")
GROUP BY "picks_pick"."id", "picks_pick"."picker", "picks_pick"."pot_id"
这个看似无关的博客 post 我在搜索“Django subquery count”时遇到了一个正确的方向:
https://mattrobenolt.com/the-django-orm-and-subqueries/
不行。 以上都不行。出于某种原因,它只计算游戏选择的数量...... ♂️
猜想正确查看文档总是有帮助的:
correct_game_picks = GamePick.objects.filter(
is_correct=True,
)
picks = Pick.objects.all().annotate(
correct_count=models.Count(
"game_picks", # The field to count needs to be mentioned specifically
filter=models.Q(game_picks__in=correct_game_picks), # ... and you can define a filter to limit the number of rows in the aggregate
distinct=True. # Prevent duplicates! Important for counting rows
)
)
聚合过滤器就是要查找的内容:https://docs.djangoproject.com/en/3.2/ref/models/querysets/#aggregate-filter
这是生成的SQL:
SELECT
"picks_pick"."id",
"picks_pick"."picker",
"picks_pick"."pot_id",
COUNT(
DISTINCT "picks_gamepick"."id"
) FILTER (
WHERE "picks_gamepick"."id" IN (
SELECT U0."id" FROM "picks_gamepick" U0
INNER JOIN "games_game" U1 ON (U0."game_id" = U1."id")
WHERE (U0."picked_team_id" = U1."winning_team_id")
)
)
AS "correct_count"
FROM "picks_pick"
LEFT OUTER JOIN "picks_gamepick" ON ("picks_pick"."id" = "picks_gamepick"."pick_id")
GROUP BY "picks_pick"."id", "picks_pick"."picker", "picks_pick"."pot_id"
在 SQL 中,您在 COUNT 聚合中生成的子查询正在加入一个 games_game table,这在您的问题中没有其他任何地方。看起来这样做是为了确定选择是否正确,在您问题的其他地方,您在 GamePick 上有一个名为 is_correct 的列用于此。
假设您有 is_correct 列并忽略 games_game table
,您将如何做到这一点
from django.db.models import Subquery, OuterRef, Count
subquery = GamePick.objects.filter(
pick=OuterRef('id'),
is_correct=True
).values(
'pick_id' # Necessary to get the proper group by
).annotate(
count=Count('pk')
).values(
'id' # Necessary to select only one column
)
picks = Pick.objects.annotate(correct_count=Subquery(subquery))
您可以使用 django-sql-utils 包获得同样的东西。 pip install django-sql-utils
然后
from sql_util.utils import SubqueryCount
from django.db.models import Q
subquery = SubqueryCount('game_pick', filter=Q(is_correct=True))
picks=Pick.objects.annotate(correct_count=subquery)
如果您需要使用 games_game table 来确定选择是否正确,我认为您可以将 is_correct=True
(在以上两个示例中)替换为
game__winning_team_id=F('picked_team_id')
我不是 100% 确定,因为我看不到那些 models/columns。
我有两个模型 Pick
和 GamePick
。 GamePick
与 Pick
有 ForeignKey
关系,可在 Pick.game_picks
.
我已经使用自定义查询集和管理器设置了 GamePick
,这样每当我使用管理器 GamePick
检索到 GamePick
时,都会用字段 is_correct
进行注释基于其他字段的值。
现在我想做的是计算有多少正确的 GamePicks
指向特定的 Pick
。
一种简单的方法是使用 Python:
中的方法执行此操作class Pick(models.Model):
...
def count_correct(self):
return self.game_picks.filter(is_correct=True).count()
到目前为止一切顺利。
但是现在,我想用那个计数来注释每个 Pick
,比如 correct_count
。这样我就可以用 Pick.objects.all().order_by("correct_count")
.
Pick
现在我该怎么做?
我在这里:
correct_game_picks = GamePick.objects.filter(
pick=models.OuterRef("pk"),
is_correct=True
)
picks = Pick.objects.annotate(
correct_count=models.Count(correct_game_picks.values("pk"))
)
这就是 pick.query
给我的:
SELECT
"picks_pick"."id",
"picks_pick"."picker",
"picks_pick"."pot_id",
COUNT((
SELECT U0."id" FROM "picks_gamepick" U0
INNER JOIN "games_game" U1 ON (U0."game_id" = U1."id")
WHERE ((U0."picked_team_id" = U1."winning_team_id") AND U0."pick_id" = "picks_pick"."id")
)) AS "correct_count"
FROM "picks_pick"
GROUP BY "picks_pick"."id", "picks_pick"."picker", "picks_pick"."pot_id"
我不擅长SQL,但看起来应该是正确的
在我的测试中,它 returns 1 应该是 2 正确的 GamePick
属于一个 Pick
.
有人有指点吗?
顺便说一句,如果我删除 .values("pk")
,我会得到这个错误:
E django.db.utils.OperationalError: sub-select returns 5 columns - expected 1
我不确定为什么在我想计算行数时我有多少列很重要。
由于反馈表明在不知道模型的情况下很难调试,这里是:
class Pot(models.Model):
name = models.CharField(max_length=250, null=False, blank=False)
class Team(models.Model):
name = models.CharField(max_length=250, null=False, blank=False)
class Game(models.Model):
teams = models.ManyToManyField(
Team,
related_name="+",
)
winning_team = models.ForeignKey(
Team,
on_delete=models.CASCADE,
related_name="+",
blank=True,
null=True,
)
class Pick(models.Model):
picker = models.CharField(max_length=100, help_text="Name of the person picking")
# This is the method is would like to replace with an annotation
def count_correct_method(self):
return self.game_picks.filter(is_correct=True).count()
class GamePickQueryset(models.QuerySet):
def annotate_is_correct(self):
return self.annotate(
is_correct=models.ExpressionWrapper(
models.Q(picked_team=models.F("game__winning_team")),
output_field=models.BooleanField(),
)
)
class GamePickManager(models.Manager):
def get_queryset(self):
queryset = GamePickQueryset(self.model, using=self._db)
queryset = queryset.annotate_is_correct()
return queryset
GamePickMangerFromQueryset = GamePickManager.from_queryset(GamePickQueryset)
class GamePick(models.Model):
pick = models.ForeignKey(
Pick, on_delete=models.CASCADE, related_name="game_picks", null=True, blank=True
)
game = models.ForeignKey(Game, on_delete=models.CASCADE,
related_name="game_picks")
picked_team = models.ForeignKey(
Team, on_delete=models.CASCADE, related_name="+", null=True, blank=False
)
objects = GamePickMangerFromQueryset()
使用这些模型,我 运行 这是我试图让注释正常工作的测试
team_1 = Team(name="Test Team 1")
team_1.save()
team_2 = Team(name="Test Team 2")
team_2.save()
team_3 = Team(name="Test Team 3")
team_3.save()
team_4 = Team(name="Test Team 4")
team_4.save()
team_5 = Team(name="Test Team 5")
team_5.save()
team_6 = Team(name="Test Team 6")
team_6.save()
assert Team.objects.count() == 6
pot = Pot(name="Test Pot")
pot.save()
assert Pot.objects.count() == 1
assert Pot.objects.first() == pot
game_1 = Game(pot=pot)
game_1.save()
game_1.teams.add(team_1, team_2)
game_1.winning_team = team_1
game_1.save()
game_2 = Game(pot=pot)
game_2.save()
game_2.teams.add(team_3, team_4)
game_2.winning_team = team_3
game_2.save()
game_3 = Game(pot=pot)
game_3.save()
game_3.teams.add(team_5, team_6)
game_3.winning_team = team_5
game_3.save()
assert Game.objects.count() == 3
assert pot.games.count() == 3
assert pot.games.all()[0].winning_team == team_1
assert pot.games.all()[1].winning_team == team_3
assert pot.games.all()[2].winning_team == team_5
pick = Pick(picker="Tester", pot=pot)
pick.save()
assert Pick.objects.count() == 1
game_pick_1 = GamePick(pick=pick, game=game_1, picked_team=team_1)
game_pick_1.save()
game_pick_2 = GamePick(pick=pick, game=game_2, picked_team=team_3)
game_pick_2.save()
game_pick_3 = GamePick(pick=pick, game=game_3, picked_team=team_6)
game_pick_3.save()
assert GamePick.objects.count() == 3
assert pick.game_picks.count() == 3
assert pick.game_picks.all()[0].is_correct == True
assert pick.game_picks.all()[1].is_correct == True
assert pick.game_picks.all()[2].is_correct == False
assert pick.count_correct() == 2
from django.db import models
correct_game_picks = GamePick.objects.filter(
pick=models.OuterRef("pk"),
is_correct=True,
)
pick = Pick.objects.all().annotate(
correct_count=models.Count(
# models.Q(game_picks__in=correct_game_picks)
models.Q(game_picks__picked_team=models.F("game_picks__game__winning_team"))
)
)[0]
assert pick.correct_count == 2
在这个测试中我得到 3 == 2
。出于某种原因,它正在计算所有 game_picks
而不仅仅是满足表达式的
真的不知道该怎么办了...
我刚刚意识到(感谢@BradMeinsberger),因为我正在做那个 __in
表达式,所以我真的不需要 OuterRef
.
所以注释可以是这样的:
correct_game_picks = GamePick.objects.filter(
is_correct=True,
)
pick = Pick.objects.all().annotate(
correct_count=models.Count(
models.Q(game_picks__in=correct_game_picks)
)
)[0]
但现在更重要的是:没有 OuterRef
我可以单独评估正确的游戏选择:
assert correct_game_picks.count() == 2
assert pick.correct_count == 2
第一个断言通过,但第二个断言没有 3 == 2
2个列表中怎么可能有2个以上?
是否发生了某种重复?
现在我可以通过 distinct=True
进入 Count
并通过
让我们测试另一个组合,例如只有 1 个正确的游戏选择:
game_pick_1 = GamePick(pick=pick, game=game_1, picked_team=team_1)
game_pick_1.save()
game_pick_2 = GamePick(pick=pick, game=game_2, picked_team=team_4)
game_pick_2.save()
game_pick_3 = GamePick(pick=pick, game=game_3, picked_team=team_6)
game_pick_3.save()
assert GamePick.objects.count() == 3
assert pick.game_picks.count() == 3
assert pick.game_picks.all()[0].is_correct == True
assert pick.game_picks.all()[1].is_correct == False
assert pick.game_picks.all()[2].is_correct == False
assert pick.count_correct() == 1
from django.db import models
correct_game_picks = GamePick.objects.filter(
is_correct=True,
)
pick = Pick.objects.all().annotate(
correct_count=models.Count(
models.Q(game_picks__in=correct_game_picks),
distinct=True
)
)[0]
assert correct_game_picks.count() == 1
assert pick.correct_count == 1
2 == 1
刚刚收到!
我想我把它弄得比需要的更复杂了。
correct_game_picks = GamePick.objects.filter(
pick=models.OuterRef("pk"),
is_correct=True
)
picks = Pick.objects.annotate(
correct_count=models.Count(
models.Q(game_picks__in=correct_game_picks)
)
)
和结果 SQL:
SELECT
"picks_pick"."id",
"picks_pick"."picker",
"picks_pick"."pot_id",
COUNT(
"picks_gamepick"."id" IN (
SELECT U0."id" FROM "picks_gamepick" U0
INNER JOIN "games_game" U1 ON (U0."game_id" = U1."id")
WHERE ((U0."picked_team_id" = U1."winning_team_id") AND U0."pick_id" = "picks_pick"."id"))
) AS "correct_count"
FROM "picks_pick"
LEFT OUTER JOIN "picks_gamepick" ON ("picks_pick"."id" = "picks_gamepick"."pick_id")
GROUP BY "picks_pick"."id", "picks_pick"."picker", "picks_pick"."pot_id"
这个看似无关的博客 post 我在搜索“Django subquery count”时遇到了一个正确的方向: https://mattrobenolt.com/the-django-orm-and-subqueries/
不行。 以上都不行。出于某种原因,它只计算游戏选择的数量...... ♂️
猜想正确查看文档总是有帮助的:
correct_game_picks = GamePick.objects.filter(
is_correct=True,
)
picks = Pick.objects.all().annotate(
correct_count=models.Count(
"game_picks", # The field to count needs to be mentioned specifically
filter=models.Q(game_picks__in=correct_game_picks), # ... and you can define a filter to limit the number of rows in the aggregate
distinct=True. # Prevent duplicates! Important for counting rows
)
)
聚合过滤器就是要查找的内容:https://docs.djangoproject.com/en/3.2/ref/models/querysets/#aggregate-filter
这是生成的SQL:
SELECT
"picks_pick"."id",
"picks_pick"."picker",
"picks_pick"."pot_id",
COUNT(
DISTINCT "picks_gamepick"."id"
) FILTER (
WHERE "picks_gamepick"."id" IN (
SELECT U0."id" FROM "picks_gamepick" U0
INNER JOIN "games_game" U1 ON (U0."game_id" = U1."id")
WHERE (U0."picked_team_id" = U1."winning_team_id")
)
)
AS "correct_count"
FROM "picks_pick"
LEFT OUTER JOIN "picks_gamepick" ON ("picks_pick"."id" = "picks_gamepick"."pick_id")
GROUP BY "picks_pick"."id", "picks_pick"."picker", "picks_pick"."pot_id"
在 SQL 中,您在 COUNT 聚合中生成的子查询正在加入一个 games_game table,这在您的问题中没有其他任何地方。看起来这样做是为了确定选择是否正确,在您问题的其他地方,您在 GamePick 上有一个名为 is_correct 的列用于此。
假设您有 is_correct 列并忽略 games_game table
,您将如何做到这一点from django.db.models import Subquery, OuterRef, Count
subquery = GamePick.objects.filter(
pick=OuterRef('id'),
is_correct=True
).values(
'pick_id' # Necessary to get the proper group by
).annotate(
count=Count('pk')
).values(
'id' # Necessary to select only one column
)
picks = Pick.objects.annotate(correct_count=Subquery(subquery))
您可以使用 django-sql-utils 包获得同样的东西。 pip install django-sql-utils
然后
from sql_util.utils import SubqueryCount
from django.db.models import Q
subquery = SubqueryCount('game_pick', filter=Q(is_correct=True))
picks=Pick.objects.annotate(correct_count=subquery)
如果您需要使用 games_game table 来确定选择是否正确,我认为您可以将 is_correct=True
(在以上两个示例中)替换为
game__winning_team_id=F('picked_team_id')
我不是 100% 确定,因为我看不到那些 models/columns。