【问题标题】:How to generate "empty" aggregate results in SQL如何在 SQL 中生成“空”聚合结果
【发布时间】:2012-05-18 18:12:26
【问题描述】:

我正在尝试优化 SQL 查询以使我的报告看起来更好。我的查询从一个表中读取数据,按几列分组并计算一些聚合字段(计数和总和)。

SELECT A, B, C, COUNT(*), SUM(D) FROM T
GROUP BY A, B, C
ORDER BY A, B, C

现在,假设 B 和 C 列是一些已定义的常量字符串,例如, B 可以是'B1''B2',C 可以是'C1''C2'。因此,一个示例结果集是:

A  | B  | C  | COUNT(*) | SUM(D)
--------------------------------
A1 | B1 | C1 |       34 |   1752
A1 | B1 | C2 |        4 |    183
A1 | B2 | C1 |      199 |   8926
A1 | B2 | C2 |       56 |   2511
A2 | B1 | C2 |        6 |     89
A2 | B2 | C2 |       12 |    231
A3 | B1 | C1 |       89 |    552
...

如您所见,对于'A1',我有所有四种可能的(B,C)组合,但对于'A2',情况并非如此。我的问题是:实际上,在给定的表中,如何生成不存在 (B, C) 组合的摘要行?也就是说,我怎样才能打印,例如,这些行:

A  | B  | C  | COUNT(*) | SUM(D)
--------------------------------
A2 | B1 | C1 |        0 |      0
A2 | B2 | C1 |        0 |      0

我能看到的唯一解决方案是创建一些具有所有 (B, C) 值的辅助表,然后使用该辅助表进行 RIGHT OUTER JOIN。但我正在寻找一种更清洁的方式......

谢谢大家。

【问题讨论】:

  • 你用的是什么mssql、oracle、mysql?
  • 甲骨文。但目前,查询是以标准方式编写的(它也在 MySQL 上运行,我们有一个数据库的 MySQL 副本,没有与问题相关的原因)。
  • 也许this 有帮助。分组集在 SQL Server 和 Oracle 中都可用。不确定 MySQL。
  • @dradu 非常有趣。我以前从未听说过 CUBE 和 GROUPING SETS。也许是因为我主要是 MySQL 用户。无论如何,它无济于事:CUBE 和 GROUPING SETS 都生成任何类型的总计,但不会生成空摘要。
  • 什么是使用右外连接解决问题的“不干净”?这似乎是正确的解决方案。

标签: sql oracle group-by aggregate-functions


【解决方案1】:

辅助表不必是真实的表,它可以是通用的表表达式——至少如果您可以从表本身获取所有可能的值(或所有您感兴趣的值)。使用@Bob Jarvis 的查询生成所有可能的组合,您可以执行以下操作:

WITH CTE AS (
    SELECT * FROM (SELECT DISTINCT a FROM T)
    JOIN (SELECT DISTINCT b, c FROM T) ON (1 = 1)
)
SELECT CTE.A, CTE.B, CTE.C,
    SUM(CASE WHEN T.A IS NULL THEN 0 ELSE 1 END), NVL(SUM(T.D),0)
FROM CTE
LEFT JOIN T ON T.A = CTE.A AND T.B = CTE.B AND T.C = CTE.C
GROUP BY CTE.A, CTE.B, CTE.C
ORDER BY CTE.A, CTE.B, CTE.C;

如果您有可能不在表格中的固定值,那么它会稍微复杂一些(或者更丑陋,并且随着更多可能的值变得更糟):

WITH CTE AS (
    SELECT * FROM (SELECT DISTINCT a FROM T)
    JOIN (SELECT 'B1' AS B FROM DUAL
        UNION ALL SELECT 'B2' FROM DUAL) ON (1 = 1)
    JOIN (SELECT 'C1' AS C FROM DUAL
        UNION ALL SELECT 'C2' FROM DUAL) ON (1 = 1)
)
SELECT CTE.A, CTE.B, CTE.C,
    SUM(CASE WHEN T.A IS NULL THEN 0 ELSE 1 END), NVL(SUM(T.D),0)
FROM CTE
LEFT JOIN T ON T.A = CTE.A AND T.B = CTE.B AND T.C = CTE.C
GROUP BY CTE.A, CTE.B, CTE.C
ORDER BY CTE.A, CTE.B, CTE.C;

但您必须加入了解“缺失”值的某事。如果其他地方需要相同的逻辑,并且您有固定的值,那么永久表可能会更干净——当然,无论哪种方式都可能需要维护。您还可以考虑使用流水线函数充当代理表,但可能取决于卷。

【讨论】:

  • 表格本身包含了所有的可能性。显然,我没想到数据库引擎会“猜测”它们。 +1 WITH 的有趣用法,我以前从未见过。
【解决方案2】:

可能有更漂亮的方法可以做到这一点,但以下内容应该可以帮助您开始实现您想要的:

SELECT * FROM 
  (SELECT DISTINCT a FROM T)
JOIN
  (SELECT DISTINCT b, c FROM T)
  ON (1 = 1)
ORDER BY a, b, c

这将为您提供 B 和 C 的所有组合,以及所有存在的 A,类似于

A1  B1  C1
A1  B1  C2
A1  B2  C1
A1  B2  C2
A2  B1  C1
A2  B1  C2
A2  B2  C1
A2  B2  C2

分享和享受。

【讨论】:

  • 我正在寻找一些不涉及连接的东西(我不会假设它存在!)...我目前正在使用一个像你写的那样的表来实现结果。请参阅我的问题的最后一段。
  • 这里的问题不同。如果表 T 包含所有可能的组合,那么你没问题,但它没有,那么你又回到了第一格。不知何故,数据库引擎需要知道所有可能的组合是什么。
  • @AleksG 好的,好的,我们没有这个问题:表本身(考虑到所有行)包含 (B, C) 值的所有组合。显然,我没想到数据库引擎会“猜测”它们。
【解决方案3】:

问题是,如果您的数据库中没有特定组合,引擎如何知道将该组合包含到结果中?为了在结果中包含所有组合,您需要提供所有组合 - 无论是在主表中还是在用于引用的其他表中。例如,您可以使用如下数据创建另一个表 R:

A  | B  | C  
------------
A1 | B1 | C1
A1 | B1 | C2
A1 | B2 | C1
A1 | B2 | C2
A2 | B1 | C1
A2 | B1 | C2
A2 | B2 | C1
A2 | B2 | C2
A3 | B1 | C1
A3 | B1 | C2
A3 | B1 | C1
A3 | B2 | C2
...

然后您的查询将如下所示:

SELECT r.*, COUNT(t.d), coalesce(SUM(t.d), 0)
FROM r LEFT OUTER JOIN t on (r.a=t.a and r.b=t.b and r.c=t.c)
GROUP BY r.a, r.b, r.c
ORDER BY r.a, r.b, r.c

这将返回你想要的集合,0 | 0 用于主表中不存在的组合。请注意,这只有在您确实知道要包含的所有可能组合时才有可能,但情况并非总是如此。

另一方面,如果您的 A、B、C 是数值,并且您只想包含一个范围内的所有数字,那么可能有另一种处理方式,如下所示:

SELECT a.n, b.n, c.n, COUNT(t.d), coalesce(SUM(t.d), 0)
FROM (SELECT (rownum) "n" FROM DUAL WHERE LEVEL >= start_a CONNECT BY LEVEL <= end_a) a,
     (SELECT (rownum) "n" FROM DUAL WHERE LEVEL >= start_b CONNECT BY LEVEL <= end_b) b,
     (SELECT (rownum) "n" FROM DUAL WHERE LEVEL >= start_c CONNECT BY LEVEL <= end_c) c,
     t
WHERE a.n = t.a(+) AND b.n = t.b(+) AND c.n = t.c(+)
GROUP BY a.n, b.n, c.n
ORDER BY a.n, b.n, c.n

(我没有方便的 Oracle 实例来测试这个,所以这更像是一个有根据的猜测,而不是其他任何东西。)

最重要的是,引擎需要知道最终结果中包含哪些内容 - 一种或另一种方式。

【讨论】:

  • 我现在就是这样做的。请参阅我的问题的最后一段。
  • @lorenzo-s 主要问题是如果没有指定所有有效组合,引擎无法知道,因此您确实需要这个临时表。
  • 表本身(考虑到所有行)包含 (B, C) 值的所有组合。
猜你喜欢
  • 2019-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-02
  • 1970-01-01
相关资源
最近更新 更多