🐬 第13章 分组数据
📄 一句话概括

聚集函数把整张表压成一行,GROUP BY 把表切成几堆、每堆各压一行。这一章讲清三件事:怎么切、 切完怎么筛(HAVING)、切完怎么排。 Demo 里 13 个产品按供应商切成 4/2/6/1 四堆; HAVING 留下三堆,加上 WHERE 先筛行就只剩两堆; 把 COUNT(*) 写进 WHERE 会当场报错——那是 WHERE 执行时还不存在的东西。

📝 本章和前面几章的关系

上一章的聚集函数只能出一行,那是「整表一堆」的特例。 这一章把「一堆」变成「好几堆」,聚集函数才真正有用。 HAVING 与 WHERE 的分工是本章的分水岭:WHERE 在分组前砍行,HAVING 在分组后砍堆。 写反了要么报错、要么结果不对。

一、数据分组:把行分堆再各算一遍

上一章问「所有产品的平均价」,一句话就够。可业务上要的往往是「每家供应商的平均价」—— 这就得先把行按供应商分成堆。 不做分组只有两条路:每家发一条查询(几家就几次往返),或者把 13 行全取回来在应用里算。 GROUP BY 把这件事交给数据库。 分组之后,SELECT 里能出现的东西变少了:只能是分组列和聚集函数, 写别的一律报错或给出无意义的随机值——因为它不知道该取哪一行。

二、创建分组:GROUP BY

GROUP BY vend_id 的意思是「按 vend_id 的值把行分堆」, 然后再对每堆各跑一次 SELECT 里的聚集函数。 13 行切成 4 堆:1001 四个、1002 两个、1003 六个、1005 一个。 每堆出一行,所以结果 4 行。 GROUP BY 后面可以跟多列:GROUP BY order_num, prod_id 是「先按单号切, 再在每张单里按产品切」。切得越细,堆越多、每堆越小。 教材强调:GROUP BY 里的列必须是「检索列或表达式」,不能拿别名或聚集函数来分组。 而且 NULL 自己会成一堆,这点要注意。

三、过滤分组:HAVING 与 WHERE 的分工

HAVING 长得像 WHERE,位置也像,但它筛的是堆不是行。 所以 HAVING 里可以用聚集函数:HAVING COUNT(*) >= 2 是「只留至少两个产品的组」。 WHERE 在分组之前执行,HAVING 在分组之后执行。 这就是为什么 WHERE COUNT(*) >= 2 会报错——那时候还没数过,COUNT(*) 根本不存在。 两者可以同时用:WHERE prod_price >= 10 GROUP BY vend_id HAVING COUNT(*) >= 2。 先把便宜的行砍掉再分组,堆自然就小了,这比先分组再筛更省事。 一条实用原则:凡是能用 WHERE 的,别拖到 HAVING。 WHERE 少读一行,后面所有步骤都少算一次。

四、分组和排序

GROUP BY 不保证顺序。 结果看着像按组号升序,那只是当前实现的巧合, 换个版本、加个索引就可能变。 要顺序就自己写 ORDER BY,而且ORDER BY 可以按聚集结果排: ORDER BY num_prods DESC 让产品最多的那组排最前。 别用 GROUP BY 代替 ORDER BY,也别指望它俩的顺序一致——教材专门点了这一条, 因为它在小数据上「看起来总是对的」。

五、SELECT 子句的顺序

书写顺序是固定的:SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY、LIMIT。 写反就是语法错,不是风格问题。 执行顺序不一样:FROM 取表、WHERE 筛行、GROUP BY 分堆、HAVING 筛堆、SELECT 算列、ORDER BY 排、 LIMIT 截。 记住执行顺序有个直接好处:能解释「为什么别名在 WHERE 里用不了」。 因为 WHERE 执行时 SELECT 还没算,别名还不存在;ORDER BY 在 SELECT 之后,所以能用。

六、小结

教材小结:GROUP BY 分组、HAVING 筛组、ORDER BY 排序,SELECT 子句顺序不能乱。 本课补三条:一是 GROUP BY 不保证顺序;二是能用 WHERE 就别用 HAVING; 三是分组后 SELECT 只能用分组列和聚集函数。

七、动手 Demo:从一堆到四堆,再筛到两堆

Demo 六段:先分组数产品,再看顺序不可靠,然后加多列分组,接着用 HAVING 筛组、 WHERE 加 HAVING 一起用,最后故意把条件放错位置看报错。 每一段都能用行数自证:四组、三组、两组,数字对得上就说明写对了。

import sqlite3
con = sqlite3.connect(":memory:")

# 按供应商分组,各数一次
con.execute("SELECT vend_id, COUNT(*) AS num_prods FROM products GROUP BY vend_id")
# GROUP BY 不保证顺序,要顺序自己写 ORDER BY
con.execute("SELECT vend_id, COUNT(*) AS num_prods FROM products "
            "GROUP BY vend_id ORDER BY num_prods DESC")
# 按多列分组:先按单号切,再在单里按产品切
con.execute("SELECT order_num, prod_id, quantity FROM orderitems "
            "GROUP BY order_num, prod_id")
# HAVING 筛组,能用聚集函数
con.execute("SELECT vend_id, COUNT(*) AS num_prods FROM products "
            "GROUP BY vend_id HAVING COUNT(*) >= 2")
# WHERE 先筛行,HAVING 后筛组
con.execute("SELECT vend_id, COUNT(*) AS num_prods FROM products "
            "WHERE prod_price >= 10 GROUP BY vend_id HAVING COUNT(*) >= 2")
# 把 HAVING 的条件错写进 WHERE:这里会报 misuse of aggregate
try:
    con.execute("SELECT vend_id, COUNT(*) AS n FROM products "
                "WHERE COUNT(*) >= 2 GROUP BY vend_id")
except sqlite3.OperationalError as e:
    print("报错:", e)

实跑输出:

--------------------------------------------------------------------
第十三课:分组数据
--------------------------------------------------------------------

  【一】先看问题:每家供应商各有几个产品
  SQL> SELECT vend_id, COUNT(*) AS num_prods FROM products GROUP BY vend_id
  vend_id     num_prods
  ----------------------
  1001        4
  1002        2
  1003        6
  1005        1
  (4 行)
  **四组,13 行被切成 4/2/6/1。** GROUP BY 的意思是「按这一列的值把行分堆,每堆各算一遍」。
  **没有 GROUP BY 的时候整表一堆,只出一行;有了它,一堆一行。**

  【二】GROUP BY 的默认顺序不可靠
  SQL> SELECT vend_id,
       COUNT(*) AS num_prods FROM products GROUP BY vend_id ORDER BY num_prods DESC
  vend_id     num_prods
  ----------------------
  1003        6
  1001        4
  1002        2
  1005        1
  (4 行)
  **结果看着是「按组号升序」,但那只是巧合。** 教材专门提醒:
  **GROUP BY 不保证顺序,要顺序必须自己写 ORDER BY。**
  上面那句按数量降序排,**1003 的六个跑到最前面了。**

  【三】按多列分组:切得更细
  SQL> SELECT order_num, COUNT(*) AS items FROM orderitems GROUP BY order_num
  order_num     items
  ------------------------
  20005         4
  20006         1
  20007         1
  20008         1
  20009         4
  (5 行)
  **五张单:20005 与 20009 各四件,其余三张各一件。**
  SQL> SELECT order_num, prod_id, quantity FROM orderitems GROUP BY order_num,
       prod_id ORDER BY order_num LIMIT 8
  order_num     prod_id     quantity
  ------------------------------------
  20005         ANV01       10
  20005         ANV02       3
  20005         FB          1
  20005         TNT2        5
  20006         JP2000      1
  20007         TNT2        100
  20008         FC          50
  20009         FB          1
  (8 行)
  **两列一起分组,等于先按单号切、再在每张单里按产品切。**
  只有 (单号, 产品) 全相同的行才会落到同一堆。

  【四】过滤分组:HAVING 与 WHERE 的分工
  SQL> SELECT vend_id, COUNT(*) AS num_prods FROM products GROUP BY vend_id HAVING COUNT(*) >= 2
  vend_id     num_prods
  ----------------------
  1001        4
  1002        2
  1003        6
  (3 行)
  **HAVING 是「分完组之后再筛」**,所以它能用聚集函数 `COUNT(*)`。 1005 只有 1 个产品,被筛掉了。
  SQL> SELECT vend_id,
       COUNT(*) AS num_prods FROM products WHERE prod_price >= 10 GROUP BY vend_id HAVING COUNT(*) >= 2
  vend_id     num_prods
  ----------------------
  1001        2
  1003        3
  (2 行)
  **WHERE 先筛行、HAVING 后筛组**:这句先只留价格不低于 10 的 6 行,再分组,再要求每组至少 2 个。
  **一句话记住分工:WHERE 管行,HAVING 管组。** **凡是能用 WHERE 的,别拖到 HAVING**——
  先在行层面砍掉,分组才省事。

  【五】把条件挪错位置会怎样
  **报错:misuse of aggregate: COUNT()**
  **WHERE 执行的时候还没分组,COUNT(*) 根本不存在**,所以这里只能写 HAVING。

  【六】SELECT 子句的书写顺序
  顺序    子句              作用
  ----------------------------------------------------------------------
  1       SELECT            要哪些列
  2       FROM              从哪张表来
  3       WHERE             先筛行
  4       GROUP BY          把行分堆
  5       HAVING            再筛堆
  6       ORDER BY          最后排序
  7       LIMIT             只取前几行
  **这个顺序不能乱**:HAVING 写在 GROUP BY 前面是语法错,ORDER BY 写在 WHERE 前面也是。
  **顺带说一句执行的顺序不完全一样**:
  FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。
  **SELECT 里的别名在 WHERE 里用不了,就是因为还没算到那一步。**

第一段分组。 四行结果对应四家供应商,4/2/6/1 加起来正好 13。 第二段排序。 1003 的六个排到最前,但别以为 GROUP BY 默认就这样排。 第三段多列分组。 五张单里 20005 与 20009 各四件,其余三张各一件。 第四段 HAVING。 要求至少两个产品,1005 那唯一的一个被筛掉,剩三组。 第五段 WHERE 加 HAVING。 先只留价格不低于 10 的 6 行,再分组,只剩 1001 与 1003 两组。 第六段故意写错。 把 COUNT(*) 放进 WHERE,引擎当场报 misuse of aggregate——它在提醒你: 这一步还没分组。

30 秒实验

  1. 把 HAVING COUNT(*) >= 2 改成 >= 4,还剩几组?
  2. 用 GROUP BY vend_id 加 AVG(prod_price),看四组的平均价各是多少。
  3. 把 WHERE prod_price >= 10 删掉,HAVING 那段的组数和每组数量会怎么变?

常见误区

  1. 把 HAVING 的条件写进 WHERE。WHERE 执行时还没分组,聚集函数不存在,直接报错。
  2. 以为 GROUP BY 会顺带排序。它不保证顺序,要顺序必须写 ORDER BY。
  3. 在 SELECT 里写没分组的列。分组后只能取分组列和聚集函数,否则取值无意义。
自测三问(点开对答案) 1. WHERE 和 HAVING 最根本的区别是什么? —— 执行时机不同:WHERE 在分组前筛行,HAVING 在分组后筛组;所以只有 HAVING 能用聚集函数。 2. 13 个产品按 vend_id 分成几组?各组几个? —— 四组:1001 四个、1002 两个、1003 六个、1005 一个,加起来 13。 3. 为什么 `SELECT` 里定义的别名不能在 `WHERE` 里用? —— 因为执行顺序是 WHERE 先于 SELECT,别名那时还没算出来;ORDER BY 在 SELECT 之后,所以能用。

分组解决了「分类汇总」, 但一次只能查一层。 下一章学「查询套查询」——先把内层的结果算出来,再拿它当外层的条件。

相关笔记