[一次查询中来自不同表的数值总和

问题描述 投票:4回答:1

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

如果返回的计数很小,此方法很好,但是如果数据库必须在内存中保留很多行以对其进行计数,则这看起来很糟糕。

python django django-orm
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文档确认不存在简单的解决方案。

Using aggregates within a Subquery expression ... ...这是在子查询中执行聚合的唯一方法,因为使用aggregate()尝试评估查询集(并且如果存在OuterRef,将无法解决)。

© www.soinside.com 2019 - 2024. All rights reserved.