SQL 为什么要反对聚合查问呢?
这看上去是个童稚的问题,但咱们还是一步步思考一下。数据以行为粒度存储,最简略的 SQL 语句是 select * from test
,拿到的是整个二维表明细,但仅做到这一点远远不够,出于以下两个目标,须要 SQL 提供聚合函数:
- 明细数据没有统计意义,比方我想晓得明天的营业额一共有多少,而不太关怀某桌客人生产了多少。
- 尽管能够先把数据查到内存中再聚合,但在数据量十分大的状况下很容易把内存撑爆,可能一张表一天的数据量就有 10TB,而 10TB 数据就算能读到内存里,聚合计算可能也会慢到难以承受。
另外聚合自身也有肯定逻辑复杂度,而 SQL 提供了聚合函数与分组聚合能力,能够不便疾速的统计出有业务价值的聚合数据,这奠定了 SQL 语言的剖析价值,因而大部分剖析软件间接采纳 SQL 作为间接面向用户的表达式。
聚合函数
常见的聚合函数有:
- COUNT:计数。
- SUM:求和。
- AVG:求平均值。
- MAX:求最大值。
- MIN:求最小值。
COUNT
COUNT 用来计算有多少条数据,比方咱们看 id 这一列有多少条:
SELECT COUNT(id) FROM test
但咱们发现其实查任何一列的 COUNT 都是一样的,那传入 id 有什么意义呢?没必要非凡找一个具体列指代呀,所以也能够写成:
SELECT COUNT(*) FROM test
但这两者存在奥妙差别。SQL 存在一种很非凡的值类型 NULL
,如果 COUNT 指定了具体列,则统计时会跳过此列值为 NULL
的行,而 COUNT(*)
因为未指定具体列,所以就算蕴含了 NULL
,甚至某一行所有列都为 NULL
,也都会蕴含进来。所以 COUNT(*)
查出的后果肯定大于等于 COUNT(c1)
。
当然任何聚合函数都能够追随查问条件 WHERE,比方:
SELECT COUNT(*) FROM test
WHERE is_gray = 1
SUM
SUM 求和所有项,因而必须作用于数值字段,而不能用于字符串。
SELECT SUM(cost) FROM test
SUM 遇到 NULL 值时当 0 解决,因为这等价于疏忽。
AVG
AVG 求所有项均值,因而必须作用于数值字段,而不能用于字符串。
SELECT AVG(cost) FROM test
AVG 遇到 NULL 值时采纳了最彻底的疏忽形式,即 NULL 齐全不参加分子与分母的计算,就像这一行数据不存在一样。
MAX、MIN
MAX、MIN 别离求最大与最小值,下面不同的时,也能够作用于字符串上,因而能够依据字母判断大小,从大到小顺次对应 a-z
,但即使能算,也没有实际意义且不好了解,因而不倡议对字符串求极值。
SELECT MAX(cost) FROM test
多个聚合字段
尽管都是聚合函数,但 MAX、MIN 严格意义上不算是聚合函数,因为它们只是寻找了满足条件的行。能够看看上面两段查问后果的比照:
SELECT MAX(cost), id FROM test -- id: 100
SELECT SUM(cost), id FROM test -- id: 1
第一条查问能够找到最大值那一行的 id,而第二条查问的 id 是无意义的,因为不晓得归属在哪一行,所以只返回了第一条数据的 id。
当然,如果同时计算 MAX、MIN,那么此时 id 也只返回第一条数据的值,因为这个查问后果对应了复数行:
SELECT MAX(cost), MIN(cost), id FROM test -- id: 1
基于这些个性,最好不要混用聚合与非聚合,也就是一条查问一旦有一个字段是聚合的,那么所有字段都要聚合。
当初很多 BI 引擎的自定义字段都有这条限度,因为混用聚合与非聚合在自定义内存计算时解决起来边界状况很多,尽管 SQL 能反对,但业务自定义的函数可能不反对。
分组聚合
分组聚合就是 GROUP BY,其实能够把它当作一种高级的条件语句。
举个例子,查问每个国家的 GDP 总量:
SELECT COUNT(GDP) FROM amazing_table
GROUP BY country
返回的后果就会依照国家进行分组,这时,聚合函数就变成了在组内聚合。
其实如果咱们只想看中、美的 GDP,用非分组也能够查,只是要分成两条 SQL:
SELECT COUNT(GDP) FROM amazing_table
WHERE country = '中国'
SELECT COUNT(GDP) FROM amazing_table
WHERE country = '美国'
所以 GROUP BY 也可了解为,将某个字段的所有可枚举的状况都查了进去,并整合成一张表,每一行代表了一种枚举状况,不须要合成为一个个 WHERE 查问了。
多字段分组聚合
GROUP BY 能够对多个维度应用,含意等价于表格查问时行 / 列拖入多个维度。
下面是 BI 查问工具视角,如果没有上下文,能够看上面这个递进形容:
- 依照多个字段进行分组聚合。
- 多字段组合起来成为惟一 Key,即
GROUP BY a,b
示意 a,b 合在一起形容一个组。 GROUP BY a,b,c
查问后果第一列可能看到许多反复的 a 行,第二列看到反复 b 行,但在同一个 a 值内不会反复,c 在 b 行中同理。
上面是一个例子:
SELECT SUM(GDP) FROM amazing_table
GROUP BY province, city, area
查问后果为:
浙江 杭州 余杭区
浙江 杭州 西湖区
浙江 宁波 海曙区
浙江 宁波 江北区
北京 .........
GROUP BY + WHERE
WHERE 是依据行进行条件筛选的。因而 GROUP BY + WHERE 并不是在组内做筛选,而是对整体做筛选。
但因为按行筛选,其实组内或非组内后果都齐全一样,所以咱们简直无奈感知这种差别:
SELECT SUM(GDP) FROM amazing_table
GROUP BY province, city, area
WHERE industry = 'internet'
然而,疏忽这个差别会导致咱们在聚合筛选时碰壁。
比方要筛选出平均分大于 60 学生的问题总和,如果不应用子查问,是无奈在一般查问中在 WHERE 加聚合函数实现的,比方上面就是一个语法错误的例子:
SELECT SUM(score) FROM amazing_table
WHERE AVG(score) > 60
不要空想下面的 SQL 能够执行胜利,不要在 WHERE 里应用聚合函数。
GROUP BY + HAVING
HAVING 是依据组进行条件筛选的。因而能够在 HAVING 应用聚合函数:
SELECT SUM(score) FROM amazing_table
GROUP BY class_name
HAVING AVG(score) > 60
下面的例子中能够失常查问,示意依照班级分组看总分,且仅筛选出平均分大于 60 的班级。
所以为什么 HAVING 能够应用聚合条件呢?因为 HAVING 筛选的是组,所以能够对组聚合后过滤掉不满足条件的组,这样是有意义的。而 WHERE 是针对行粒度的,聚合后全表就只有一条数据,无论过滤与否都没有意义。
但要留神的是,GROUP BY 生成派生表是无奈利用索引筛选的,所以 WHERE 能够利用给字段建设索引优化性能,而 HAVING 针对索引字段不起作用。
总结
聚合函数 + 分组能够实现大部分简略 SQL 需要,在写 SQL 表达式时,须要思考这样的表达式是如何计算的,比方 MAX(c1), c2
是正当的,而 SUM(c1), c2
这个 c2
就是无意义的。
最初记住 WHERE 是 GROUP BY 之前执行的,HAVING 针对组进行筛选。
探讨地址是:精读《SQL 聚合查问》· Issue #401 · ascoders/weekly
如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 – 帮你筛选靠谱的内容。
关注 前端精读微信公众号
<img width=200 src=”https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg”>
版权申明:自在转载 - 非商用 - 非衍生 - 放弃署名(创意共享 3.0 许可证)