在一个查询中从不同表中汇总数字值

4
在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()

如果返回的计数较小,则这很有效,但如果数据库必须在内存中保留大量行以进行计数,则似乎不太好。

1个回答

1
你的解决方案可以通过使用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)(或外部引用如果可行))来延迟评估,但是查询计划可能会更差。

另一个问题是没有表就无法进行SELECT。因此,在查询的基础上,我将使用一个不重要的表的不重要行。

示例:

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文档确认没有简单的解决方案。
在子查询表达式中使用聚合是唯一的方法,因为使用aggregate()尝试评估查询集(如果有OuterRef,则无法解析)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接