首页 > 解决方案 > 在一个查询中对来自不同表的数值求和

问题描述

在 SQL 中,我可以将两个计数相加,例如

SELECT (
  (SELECT count(*) FROM a WHERE val=42)
  +
  (SELECT count(*) FROM b WHERE val=42)
)

如何使用 Django ORM 执行此查询?

我得到的最接近的是

a.objects.filter(val=42).order_by().values_list('id', flat=True).union(
    b.objects.filter(val=42).order_by().values_list('id', flat=True)
).count()

如果返回的计数很小,这很好用,但如果数据库必须在内存中保存很多行来计算它们,这似乎很糟糕。

标签: pythondjangodjango-orm

解决方案


您的解决方案只能通过values('pk')而不是简化一点values_list('id', flat=True),因为这只会影响输出的一种类型,但是两个查询集的源 SQL 是相同的:

SELECT id FROM a WHERE val=42 UNION SELECT id FROM b WHERE val=42

并且该方法.count()仅围绕子查询进行查询:

SELECT COUNT(*) FROM (... subquery ...)

数据库后端不必将所有值保存在内存中。它也只能数数而忘记。(未检查)

同样,如果你运行一个简单的SELECT COUNT(id) FROM a,它不需要收集id


更大查询中的表单子SELECT count(*) FROM a WHERE val=42查询是不可能的,因为 Django 不对聚合使用惰性求值并立即求值。

评估可以推迟,例如,通过一些只有一个可能值的表达式进行分组,例如GROUP BY (i >= 0)(或者通过外部引用,如果它可以工作),但查询计划可能更糟。

另一个问题是 aSELECT没有表是不可能的。因此,我将在查询的基础上使用不重要表的不重要行。

例子:

qs = Unimportant.objects.filter(pk=unimportant_pk).values('id').annotate(
    total_a=a.objects.filter(val=42).order_by().values('val')
        .annotate(cnt=models.Count('*')).values('cnt'),
    total_b=b.objects.filter(val=42).order_by().values('val')
        .annotate(cnt=models.Count('*')).values('cnt')
)

这不是很好,但它可以很容易地并行化

SELECT
    id,
    (SELECT COUNT(*) AS cnt FROM a WHERE val=42 GROUP BY val) AS total_a,
    (SELECT COUNT(*) AS cnt FROM b WHERE val=42 GROUP BY val) AS total_b
FROM unimportant WHERE id = unimportant_pk

Django docs 确认不存在简单的解决方案。

在子查询表达式中使用聚合
...
...这是在子查询中执行聚合的唯一方法,因为使用 aggregate() 尝试评估查询集(如果有 OuterRef,这将无法解决)。


推荐阅读