在包含 NULL 的列上使用 GROUP BY WITH ROLLUP

Adr*_*ach 6 mysql null group-by

我有下表(在 SQL Fiddle 上查看)(我创建是为了解决我的问题):

| ID | word    |
----------------
| 5  | "Hello" |
| 6  |  NULL   |
| 7  | "World" |
| 8  | "World" |
Run Code Online (Sandbox Code Playgroud)

现在我想使用GROUP BY word WITH ROLLUP. ROLLUP 生成的行的列 word 中的 NULL 应替换为“total”:

SELECT
  ID,
  ifnull(word, "total") as word,
  count(*) as occurrences
FROM test
GROUP BY word WITH ROLLUP;
Run Code Online (Sandbox Code Playgroud)

问题是它还会用NULL单词为 NULL 的行数替换记录中的 :

| ID |  word | occurrences |
|----|-------|-------------|
|  6 | total |           1 | <- Here lies the problem
|  5 | Hello |           1 |
|  7 | world |           2 |
|  7 | total |           4 |
Run Code Online (Sandbox Code Playgroud)

所以我现在count(word)用来区分它是我的数据中的 NULL 还是 ROLLUP 创建的 NULL 以便我可以用“空”或“总计”替换它:

SELECT
  ID,
  if(count(word) = 0, "empty", ifnull(word, "total")) as word,
  count(*) as occurrences
FROM test
group by word with ROLLUP;
Run Code Online (Sandbox Code Playgroud)

这适用于我的用例,但它仍然有一个缺陷:如果单词在所有行中都是 NULL,count(word)则在显示总数的最后一行中也是 0:( SQL Fiddle with different data (only record#6) )

| ID |  word | occurrences |  
|----|-------|-------------|  
|  6 | empty |           1 |  
|  6 | empty |           1 |  <- word should be "total"
Run Code Online (Sandbox Code Playgroud)

以下语句提供了我想要的结果:

SELECT
  ID,
  ifnull(word, "total") as word,
  count(*) as occurrences
FROM
(
SELECT
  ID,
  ifnull(word, "empty") as word
FROM test
) tmp
GROUP BY word WITH ROLLUP
Run Code Online (Sandbox Code Playgroud)

但是因为在这里的大多数答案中似乎不鼓励使用子查询,所以我想知道是否有更好的解决方案。

jyn*_*nus 4

你有一个“解决方案”,所以说

我想知道是否有更好的解决方案。

我想你想对此发表评论。我重点讲几个方面:

  1. SELECT ID [...] GROUP BY word,无论是否有 aWITH ROLLUP都是错误的。您正在选择一个值未确定的字段。特别是,使用 MySQL,您会遇到返回随机值的情况;或者如果您使用严格模式(推荐,并且在最新版本的 MySQL 中默认),查询将失败:“db_9_b0ff7.test.ID”不在 GROUP BY 中

  2. 无论正式与否,WITH ROLLUP注意-(我指的是 MySQL 实现)在某些情况下是很好的,因为它避免了对表的双重重新扫描,但我在生产代码中还没有看到很多。这没有什么问题,但它们是 MSSQL 独有的功能,直到它在 SQL1999 上实现为止,并且要么在当时没有广泛使用,要么像MySQL 一样,它是一个有限的实现。参考: https: //www.percona.com/blog/2007/09/17/using-group-by-with-rollup-for-reporting-performance-optimization/

  3. 原则上我没有看到你的最终查询有问题,如果我们对两个查询(没有 ID)运行解释,我们会看到:

    MariaDB [test]> EXPLAIN SELECT   if(count(word) = 0, "empty", ifnull(word, "total")) as word,   count(*) as occurrences FROM test group by word with ROLLUP\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: test
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 4
            Extra: Using filesort
    1 row in set, 1 warning (0.00 sec)
    
    Warning (Code 1052): Column 'word' in group statement is ambiguous
    MariaDB [test]> EXPLAIN SELECT   ifnull(word, "total") as word,   count(*) as occurrences FROM ( SELECT   ifnull(word, "empty") as word FROM test ) tmp GROUP BY word WITH ROLLUP\G
    *************************** 1. row ***************************
               id: 1
      select_type: SIMPLE
            table: test
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 4
            Extra: Using filesort
    1 row in set, 1 warning (0.01 sec)
    
    Run Code Online (Sandbox Code Playgroud)

    通常对 MySQL 的憎恨是因为它对 MySQL 早期版本的限制。我并不是说它现在已经很完美了,仍然会有问题,因为 MySQL 的开发一如既往,专注于快速、易入、易出的 RDBMS,而不是成熟的关系引擎,但是在正确的位置使用子查询是可以的(毕竟,无论如何,有些查询都需要子查询。您的用法是可以的- 它只会产生再次遍历汇总行的开销,我想这是真正的交易,与全套相比,这些并不重要。

    注意-在我的实例上,子查询没有显示,但您可以在 5.6 中通过具体化子查询看到它:http://sqlfiddle.com/#! 9/b0ff7/7(它将执行内部查询,然后外部部分,没有问题)。索引绝对可以在这里提供帮助(由于文件排序),但这取决于最终的记录数。如果索引(现在不存在)无法使用,则子查询可能会损害性能。您必须测试您的特定 mysql 版本并创建这样的索引。

  4. 我认为你的问题在于数据模型 - 在 SQL 世界(Oracle 之外)中,NULL 是一个未知/无效值。似乎将其与“空”相关联,这是不正确的 - 如果已知一个单词不存在,则它应该是“”(空字符串,而不是 NULL)。我不知道你是否可以改变这一点,但我不喜欢你当前的模型。我知道你可能对此无话可说,所以我不会重点讨论这一点。Alos 你计算 NULL 单词吗?使用 '' 而不是 NULL 的更多理由。
  5. 现在最后一个问题是,是否可以在 MySQL 上不使用子查询来完成此操作,并且它会更好/不那么难看吗?我真的没有看到一种方法- 我们可以进行连接,但它仍然是子查询。我可以想到一个替代方案,那就是:

    SELECT * FROM (
        SELECT   word,   
        count(*) as occurrences 
        FROM test 
        GROUP BY word with rollup) 
    tmp 
    ORDER BY occurrences;
    
    Run Code Online (Sandbox Code Playgroud)

    这确保最后一行包含摘要(如果有其他空值,则它是实际的空值)。即使在只有空值的情况下也会发生该顺序。相反,它可能会更慢,因为它需要排序,并且您可能再次需要一个索引,并且该索引可能会也可能不会被使用。