代码之家  ›  专栏  ›  技术社区  ›  phihag

在一个查询中对不同表中的数值求和

  •  2
  • phihag  · 技术社区  · 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()
    

    如果返回的计数很小,那么这种方法可以很好地工作,但是如果数据库必须在内存中保存很多行才能对它们进行计数,那么这种方法就很糟糕了。

    0 回复  |  直到 4 年前
        1
  •  1
  •   hynekcer    4 年前

    您的解决方案只能通过 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 docs确认简单解决方案不存在。

    Using aggregates within a Subquery expression
    ...
    ... 这是在子查询中执行聚合的唯一方法,因为使用aggregate()尝试计算queryset(如果存在OuterRef,则无法解析)。