在构建QuerySet
带有多个批注的复合体时,我遇到了一个问题,该问题可以通过以下简单设置重现。
这些是模型:
class Player(models.Model):
name = models.CharField(max_length=200)
class Unit(models.Model):
player = models.ForeignKey(Player, on_delete=models.CASCADE,
related_name='unit_set')
rarity = models.IntegerField()
class Weapon(models.Model):
unit = models.ForeignKey(Unit, on_delete=models.CASCADE,
related_name='weapon_set')
使用我的测试数据库,可以获得以下(正确)结果:
Player.objects.annotate(weapon_count=Count('unit_set__weapon_set'))
[{'id': 1, 'name': 'James', 'weapon_count': 23},
{'id': 2, 'name': 'Max', 'weapon_count': 41},
{'id': 3, 'name': 'Bob', 'weapon_count': 26}]
Player.objects.annotate(rarity_sum=Sum('unit_set__rarity'))
[{'id': 1, 'name': 'James', 'rarity_sum': 42},
{'id': 2, 'name': 'Max', 'rarity_sum': 89},
{'id': 3, 'name': 'Bob', 'rarity_sum': 67}]
如果现在将两个批注合并到相同的QuerySet
,我将获得不同的(不准确的)结果:
Player.objects.annotate(
weapon_count=Count('unit_set__weapon_set', distinct=True),
rarity_sum=Sum('unit_set__rarity'))
[{'id': 1, 'name': 'James', 'weapon_count': 23, 'rarity_sum': 99},
{'id': 2, 'name': 'Max', 'weapon_count': 41, 'rarity_sum': 183},
{'id': 3, 'name': 'Bob', 'weapon_count': 26, 'rarity_sum': 113}]
请注意,rarity_sum
现在与以前相比具有不同的值。删除distinct=True
不会影响结果。我还尝试使用此答案中的DistinctSum
函数,在这种情况下,所有函数都设置为(也不准确)。rarity_sum``18
为什么是这样?如何将两个注释同时合并QuerySet
?
编辑 :这是由组合QuerySet生成的sqlite查询:
SELECT "sandbox_player"."id",
"sandbox_player"."name",
COUNT(DISTINCT "sandbox_weapon"."id") AS "weapon_count",
SUM("sandbox_unit"."rarity") AS "rarity_sum"
FROM "sandbox_player"
LEFT OUTER JOIN "sandbox_unit" ON ("sandbox_player"."id" = "sandbox_unit"."player_id")
LEFT OUTER JOIN "sandbox_weapon" ON ("sandbox_unit"."id" = "sandbox_weapon"."unit_id")
GROUP BY "sandbox_player"."id", "sandbox_player"."name"
用于以上结果的数据可在此处获得。
这不是Django ORM的问题,这只是关系数据库的工作方式。当您构建简单的查询集时,例如
Player.objects.annotate(weapon_count=Count('unit_set__weapon_set'))
要么
Player.objects.annotate(rarity_sum=Sum('unit_set__rarity'))
ORM不正是你希望它做什么-加入Player
与Weapon
SELECT "sandbox_player"."id", "sandbox_player"."name", COUNT("sandbox_weapon"."id") AS "weapon_count"
FROM "sandbox_player"
LEFT OUTER JOIN "sandbox_unit"
ON ("sandbox_player"."id" = "sandbox_unit"."player_id")
LEFT OUTER JOIN "sandbox_weapon"
ON ("sandbox_unit"."id" = "sandbox_weapon"."unit_id")
GROUP BY "sandbox_player"."id", "sandbox_player"."name"
或Player
搭配Unit
SELECT "sandbox_player"."id", "sandbox_player"."name", SUM("sandbox_unit"."rarity") AS "rarity_sum"
FROM "sandbox_player"
LEFT OUTER JOIN "sandbox_unit" ON ("sandbox_player"."id" = "sandbox_unit"."player_id")
GROUP BY "sandbox_player"."id", "sandbox_player"."name"
并对其执行COUNT
或SUM
聚合。
请注意,尽管第一个查询在三个表之间具有两个联接,但是中间表Unit
既不在引用的列中SELECT
也不在GROUP BY
子句中。那唯一的作用Unit
在这里踢球是加入Player
同Weapon
。
现在,如果您查看第三个查询集,事情将变得更加复杂。再次,如在第一个查询中一样,联接位于三个表之间,但现在由于存在以下汇总而Unit
被引用:SELECT``SUM``Unit.rarity
SELECT "sandbox_player"."id",
"sandbox_player"."name",
COUNT(DISTINCT "sandbox_weapon"."id") AS "weapon_count",
SUM("sandbox_unit"."rarity") AS "rarity_sum"
FROM "sandbox_player"
LEFT OUTER JOIN "sandbox_unit" ON ("sandbox_player"."id" = "sandbox_unit"."player_id")
LEFT OUTER JOIN "sandbox_weapon" ON ("sandbox_unit"."id" = "sandbox_weapon"."unit_id")
GROUP BY "sandbox_player"."id", "sandbox_player"."name"
这是第二和第三查询之间的关键区别。在第二个查询,要加入Player
到Unit
,所以单Unit
将再次为每个玩家,它引用被列出。
但在第三个查询要加入Player
到Unit
再Unit
至Weapon
,所以不能只有一个Unit
会被列出一次为每个玩家,它的参考,
同时也为每个武器引用Unit
。
让我们看一个简单的例子:
insert into sandbox_player values (1, "player_1");
insert into sandbox_unit values(1, 10, 1);
insert into sandbox_weapon values (1, 1), (2, 1);
一个玩家,一个单位和两个引用相同单位的武器。
确认问题存在:
>>> from sandbox.models import Player
>>> from django.db.models import Count, Sum
>>> Player.objects.annotate(weapon_count=Count('unit_set__weapon_set')).values()
<QuerySet [{'id': 1, 'name': 'player_1', 'weapon_count': 2}]>
>>> Player.objects.annotate(rarity_sum=Sum('unit_set__rarity')).values()
<QuerySet [{'id': 1, 'name': 'player_1', 'rarity_sum': 10}]>
>>> Player.objects.annotate(
... weapon_count=Count('unit_set__weapon_set', distinct=True),
... rarity_sum=Sum('unit_set__rarity')).values()
<QuerySet [{'id': 1, 'name': 'player_1', 'weapon_count': 2, 'rarity_sum': 20}]>
从该示例可以很容易地看出问题是在组合查询中该单元将被列出两次,而引用该单元的每种武器都将被列出一次:
sqlite> SELECT "sandbox_player"."id",
...> "sandbox_player"."name",
...> "sandbox_weapon"."id",
...> "sandbox_unit"."rarity"
...> FROM "sandbox_player"
...> LEFT OUTER JOIN "sandbox_unit" ON ("sandbox_player"."id" = "sandbox_unit"."player_id")
...> LEFT OUTER JOIN "sandbox_weapon" ON ("sandbox_unit"."id" = "sandbox_weapon"."unit_id");
id name id rarity
---------- ---------- ---------- ----------
1 player_1 1 10
1 player_1 2 10
正如@ivissani所提到的,最简单的解决方案之一是为每个聚合编写子查询:
>>> from django.db.models import Count, IntegerField, OuterRef, Subquery, Sum
>>> weapon_count = Player.objects.annotate(weapon_count=Count('unit_set__weapon_set')).filter(pk=OuterRef('pk'))
>>> rarity_sum = Player.objects.annotate(rarity_sum=Sum('unit_set__rarity')).filter(pk=OuterRef('pk'))
>>> qs = Player.objects.annotate(
... weapon_count=Subquery(weapon_count.values('weapon_count'), output_field=IntegerField()),
... rarity_sum=Subquery(rarity_sum.values('rarity_sum'), output_field=IntegerField())
... )
>>> qs.values()
<QuerySet [{'id': 1, 'name': 'player_1', 'weapon_count': 2, 'rarity_sum': 10}]>
产生以下SQL
SELECT "sandbox_player"."id", "sandbox_player"."name",
(
SELECT COUNT(U2."id") AS "weapon_count"
FROM "sandbox_player" U0
LEFT OUTER JOIN "sandbox_unit" U1
ON (U0."id" = U1."player_id")
LEFT OUTER JOIN "sandbox_weapon" U2
ON (U1."id" = U2."unit_id")
WHERE U0."id" = ("sandbox_player"."id")
GROUP BY U0."id", U0."name"
) AS "weapon_count",
(
SELECT SUM(U1."rarity") AS "rarity_sum"
FROM "sandbox_player" U0
LEFT OUTER JOIN "sandbox_unit" U1
ON (U0."id" = U1."player_id")
WHERE U0."id" = ("sandbox_player"."id")
GROUP BY U0."id", U0."name") AS "rarity_sum"
FROM "sandbox_player"
sum 某个列字段相加sum($table, $column, $where) table [string] 表名. column [string] 查询的列. where (optional) [array] WHERE 条件.sum($table, $join, $column, $where) table [string] The table name. join [array] Table
sum 某个列字段相加sum($table, $column, $where) table [string] 表名. column [string] 查询的列. where (optional) [array] WHERE 条件.sum($table, $join, $column, $where) table [string] The table name. join [array] Table
使用和的优点是什么? 有什么不同? 可以一起使用吗?
来源VIP学员反馈的快手面试题【2024/05/15】 推荐阅读文章列表 我的大数据开发学习之路 2024最新大数据开发面试笔记V6.0 大数据开发面经汇总【持续更新】 SQL题目 有一张用户关注表dwd_online_user_follow_dd, 包含用户id(user_id)和关注者id(follower_id) 求相互关注的用户对 答案解析 模拟数据 insert into dwd_onl
问题内容: 我有一个用Java编写的应用程序,我想为其添加一个Flash前端。Flash前端将与独立Flash Player中的Java应用程序在同一台计算机上运行。我需要两个部分之间的双向通信,并且甚至不知道该如何开始。我想可以在两个程序之间打开一个套接字,但是我觉得必须有一个更简单的方法。ActionScript 3.0中的api是否有一个很好的部分,可以让我直接访问java方法,还是必须诉诸
问题内容: 我正在从本教程中学习JPA 。 我在理解以下注释时有些困惑: 可嵌入类型的字段默认为持久性,就像用@Embedded注释一样。 如果可嵌入类型的字段默认为持久性,那么为什么我们需要注释 问题答案: 该注解允许指定一个类,其实例存储为拥有实体的固有部分。此注释 没有属性 。 该注释用于指定其值是一个可嵌入类的实例实体的持久字段或属性。默认情况下,类中指定的列定义适用于拥有实体的表,但是