Django Count 和 Sum 注释相互干扰

2024-03-17

在建设综合体的同时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函数来自这个答案 https://stackoverflow.com/questions/4371550/django-and-aggregate-sum-of-distinct-values/54278155#54278155,在这种情况下所有rarity_sum被设置为18(也不准确)。

为什么是这样?如何将两个注释组合在同一个注释中QuerySet?

Edit:这是由组合的 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"

上述结果所使用的数据是可以在这里找到 https://pastebin.com/qf5e6vnS.


这不是 Django ORM 的问题,这只是关系数据库的工作方式。当您构建简单的查询集时,例如

Player.objects.annotate(weapon_count=Count('unit_set__weapon_set'))

or

Player.objects.annotate(rarity_sum=Sum('unit_set__rarity'))

ORM 完全按照您的预期行事 - 加入Player with 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"

or Player with 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 or SUM对它们进行聚合。

请注意,虽然第一个查询在三个表之间有两个联接,但中间表Unit既不在中引用的列中SELECT,也不在GROUP BY条款。唯一的角色是Unit在这里玩就是加入Player with 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 to Unit,所以单个Unit将为它引用的每个玩家列出一次。

但在第三个查询中您要加入Player to Unit进而Unit to 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"
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Django Count 和 Sum 注释相互干扰 的相关文章

随机推荐

  • “在源 Y 中找不到事件 ID X 的描述。”

    我正在尝试将自定义事件从我的 Web 应用程序写入 Windows 事件日志 我没有运气让消息字符串正常工作 我不断收到 无法找到源 Y 中事件 ID X 的描述 为了缩小范围 我决定将一个事件写入我的机器上已存在的源 我只是查看了已经写出
  • Nginx 配置中静态位置的多个位置

    我的应用程序将在两个位置提供静态文件 一个是 my path project static 另一个是 my path project jsutils static 我很难让网络服务器在两个目录中查找静态内容 这是我的应用程序的 nginx
  • UICollectionView 动画(插入/删除项目)

    我想在插入和 或删除 UICollectionViewCell 时自定义动画样式 我需要这个的原因是 默认情况下 我看到插入单元格的动画具有平滑的淡入淡出效果 但是删除单元格具有向左移动 淡出动画的组合 如果不是因为一个问题 我会对此感到非
  • 使用具有多个比较器的比较器

    我可以使用此代码中的所有简单比较器进行排序 但不能ComplexComparator 我不知道如何编码才能使其正常工作 任何建议 解释将不胜感激 这是我的主要程序 package pkgTest import java util Array
  • 如何使用 Elasticsearch 编写条件查询?

    我有一个简单的 Elasticsearch JSON 查询 如下所示 query bool must match id 1 must match tags name a1 仅当值 本例中为 a1 不为空时 如何执行第二个 必须 条件 您可以
  • IBM Blockchain Marble 演示中的交易存储在哪里?

    我正在运行下面链接中提到的 IBM 提供的区块链演示 https github com IBM Blockchain marbles https github com IBM Blockchain marbles 我在一个 Linux 系统
  • 如何从 Firestore 查询中排除元素?

    我有一个用户集合 我想从数据库中查询所有用户并将它们显示在RecyclerView除了一个 mine 这是我的数据库架构 users collection uid document uid fR5bih7SysccRu2Gu9990TeSS
  • 如何像对象一样操作Json响应?

    我的 jQuery ajax 返回 JSon 对象 我首先阅读了其他文章 但他们的回复文字不喜欢我的 我的回复内容 来自萤火虫响应 item country USA lan EN country Turkiye lan TR 现在我试图提醒
  • 通过内省打印 ctypes“Structure”的所有字段

    test c include
  • 在 XAML (WPF) 中将图标放入文本框中

    我想将一个小图标 一个 png 文件 放在文本框的一角 我的意思的草图位于http dosketch com view php k mIPeHLSBLx0e2YpoVpYO http dosketch com view php k mIPe
  • 创建自定义链轮处理器的文档?

    我正在尝试为 Rails 创建一个链轮预处理器 它会发现 png rb资产管道中的文件 并使用它们生成我的应用程序中各个页面的 png 屏幕截图 我已经阅读了很多关于这个主题的内容 但我似乎找不到任何关于如何进行此设置的简单文档 请帮助 这
  • 在 Active Record 中使用 find() 和多个 where 子句

    我想将 Active Record 查询后的内容 使用括号 分为 3 组 第一组是从第一个 Where 子句到最后一个 orWhere 第二个和第三个将使用 andWhere 请给我关于如何使用括号分隔所有 3 个部分的建议 query B
  • UICollectionView 不调用委托方法

    我已经设置了一个UICollectionView在故事板中并连接数据源和委托出口 我已经通过笔尖注册了一个单元格 我将其出队cellForItemAtIndexPath 方法 所有工作都完美地期望委托方法永远不会被调用 例如 当触摸一个单元
  • 如何在 Javascript 中实现应用模式

    Javascript 中引用函数调用模式的应用调用模式是什么 我该如何使用它 使用这种调用模式有什么好处 指某东西的用途apply与函数上下文相关 this关键字 和参数传递 首先 我想你应该知道在什么情况下this关键字是隐含地 set
  • 如何从 ~/.aws/config 加载配置

    变更日志 https github com aws aws sdk js blob master CHANGELOG md 2440 says Load config from aws config if AWS SDK LOAD CONF
  • ListField 在编辑/创建帖子中显示

    我正在一个项目中使用 Flask mongoengine 我正在尝试从中获取基本的东西http docs mongodb org manual tutorial write a tumblelog application with flas
  • 如何在MASM中为一个项目编写和组合多个源文件?

    对于组装来说还是个新手 玩起来很有趣 我想将程序的功能拆分到多个文件中 特别是通过将类似的功能分组在一起进行组织 这些其他文件将由主文件 甚至希望其他非主文件 调用 我还没有成功 希望得到帮助 我不使用 IDE 更喜欢使用 notepad
  • Excel VBA 用于匹配和排列行

    我有一个 Excel 文档 其中包含 A 到 J 列 K 到 N 列包含相关数据 但未对齐 我需要将 F 列中的值与 K 列中的值进行匹配 以便它们对齐 当我移动K时 我必须一起移动L M N 我无法对 A 到 J 列进行排序 它们必须保留
  • JWT 计算签名 SHA256withRSA

    我试图 使用 SHA256withRSA 对输入的 UTF 8 表示形式进行签名 也可以 称为带有 SHA 256 哈希函数的 RSASSA PKCS1 V1 5 SIGN 从API控制台获取的私钥 输出将是 字节数组 所以让我们将 Hea
  • Django Count 和 Sum 注释相互干扰

    在建设综合体的同时QuerySet通过几个注释 我遇到了一个可以通过以下简单设置重现的问题 以下是型号 class Player models Model name models CharField max length 200 class