2

我正在使用django-eztables对数据表进行服务器端处理,并且我想添加一些涉及聚合的字段(如此处所述

以下每个单独工作正常:

def get_queryset(self):
qs = super(SomeObjectDataTableView, self).get_queryset()
return qs.select_related().annotate(items_count=Count('items'))

def get_queryset(self):
qs = super(SomeObjectDataTableView, self).get_queryset()
return qs.select_related().annotate(total_sum=Sum('anotherobject__differentobject__total'))

但是,如果我尝试以任何一种顺序,以相同的顺序annotate()或一个接一个地链接,它们最终都会得到相同的数字,这是它们各自应该是的乘积。

如果我添加distinct=True到 Count,它会产生正确的值,但它仍然会使 Sum 给出一个膨胀的值。(同样,更改顺序没有帮助,并且 Sum 不带distinct参数)

我在 SO 上看到了几个类似的问题,但大多数似乎都涉及多个计数,可以使用distinct=True. 有一个有一个 Sum,但解决方案涉及使用extra()和一些手工制作的 SQL,到目前为止,我无法适应我需要做的所有外键遍历(我使用了一点 SQL,但是我绝不是专家)。这是相关模型的基本设置,以防万一extra()是唯一可行的解​​决方案:

  • Item 有一个指向 SomeObject 的外键
  • AnotherObject 有一个指向 DifferentObject 的外键和一个指向 SomeObject 的外键

如果有人知道如何解决这些问题并在我的查询集上获得两个注释,将不胜感激。

4

1 回答 1

1

Django 对于如何将关系转换为 SQL 是不透明的……而 SQL 通常解释了 Django 输出中的潜在问题。采用如下数据结构:

成人桌(姓名)

  • 鲍勃
  • ...

子表(父、子)

  • 鲍勃,约翰
  • 鲍勃,史黛西
  • ...

宠物桌(主人,宠物)

  • 鲍勃,猫
  • 鲍勃,狗

假设你想要一张桌子

  • 姓名
  • # 孩子们
  • # 宠物

注释是使用 JOIN 实现的。如果您尝试使用两个计数进行注释,Django 将运行以下查询(伪 SQL,以防读者不知道 SQL):

SELECT Adult's Name, Count of Children, Count of Pets 
FROM Adult 
JOIN Child (where the adult is the parent) 
JOIN Pet (where the adult is the owner)

不幸的是,这将导致 Bob 出现以下行:

Bob, John, Cat
Bob, John, Dog
Bob, Stacy, Cat
Bob, Stacy, Dog

如果您只计算 Child 和 Pet,您将得到两个计数的乘积 (4 = 2 x 2)。在您看到的示例中,这是通过在每个计数中包含 DISTINCT 值以消除重复项来解决的。

在您的情况下,您从相关对象收集总数,而不是像宠物这样明确命名的对象:

Bob, John, 250
Bob, John, 100
Bob, Stacy, 250
Bob, Stacy, 100

即使你可以让 DISTINCT 在一个简单的情况下工作,它也会非常危险,因为两个相关对象的总数可能完全相同。DISTINCT 调用将消除这两个值之一,即使两者都是有效的。从技术上讲,这种方法也可能会破坏命名值。例如,如果您想计算一条街道上的宠物数量,如果其中两只宠物的名字相同,则 DISTINCT 可能会提供错误的答案。

在 Django 中直接实现期望的结果很困难,因为在 SQL 中很难:

  • “最简单”的方法是运行完整查询,但在使用值时将 SUM 除以 COUNT。您可以在上面的示例中看到为什么这有效......因为将针对其他计数值的每个实例列出整个总数。
  • 如何实现这一点取决于您的情况:
    • 我想你想使用额外的()。 这个问题的答案向您展示了如何使用 extra() 组合两个聚合值而不使用原始 SQL。我认为(但尚未测试)您可以轻松地将总和除以计数。
    • 您可以手动遍历值并运行第二个查询。由于您使用的是数据表,我认为这是不可行的。
    • 根据您的 Datatable 框架,您可能还可以在定义或显示 Datatable 时执行此操作。

这可能并不详尽,但第一个非常严格,所以我没有费心去想其他人。

于 2014-08-03T15:58:38.393 回答