聚集函数把多行压成一个数:平均、计数、最大最小、求和,就这五个。
这一章的重点不是记住名字,而是记住它们的两个脾气——跳过 NULL、吃 DISTINCT。
Demo 在 13 个产品上跑出:平均价 14.68,去重后 16.22;
COUNT(*) 给 5 而 COUNT(cust_email) 只给 3;整库卖了 174 件、1355.85 元。
上一章的函数是「一行进、一行出」,这一章的函数是「多行进、一行出」——方向反了, 所以它们叫聚集(aggregate)而不叫函数。 没有 GROUP BY 的时候,整张表就是一个组,所以结果永远只有一行。 想每组一行,那是下一章的事。
一、五个聚集函数
AVG 平均、COUNT 计数、MAX 最大、MIN 最小、SUM 求和。
五个都只吃一个参数(列或表达式),都只还一个值。
它们把「多行」压成「一行」,所以 SELECT AVG(prod_price) FROM products 只出一行,不是十三行。
这是最容易和普通函数搞混的地方。
SUM 也能吃表达式:SUM(quantity * item_price) 就是整库卖了多少钱,
不必先把小计算成一列再求和。
MAX/MIN 对文本也管用,按排序规则给最大最小;
但对日期用之前先确认列的类型,字符串日期比出来的是字典序。
二、COUNT(*) 与 COUNT(列)
这两个经常被当成一回事,其实差得很远:COUNT(*) 数行数,COUNT(列) 只数这一列非 NULL 的行。
本课 5 个客户,COUNT(*) 给 5,COUNT(cust_email) 只给 3——两个客户没留邮箱,被跳过了。
所以问「表里几行」用星号,问「有几行填了这一列」才用列名。
用错了不会报错,只会悄悄少几行。
三、聚集不同值:DISTINCT
DISTINCT 写在函数括号里:AVG(DISTINCT prod_price) 是「先去重再平均」。
写成 DISTINCT AVG(...) 是语法错——它修饰的是列,不是结果。
13 行只有 11 个不同价格(10.00 和 2.50 各出现两次),所以两个平均价不一样:全算 14.6823,
去重后 16.2155。
去重后更高,说明重复的那几个价格偏低——把均价往下拉了一点。
这个差值本身就是信息:差得多说明低价商品卖得杂,差得少说明价格分布均匀。
四、组合聚集函数
一条 SELECT 可以同时算多个聚集:
SELECT COUNT(*), MIN(prod_price), MAX(prod_price), AVG(prod_price) FROM products 一次给四个数。
比发四条查询省三次往返,在网络应用里这很实在。
而且四个数来自同一时刻的快照,不会出现「查完均价又有人插了一行」的错位。
结果只有一行:没有 GROUP BY 时整张表就是一个组。有了 GROUP BY 才是每组一行——那是下一章。
五、聚集函数都跳过 NULL
五个聚集函数全都忽略 NULL,连 COUNT(*) 之外的四个人都这样。这不是疏忽,是标准规定。
AVG(LENGTH(cust_email)) 算的是「填了的三个」的平均长度,分母是 3 不是 5。
SUM 也一样,NULL 那行就当没看见。
要按全部 5 行算,得自己兜底:AVG(COALESCE(LENGTH(cust_email), 0))。
这是最隐蔽的一类错误——数字看着正常,只是分母悄悄少了几行。
六、小结
教材小结:
五个聚集函数把多行压成一行,COUNT(*) 数行、COUNT(列) 数非 NULL,DISTINCT 先去重再聚集,
可以一次算多个。
本课补三条:一是聚集函数全跳过 NULL,分母会变小;二是没有 GROUP BY 就只有一个组;
三是 SUM(表达式) 比「先算一列再求和」少一步。
七、动手 Demo:五个函数、两种 COUNT、一次去重
Demo 六段:先看平均价与最大最小,再把 COUNT(*) 与 COUNT(列) 摆在一起比,
然后看 DISTINCT 怎么改平均数,最后验证聚集函数对 NULL 的态度。
注意每段的「(1 行)」——这就是聚集函数把多行压成一行的证据。
import sqlite3
con = sqlite3.connect(":memory:")
# 五个聚集函数:多行压成一个数
con.execute("SELECT AVG(prod_price) AS avg_price FROM products")
con.execute("SELECT COUNT(*) AS n, MIN(prod_price) AS lo, "
"MAX(prod_price) AS hi FROM products")
# COUNT(*) 数行,COUNT(列) 只数非 NULL
con.execute("SELECT COUNT(*) AS num_rows, COUNT(cust_email) AS num_email "
"FROM customers")
# SUM 能吃表达式
con.execute("SELECT SUM(quantity) AS qty, "
"SUM(quantity * item_price) AS amount FROM orderitems")
# DISTINCT 修饰的是列,不是结果
con.execute("SELECT COUNT(*) AS n_all, COUNT(DISTINCT prod_price) AS n_dis "
"FROM products")
con.execute("SELECT AVG(prod_price) AS a_all, AVG(DISTINCT prod_price) AS a_dis "
"FROM products")
# 一条 SELECT 出五个数
con.execute("SELECT COUNT(*) AS n, MIN(prod_price) AS lo, "
"MAX(prod_price) AS hi, AVG(prod_price) AS avg FROM products")
实跑输出:
--------------------------------------------------------------------
第十二课:汇总数据
--------------------------------------------------------------------
【一】五个聚集函数:把多行压成一个数
SQL> SELECT AVG(prod_price) AS avg_price FROM products
avg_price
----------
14.68
(1 行)
**13 行价格压成一个平均数。聚集函数都这样:喂一列,还一个值。**
【二】COUNT 的两种写法:星号与列名
SQL> SELECT COUNT(*) AS num_rows, COUNT(cust_email) AS num_email FROM customers
num_rows num_email
-----------------------
5 3
(1 行)
**同样是 5 个客户,星号给 5,列名只给 3。**
**COUNT(列) 不数 NULL**——两个客户没留邮箱,就被它跳过了。
**这条差别经常用错**:想知道「表里几行」用 COUNT(*),想知道「有几行填了这一列」才用 COUNT(列)。
SQL> SELECT COUNT(*) AS n FROM customers WHERE cust_email IS NULL
n
--
2
(1 行)
**5 减 3 等于 2,和上面那句对上了。**
【三】MAX、MIN、SUM
SQL> SELECT MAX(prod_price) AS price_max, MIN(prod_price) AS price_min FROM products
price_max price_min
------------------------
55.00 2.50
(1 行)
SQL> SELECT SUM(quantity) AS items_total,
SUM(quantity * item_price) AS amount_total FROM orderitems
items_total amount_total
-----------------------------
174 1355.85
(1 行)
**SUM 也能吃表达式**(数量和单价相乘再累加),这就是「整库卖了多少钱」的算法。
**MAX/MIN 对文本也管用**,按排序规则给最大最小;
**但对日期用之前先确认列的类型**,字符串日期比出来的是字典序。
SQL> SELECT MAX(prod_name) AS name_max, MIN(prod_name) AS name_min FROM products
name_max name_min
--------------------------------
TNT (5 sticks) .5 ton anvil
(1 行)
【四】DISTINCT:先去重再聚集
SQL> SELECT COUNT(*) AS n_all, COUNT(DISTINCT prod_price) AS n_dis FROM products
n_all n_dis
----------------
13 11
(1 行)
**13 行只有 11 个不同价格**——10.00 和 2.50 各出现了两次。
**所以两个平均价不一样**:全算 14.6823,去重后 16.2155。
**去重后更高,说明重复的那几个价格偏低**,把均价往下拉了一点。
**DISTINCT 只能写在函数括号里**(`AVG(DISTINCT 列)`),
写成 `DISTINCT AVG(列)` 是语法错——**它修饰的是列,不是结果。**
【五】组合聚集:一条 SQL 出五个数
SQL> SELECT COUNT(*) AS num_items, MIN(prod_price) AS price_min,
MAX(prod_price) AS price_max, AVG(prod_price) AS price_avg FROM products
num_items price_min price_max price_avg
----------------------------------------------------
13 2.50 55.00 14.68
(1 行)
**一条 SELECT 可以同时算多个聚集**,比发五条查询省四次往返。
**注意结果只有一行**:没有 GROUP BY 时,整张表就是一个组。
**有了 GROUP BY 才是「每组一行」**——那是下一章的事。
【六】聚集函数全都跳过 NULL
SQL> SELECT COUNT(*) AS n_rows, COUNT(cust_email) AS n_email,
AVG(LENGTH(cust_email)) AS avg_len FROM customers
n_rows n_email avg_len
-------------------------------
5 3 16.67
(1 行)
**`AVG(LENGTH(cust_email))` 算的是「填了的三个」的平均长度 16.67**,不是除以 5。
**要按全部 5 行算,得自己兜底**:`AVG(COALESCE(LENGTH(cust_email), 0))`。
第一段 AVG。 13 行价格压成一个 14.68,结果只有一行。
第二段 COUNT 对照。 同一张表,星号给 5,列名给 3,差的两个就是没填邮箱的客户。
第三段 SUM 吃表达式。 174 件、1355.85 元,这两个数就是整库的销量与营业额。
第四段 DISTINCT。 13 行只有 11 个不同价格,平均价从 14.6823 抬到 16.2155。
第五段组合。 一条 SELECT 出四个数,省三次往返。
第六段 NULL。 三个邮箱的平均长度 16.67,分母是 3 不是 5——COUNT(*) 那列仍然是 5,
两个数放在一起就看出来了。
30 秒实验
- 把
AVG(prod_price)换成SUM(prod_price),13 个产品的价格合计是多少? - 用
MAX(quantity)和MIN(quantity)看 orderitems,最大最小各是多少? - 算
AVG(COALESCE(LENGTH(cust_email), 0)),和 16.67 差多少?为什么?
常见误区
- 以为 COUNT(*) 和 COUNT(列) 一样。列名会跳过 NULL,两个数可能差好几行。
- 以为聚集函数会把 NULL 当 0 算。它们直接跳过,分母跟着变小。
- 写
DISTINCT AVG(列)。DISTINCT 要写在括号里、修饰列,写成修饰结果就是语法错。
自测三问(点开对答案)
1. 五个聚集函数是哪五个? —— AVG 平均、COUNT 计数、MAX 最大、MIN 最小、SUM 求和;都是一列进、一个值出。 2. 5 个客户里有 2 个没填邮箱,`COUNT(*)` 和 `COUNT(cust_email)` 各给几? —— 星号给 5,列名给 3;COUNT(列) 只数非 NULL,NULL 那两行被跳过。 3. 为什么 `AVG(DISTINCT prod_price)` 比 `AVG(prod_price)` 大? —— 因为重复的那两个价格(10.00 和 2.50)都偏低,去重等于把低价样本删掉一部分,均价自然抬高。聚集函数把整张表压成一行, 但业务上要的往往是「每家供应商的平均价」。 下一章用 GROUP BY 把表切成几块,每块各算一遍。
- ☐ (本章完,下一章 第13章 分组数据)