开心一刻

  今天,小区有个很漂亮的姑娘出嫁

  我对儿子说:你要好好学习,认真写作业,以后才能娶到这么漂亮的老婆

  儿子好像听明白了,思考了一会,默默的收起了作业本

  然后如释重负的跟我说到:爸,我以后还是不娶老婆了

环境准备

  后文要讲的重点是标准 SQL ,与具体的数据库没关系,所以理论上来讲,所有的关系型数据库都应该支持

  但理论是理论,事实是事实,大家需要结合当下的实际情况来看问题

  关系型数据库很多,后文主要基于 MySQL 8.0.30 来讲解,偶尔会插入 PostgreSQL 14.1 ,没有特殊说明的情况下,都是基于 MySQL 8.0.30

   MySQL 建表 tbl_ware ,并初始化数据

CREATE TABLE `tbl_ware` (
`ware_id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '商品id',
`ware_name` VARCHAR(100) NOT NULL COMMENT '商品名称',
`ware_category` VARCHAR(100) NOT NULL COMMENT '商品类别',
`sale_unit_price` INT COMMENT '销售单价',
`purchase_unit_price` INT COMMENT '进货单价',
`registration_date` DATE COMMENT '登记日期',
PRIMARY KEY (`ware_id`) USING BTREE
) ENGINE=InnoDB COMMENT='产品';

   PostgreSQL 建表 tbl_ware ,并初始化数据

CREATE TABLE tbl_ware (
ware_id INT PRIMARY KEY,
ware_name VARCHAR(100) NOT NULL,
ware_category VARCHAR(100) NOT NULL,
sale_unit_price INT,
purchase_unit_price INT,
registration_date DATE
); INSERT INTO tbl_ware VALUES
(1, 'T恤衫', '衣服', 100, 50, '2023-12-11'),
(2, '打孔器', '办公用品', 25, 10, '2023-12-13'),
(3, '运动T恤', '衣服', 150, 50, '2023-12-10'),
(4, '菜刀', '厨房用具', 75, 30, '2023-12-15'),
(5, '高压锅', '厨房用具', 600, 200, '2023-12-15'),
(6, '叉子', '厨房用具', 7, 3, NULL),
(7, '菜板', '厨房用具', 98, 30, '2023-12-12'),
(8, '圆珠笔', '办公用品', 5, 2, '2023-12-15'),
(9, '带帽卫衣', '衣服', 150, 90, NULL),
(10, '砍骨刀', '厨房用具', 150, 69, '2023-12-13'),
(11, '羽绒服', '衣服', 800, 200, NULL);

小计与合计

  关于 小计与合计 ,大家肯定不会陌生,甚至很熟悉

  或多或少都实现过这样的功能,尤其是涉及到报表统计的时候, 小计与合计 是绕不过去的坎

  那有哪些实现方式了,我们今天就来盘一盘

  GROUP BY + 应用程序汇总

  先通过数据库层面的 GROUP BY 得到小计,类似如下

  然后通过程序代码对 商品类别 的小计进行一个合计

  我敢断定,这种方式肯定是大家用的最多的方式,因为我就是这么用的!

  但是,如果加个限制条件:只用 SQL

  此时如何实现小计和合计,各位该如何应对?

  是不是有面试內味了?

  GROUP BY + UNION ALL

  直接上 SQL

  这个 SQL ,大家都能看懂,我就不做过多解释了

  补充问下,用 UNION 可以吗

  答案是可以的,但由于两条 SELECT 语句的聚合键不同,一定不会出现重复行,可以使用 UNION ALL

   UNION ALL 和 UNION 的不同之处在于它不会对结果进行排序,所以它有更好的性能

  就从结果而言,是不是只用 SQL 实现了 小计与合计 ?

  但是,这可恶的 但是 来了

  执行 2 次几乎相同的 SELECT 语句,再将其结果进行连接,你们不觉得繁琐吗?

  在我看来不仅繁琐,效率也会因为繁琐而低下

  面试官又会接着问了:在只用 SQL 的前提下,有没有更合适的实现方法?

  此时,各位又该如何应对?

  ROLLUP

  我就不卖关子了,直接上绝招

  斗胆问一句,这算实现了吗?

  可能有小伙伴会说:这不能算实现了,没看到那么明显的 Null 吗?

  如果非要较真的话,这么说也有道理,但是假若我们在展现层(比如前端)将 Null 当 合计 处理了?

  为什么我不说在后端将 Null 处理成 合计 ?

  如果我们在后端将 Null 处理成 合计 ,为什么不直接用方式: GROUP BY + 应用程序汇总 ?

  不过, Null 看着着实不爽,关键是坑还多:神奇的 SQL 之温柔的陷阱 → 三值逻辑 与 NULL !

  那就把它干掉,调整下 SQL

  这下完美了吧,从结果上来看是完美了

  但从整体上来看,我觉得还不够完美,主要有 2 点

  1、 WITH ROLLUP 是 MySQL 的独有写法

     ROLLUP 的标准写法是 GROUP BY ROLLUP(列名1,列名2,...) ,例如在 PostgreSQL 实现小计与合计

    主流的关系型数据库( Oracle 、 SQL Server 、 DB2 、 PostgreSQL )都是按 SQL 标准来实现的

    唯独 MySQL 没有按标准来,她发挥了她的小任性,用 WITH ROLLUP 坚持了自己的个性

  2、 GROUPING 、 ROLLUP ,你认识吗

    这是本文的重点(呼应开头了),请继续往下看

    你们不要怀疑我是不是在套娃,请把怀疑去掉,我就是在套娃!

GROUPING

  考虑到 MySQL 8.0.30 不支持 CUBE 和 GROUPING SETS ,所以后面的 SQL 都基于 PostgreSQL 14.1

   GROUPING 不会单独使用,往往会结合 ROLLUP 、 CUBE 和 GROUPING SETS 其中之一来使用

  ROLLUP

  关于 ROLLUP ,前面已经演示了一个案例

  

   商品类别 值 NULL 的那一行,没有聚合键,也就相当于没有 GROUP BY 子句,这时会得到全部数据的 合计行

  该合计行记录称为 超级分组记录(super group row) ,虽然听上去很屌,但还是希望大家把它当做未使用 GROUP BY 的 合计行 来理解

  正是因为 合计行 的 ware_category 列的键值不明确,所以会默认使用 NULL

  前面的案例只有一个聚合列,如果再加一列 registration_date ,会是什么结果?

  就问你们看的懵不懵?

  反正我有 2 点比较懵:

    1、每一行记录的含义是什么?

    2、这么多 Null ,分别表示什么

  关于懵点 1,如果大家细看的话,还是能看明白每一行记录的含义的

  至此,相信大家对 ROLLUP 的作用有一定感觉了

  总结下, ROLLUP 作用就如其名一样,能够得到像从小计到合计,从最小的聚合级开始,聚合单位逐渐扩大的结果

  GROUP BY ROLLUP(ware_category) 时,那么结果就是以 ware_category 归类的 小计 加上这些 小计 的 合计 ,一共 3 + 1 = 4 条记录

  GROUP BY ROLLUP(ware_category,registration_date) 时,那么结果就是以 ware_category,registration_date 归类的 小计 加上 GROUP BY ROLLUP(ware_category) 的结果,一共 9 + 4 = 13 条记录

  如果聚合列有 3 列,大家还能明白每一行记录的含义吗

  关于懵点 2, Null 看着确实难受,关键是难以区分:到底是值是 Null ,还是超级分组记录的 Null

  所以为了避免混淆, SQL 标准就规定用 GROUPING 函数来判断超级分组记录的 NULL

  如果 GROUPING 函数的值是 1,则表示是超级分组记录,0 则表示其他情况

  我们调整下 SQL

SELECT
CASE WHEN GROUPING(ware_category) = 1
THEN '商品类别 合计'
ELSE ware_category
END AS ware_category,
CASE WHEN GROUPING(registration_date) = 1
THEN '登记日期 合计'
ELSE TO_CHAR(registration_date, 'YYYY-MM-DD')
END AS registration_date,
SUM(purchase_unit_price) AS purchase_unit_prices
FROM tbl_ware
GROUP BY ROLLUP(ware_category,registration_date)
ORDER BY ware_category DESC, registration_date;

  这样看着是不是清晰很多?

  CUBE

  语法和 ROLLUP 一样,我们直接看案例

SELECT
CASE WHEN GROUPING(ware_category) = 1
THEN '商品类别 合计'
ELSE ware_category
END AS ware_category,
CASE WHEN GROUPING(registration_date) = 1
THEN '登记日期 合计'
ELSE TO_CHAR(registration_date, 'YYYY-MM-DD')
END AS registration_date,
SUM(purchase_unit_price) AS purchase_unit_prices
FROM tbl_ware
GROUP BY CUBE(ware_category,registration_date)
ORDER BY ware_category DESC, registration_date;

  与 ROLLUP 的结果相比, CUBE 结果多了几行记录,而这几行记录就是 GROUP BY(registration_date) 的聚合记录

  所谓 CUBE ,就是将 GROUP BY 子句中的聚合键的 所有可能组合 的聚合结果集中到一个结果集中的功能

  因此,组合的个数就 2 的 n 次方(n 是聚合键的个数)

  本例中,聚合键有 2 个( ware_category,registration_date ),所以组合个数就是 2 的 2 次方,即 4 个

  如果再添加 1 个变为 3 个聚合键的话,那么组合的个数就是 2 的 3 次方,即 8 个

  反观 ROLLUP ,组合个数就是 n + 1

  提个疑问, ROLLUP 的结果一定包含在 CUBE 的结果之中吗?

  GROUPING SETS

  该运算符主要用于从 ROLLUP 或者 CUBE 的结果中取出部分记录

  例如,如果希望从 GROUP BY CUBE(ware_category,registration_date) 的结果中选出 商品类别 和 登记日期 各自作为聚合键的结果

  可以这么实现

SELECT
CASE WHEN GROUPING(ware_category) = 1
THEN '商品类别 合计'
ELSE ware_category
END AS ware_category,
CASE WHEN GROUPING(registration_date) = 1
THEN '登记日期 合计'
ELSE TO_CHAR(registration_date, 'YYYY-MM-DD')
END AS registration_date,
SUM(purchase_unit_price) AS purchase_unit_prices
FROM tbl_ware
GROUP BY GROUPING SETS (ware_category,registration_date);

  提个问题,有 Null 的哪一行记录表示什么?

  相比 ROLLUP 和 CUBE 相比, GROUPING SETS 的使用场景特别少,有所了解即可

总结

  GROUPING

  作用很明显,就是为了区分 超级分组记录 的 NULL 和原始数据 NULL

  说白了,就是为了标识出 合计 记录

  ROLLUP

  做个等价替换,方便大家理解

   GROUP BY ROLLUP(ware_category,registration_date) 等价于

  如果是 3 个聚合键了,等价情况是怎么样的?

  CUBE

  同样做个等价替换

   GROUP BY CUBE(ware_category,registration_date) 等价于

  如果是 3 个聚合键了,等价情况又是怎么样的?

参考

  《SQL基础教程》

神奇的 SQL ,同时实现小计与合计,阁下该如何应对的更多相关文章

  1. 用SQL实现统计报表中的"小计"与"合计"的方法详解

    本篇文章是对使用SQL实现统计报表中的"小计"与"合计"的方法进行了详细的分析介绍,需要的朋友参考下   客户提出需求,针对某一列分组加上小计,合计汇总.网上找 ...

  2. 用SQL实现统计报表中的“小计”和“合计”

    问题: 开发一个关于各烟叶等级的二次验级的原发件数.原发重量及验收重量的统计报表.其中,原发件数.原发重量和验收重量等列要求计算出各等级组别的小计和所有记录的合计. 语句: SELECT DECODE ...

  3. PB gird类型数据窗口 设置分组、分组小计、合计

    今天遇到一个需求,gird表格数据如下:  部门  类型 数据   A  类型1  1  A  类型2  2  B  类型1  3  B  类型2  4   合计 10 实际需要显示的结果为:  部门 ...

  4. orcl rollup 分组小计、合计

    表数据: select * from group_test; 分组小计.合计: select group_id, decode(concat(job, group_id), null, '合计', g ...

  5. oracle group by rollup实现小计、合计

    SQL合计汇总实现数据N+1条显示: 注意group by rollup((ename, job, empno))!!! select decode(grouping(ename) + groupin ...

  6. sql 查询优化小计

    好久没更博了,偷偷的抽时间写一下. 早上开始working的时候,发现一个页面加载很慢,经排查是昨天写的一条联合查询的sql导致的.于是着手优化! 首先想到的是在join的时候,减少表体积之后再进行关 ...

  7. sql小计合计

    转自:http://www.jb51.net/article/18860.htm 这里介绍sql server2005里面的一个使用实例: CREATE TABLE tb(province nvarc ...

  8. 每日学习心得:SQL查询表的行列转换/小计/统计(with rollup,with cube,pivot解析)

    2013-8-20 1.    SQL查询表的行列转换/小计/统计(with  rollup,with cube,pivot解析) 在实际的项目开发中有很多项目都会有报表模块,今天就通过一个小的SQL ...

  9. SQL查询表的行列转换/小计/统计(with rollup,with cube,pivot解析)

    SQL查询表的行列转换/小计/统计(with rollup,with cube,pivot解析) 2013-8-20 1.    SQL查询表的行列转换/小计/统计(with  rollup,with ...

  10. sql小计汇总 rollup用法实例分析

    这里介绍sql server2005里面的一个使用实例: ),city ),score int) GO 1. 只有一个汇总 select province as 省,sum(score) as 分数 ...

随机推荐

  1. Educational Codeforces Round 93 (Rated for Div. 2)

    Educational Codeforces Round 93 (Rated for Div. 2) A. Bad Triangle input 3 7 4 6 11 11 15 18 20 4 10 ...

  2. java中除法结果不对。

    今天遇一个非常简单地计算,计算结果居然是不对0,查了一些前辈们的资料动手实验了一下,实验结果和代码分享给大家.需要计算的公式:(7/10)*0.8 结果居然不是0.56 而是 0,最后找到原因(7/1 ...

  3. 【内核】深入分析内核panic(三)--内核错误处理流程

    1 内核错误处理方式 当内核出现致命错误时,只要cpu还能正常运行,那么最重要的就是向用户输出详细的错误信息,以及保存问题出现时的错误现场.以上致命错误可包含以下两种类型: (1)硬件能检测到的错误, ...

  4. qsort函数使用方法总结(详细全面+代码)

    目录 qsort函数原型 compar参数 int 数组排序 结构体排序 字符串指针数组排序 字符串二维数组排序 整型二维数组(力扣题目) qsort函数原型 void qsort( void *ba ...

  5. ClickHouse中“大列”造成的JOIN的内存超限问题

    ClickHouse中"大列"造成的JOIN的内存超限问题 "大列"是指单行数据量非常大的列,通常是100KiB以上.这样的列会导致JOIN(通常LEFT JO ...

  6. linux 开机默认进入命令行模式

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  7. Go-发送邮件

    1. 邮件 - mail From -- 发送者(这封邮件由谁进行发送的,一般都是该邮件的作者) To -- 邮件的接收者(发送邮件的人希望谁能收到邮件) Subject -- 邮件的主题(类似文章的 ...

  8. [转帖]一问带你掌握通过storcli做RAID

    因为系统不支持直接做raid,所以需要使用storcli这个工具来操作.首先把工具上传到服务器任意目录,并使用命令chmod +x storcli64修改文件权限为可执行. 另外可通过命令ln -s ...

  9. [转帖]Datadog 能成为最大的云监控厂商吗

    https://xie.infoq.cn/article/901cfd6b284e3e103ac70aeb3 作者:睿象云 2021-03-25 本文字数:2256 字 阅读完需:约 7 分钟   D ...

  10. 【转帖】基于paramiko的二次封装

    https://www.jianshu.com/p/944674f44b24 paramiko 是 Python 中的一个用来连接远程主机的第三方工具,通过使用 paramiko 可以用来代替以 ss ...