在 MySQL 中使用 WITH ROLLUP 时如何过滤掉小计?

问题描述

我正在尝试使用 GROUP ... WITH ROLLUP 来获取多个分组值的一系列总计,但没有小计。因此,在我人为的示例中,我有一个fruits,如下所示:

SELECT * FROM fruits ORDER BY fruit;
+----+----------+------+-------+
| id | fruit    | size | price |
+----+----------+------+-------+
|  1 | apple    |    3 |  4.99 |
|  2 | apple    |    4 |  5.99 |
|  3 | apple    |    6 |  2.99 |
|  8 | apple    |    3 |  3.00 |
|  9 | apple    |    4 |  6.50 |
|  4 | banana   |    2 |  3.50 |
|  5 | banana   |    3 |  2.10 |
|  6 | banana   |    1 |  8.99 |
|  7 | banana   |    7 |  6.99 |
| 10 | banana   |    3 |  3.50 |
| 14 | banana   |    7 |  8.00 |
| 11 | cucumber |    1 |  1.50 |
| 12 | cucumber |    1 |  2.30 |
| 13 | cucumber |    2 |  3.30 |
+----+----------+------+-------+

假设我想按 fruitsize 对水果表进行分组,并在底部显示平均价格。我可以使用 GROUP WITH ROLLUP 来实现这一点:

SELECT fruit,size,AVG(price) FROM fruits GROUP BY fruit,size WITH ROLLUP;
+----------+------+------------+
| fruit    | size | AVG(price) |
+----------+------+------------+
| apple    |    3 |   3.995000 |
| apple    |    4 |   6.245000 |
| apple    |    6 |   2.990000 |
| apple    | NULL |   4.694000 |
| banana   |    1 |   8.990000 |
| banana   |    2 |   3.500000 |
| banana   |    3 |   2.800000 |
| banana   |    7 |   7.495000 |
| banana   | NULL |   5.513333 |
| cucumber |    1 |   1.900000 |
| cucumber |    2 |   3.300000 |
| cucumber | NULL |   2.366667 |
| NULL     | NULL |   4.546429 |
+----------+------+------------+

但我不想要所有这些小计行...我只想要最后一行,其中 fruitsize显示为 NULL。换句话说,这是:

+----------+------+------------+
| fruit    | size | AVG(price) |
+----------+------+------------+
| apple    |    3 |   3.995000 |
| apple    |    4 |   6.245000 |
| apple    |    6 |   2.990000 |
| banana   |    1 |   8.990000 |
| banana   |    2 |   3.500000 |
| banana   |    3 |   2.800000 |
| banana   |    7 |   7.495000 |
| cucumber |    1 |   1.900000 |
| cucumber |    2 |   3.300000 |
| NULL     | NULL |   4.546429 |
+----------+------+------------+

我无法访问 MysqL 8.0,因此我无法使用 GROUPING() 函数 described in the docs 来确定行中的值是表示小计还是总计。所以我想我必须过滤掉 NULL 值。

我试着像这样适应this answer to a similar question

SELECT fruit,size WITH ROLLUP HAVING size IS NOT NULL or fruit IS NULL;
+----------+------+------------+
| fruit    | size | AVG(price) |
+----------+------+------------+
| apple    |    3 |   3.995000 |
| apple    |    4 |   6.245000 |
| apple    |    6 |   2.990000 |
| banana   |    1 |   8.990000 |
| banana   |    2 |   3.500000 |
| banana   |    3 |   2.800000 |
| banana   |    7 |   7.495000 |
| cucumber |    1 |   1.900000 |
| cucumber |    2 |   3.300000 |
+----------+------+------------+

不过,正如您所看到的,这不会产生我想要的结果 - 它只是完全砍掉了总计。我知道我只想获取大小为非空且水果为空或不为空的行,但是我尝试测试 NULL 的各种方法没有按预期工作:

SELECT fruit,AVG(price),fruit IS NULL,fruit IS NOT NULL,ISNULL(fruit),fruit = "" FROM fruits GROUP BY fruit,size WITH ROLLUP;
+----------+------+------------+---------------+-------------------+---------------+------------+
| fruit    | size | AVG(price) | fruit IS NULL | fruit IS NOT NULL | ISNULL(fruit) | fruit = "" |
+----------+------+------------+---------------+-------------------+---------------+------------+
| apple    |    3 |   3.995000 |             0 |                 1 |             0 |          0 |
| apple    |    4 |   6.245000 |             0 |                 1 |             0 |          0 |
| apple    |    6 |   2.990000 |             0 |                 1 |             0 |          0 |
| apple    | NULL |   4.694000 |             0 |                 1 |             0 |          0 |
| banana   |    1 |   8.990000 |             0 |                 1 |             0 |          0 |
| banana   |    2 |   3.500000 |             0 |                 1 |             0 |          0 |
| banana   |    3 |   2.800000 |             0 |                 1 |             0 |          0 |
| banana   |    7 |   7.495000 |             0 |                 1 |             0 |          0 |
| banana   | NULL |   5.513333 |             0 |                 1 |             0 |          0 |
| cucumber |    1 |   1.900000 |             0 |                 1 |             0 |          0 |
| cucumber |    2 |   3.300000 |             0 |                 1 |             0 |          0 |
| cucumber | NULL |   2.366667 |             0 |                 1 |             0 |          0 |
| NULL     | NULL |   4.546429 |             0 |                 0 |             0 |       NULL |
+----------+------+------------+---------------+-------------------+---------------+------------+

这些结果都没有任何意义。我会假设 fruit is NULLISNULL(fruit) 会在水果的值实际上为 NULL 时返回 1。但他们没有。所以我想知道这是否与空字符串和 null 之间的区别有关……但我不明白为什么,当我测试 fruit = "" 时,当水果为 NULL 时它返回 NULL。>

我应该使用什么条件来测试 NULL,以便我可以从我的表中排除我想要的结果?

解决方法

将您的聚合查询用作子查询和过滤器:

SELECT *
FROM (
  SELECT fruit,size,AVG(price) avg_price
  FROM fruits 
  GROUP BY fruit,size 
  WITH ROLLUP
) t
WHERE (fruit IS NOT NULL AND size IS NOT NULL)
   OR (fruit IS NULL AND size IS NULL)

你也可以不用子查询,使用 HAVING 子句:

SELECT fruit,AVG(price) avg_price
FROM fruits 
GROUP BY fruit,size 
WITH ROLLUP
HAVING (fruit IS NOT NULL AND size IS NOT NULL)
    OR (fruit IS NULL AND size IS NULL)

参见demo

,

不需要子查询。您可以使用 having 子句:

select fruit,avg(price)
from fruits
group by fruit,size with rollup
having fruit is not null and size is not null or
       fruit is null and size is null;

还有其他表达having的方式,例如:

having (fruit is not null) = (size is not null)

Here 是一个 dbfiddle。