GROUP BY 与 HAVING 子句的高级用法与优化

好的,各位老铁,今天咱们聊聊SQL里一对“神雕侠侣”—— GROUP BYHAVING。 这俩货就像厨房里的刀和铲,用得好能做出满汉全席,用不好…emmm…也就只能切切黄瓜了。 咱们的目标是,把它们玩儿得溜溜的,让你的SQL语句像火箭一样🚀嗖嗖地跑,数据分析报告像诗一样美!

一、GROUP BY:化繁为简的魔法师🧙‍♂️

想象一下,你有一堆散落在桌子上的硬币,有1块的,5毛的,1毛的。 要数清楚每种硬币有多少个,你会怎么做? 肯定不是一个一个数吧? 那样太低效了! 你会把它们按照面值分成几堆,然后数每一堆的数量。

GROUP BY 就是SQL里的这个“分堆”的魔法师。 它的作用是,按照指定的列,把数据分成若干个组。 就像把硬币按照面值分堆一样,GROUP BY 会把表中具有相同值的行,放到同一个组里。

1. 语法结构

SELECT 列名1, 列名2, ... , 聚合函数(列名)
FROM 表名
WHERE 条件
GROUP BY 列名1, 列名2, ...
ORDER BY 列名;
  • SELECT: 选择要显示的列。 重点来了,GROUP BY 后面的列,必须出现在 SELECT 列表中(或者被聚合函数包裹)。 这就像你分硬币的时候,必须要知道硬币的面值才能分一样。
  • FROM: 指定要查询的表。
  • WHERE: 可选,指定筛选条件。 先筛选数据,再分组,这样效率更高。
  • GROUP BY: 指定分组的列。 这就是我们的主角!
  • ORDER BY: 可选,指定排序方式。

2. 举个栗子🌰

假设我们有一张 orders 表,记录了用户的订单信息:

order_id user_id product_id order_date amount
1 101 1 2023-10-26 100
2 102 2 2023-10-26 200
3 101 1 2023-10-27 150
4 103 3 2023-10-27 300
5 102 2 2023-10-28 250

现在,我们想统计每个用户的订单总金额。 就可以使用 GROUP BY

SELECT user_id, SUM(amount) AS total_amount
FROM orders
GROUP BY user_id;

这条SQL语句的意思是:

  1. orders 表中查询数据。
  2. 按照 user_id 进行分组。
  3. 对于每个 user_id 组,计算 amount 列的总和,并命名为 total_amount
  4. 显示 user_idtotal_amount

查询结果:

user_id total_amount
101 250
102 450
103 300

3. 多个列分组

GROUP BY 后面可以跟多个列,就像把硬币先按照面值分堆,再按照年份分堆一样。

例如,我们想统计每个用户每天的订单总金额:

SELECT user_id, order_date, SUM(amount) AS daily_amount
FROM orders
GROUP BY user_id, order_date;

查询结果:

user_id order_date daily_amount
101 2023-10-26 100
101 2023-10-27 150
102 2023-10-26 200
102 2023-10-28 250
103 2023-10-27 300

二、HAVING:分组后的“守门员” 👮‍♀️

WHERE 子句是用来筛选行的,而 HAVING 子句是用来筛选组的。 就像你去参加一个相亲大会,WHERE 帮你筛选出符合年龄、学历等条件的候选人,而 HAVING 帮你筛选出符合收入、房产等条件的候选人。

HAVING 必须和 GROUP BY 配合使用,因为它是对分组后的结果进行筛选。

1. 语法结构

SELECT 列名1, 列名2, ... , 聚合函数(列名)
FROM 表名
WHERE 条件
GROUP BY 列名1, 列名2, ...
HAVING 条件
ORDER BY 列名;
  • HAVING: 指定筛选分组的条件。 HAVING 后面可以使用聚合函数,例如 SUM(), AVG(), COUNT(), MAX(), MIN()

2. 举个栗子🌰

我们想找出订单总金额超过 300 的用户:

SELECT user_id, SUM(amount) AS total_amount
FROM orders
GROUP BY user_id
HAVING SUM(amount) > 300;

这条SQL语句的意思是:

  1. orders 表中查询数据。
  2. 按照 user_id 进行分组。
  3. 对于每个 user_id 组,计算 amount 列的总和,并命名为 total_amount
  4. 筛选出 total_amount 大于 300 的组。
  5. 显示 user_idtotal_amount

查询结果:

user_id total_amount
102 450

3. WHERE vs HAVING: 傻傻分不清楚? 🤔

很多新手都会搞混 WHEREHAVING, 它们都是用来筛选数据的,但作用对象不同:

  • WHERE: 作用于,在分组之前进行筛选。
  • HAVING: 作用于,在分组之后进行筛选。

记住这个原则: 先 WHERE 后 GROUP BY,再 HAVING。 就像你去参加相亲大会,先筛选出年龄合适的,再按照收入筛选。

什么时候用 WHERE? 什么时候用 HAVING?

  • 如果筛选条件不涉及聚合函数,就用 WHERE
  • 如果筛选条件涉及聚合函数,就用 HAVING

例如:

  • 找出 2023-10-27 之后的订单: 用 WHERE (WHERE order_date > '2023-10-27')
  • 找出订单总金额超过 300 的用户: 用 HAVING (HAVING SUM(amount) > 300)

三、GROUP BYHAVING 的高级用法 🚀

掌握了基本用法,咱们来点高级的,让你的SQL语句更上一层楼!

1. ROLLUPCUBE: 汇总的艺术

ROLLUPCUBEGROUP BY 的扩展,可以生成更丰富的汇总信息。 它们就像Excel里的“分类汇总”功能,可以帮你快速计算出各种维度的汇总数据。

  • ROLLUP: 按照指定的列,从右向左依次进行汇总。
  • CUBE: 生成所有可能的汇总组合。

举个栗子🌰:

SELECT user_id, product_id, SUM(amount) AS total_amount
FROM orders
GROUP BY ROLLUP(user_id, product_id);

这条SQL语句会生成以下几种汇总:

  1. 每个用户每个产品的订单总金额。
  2. 每个用户的订单总金额(product_id 为 NULL)。
  3. 所有用户的订单总金额(user_id 和 product_id 都为 NULL)。

CUBE 会生成更多的汇总组合,这里就不一一列举了。

2. GROUPING SETS: 自定义汇总

GROUPING SETS 可以让你更灵活地指定需要生成的汇总组合。 就像你定制了一份专属的报表,只显示你关心的汇总数据。

举个栗子🌰:

SELECT user_id, product_id, SUM(amount) AS total_amount
FROM orders
GROUP BY GROUPING SETS ((user_id, product_id), (user_id), ());

这条SQL语句会生成以下几种汇总:

  1. 每个用户每个产品的订单总金额。
  2. 每个用户的订单总金额。
  3. 所有用户的订单总金额。

3. 窗口函数: 分组内的精细计算

窗口函数可以在分组内进行更精细的计算,例如计算每个用户的订单排名、累计订单金额等。 它们就像一个放大镜,让你更清晰地看到分组内的细节。

举个栗子🌰:

SELECT
    user_id,
    order_date,
    amount,
    RANK() OVER (PARTITION BY user_id ORDER BY amount DESC) AS rank
FROM orders;

这条SQL语句会计算每个用户的订单金额排名。

四、GROUP BYHAVING 的优化技巧 🛠️

再牛逼的工具,也需要精心维护才能发挥最大的威力。 咱们来聊聊 GROUP BYHAVING 的优化技巧,让你的SQL语句跑得更快!

1. 索引优化

  • GROUP BYHAVING 的列上建立索引,可以加快分组和筛选的速度。
  • 如果 WHERE 子句中也有筛选条件,也要在相应的列上建立索引。

2. 避免不必要的排序

  • GROUP BY 默认会进行排序,如果不需要排序,可以使用 ORDER BY NULL 来禁用排序。
  • 如果只需要分组,不需要排序,可以使用 HASH GROUP BY (MySQL 8.0+)。

3. 先 WHERE 后 GROUP BY

  • 尽量在 WHERE 子句中过滤掉不需要的数据,减少分组的数据量。
  • WHERE 子句可以使用索引,而 HAVING 子句通常不能使用索引。

4. 避免使用 DISTINCT

  • GROUP BY 已经具有去重的功能,如果只需要去重,可以使用 GROUP BY 代替 DISTINCT

5. 合理使用临时表

  • 对于复杂的 GROUP BYHAVING 查询,可以先将中间结果保存到临时表中,再进行后续操作。

6. SQL 重写

有时候,同样的逻辑可以用不同的SQL语句来实现。 尝试使用不同的SQL语句,看看哪个效率更高。 例如,可以使用子查询、连接查询等方式来代替 GROUP BYHAVING

7. 数据库参数调优

  • 调整数据库的参数,例如 sort_buffer_sizetmp_table_size 等,可以影响 GROUP BY 的性能。

五、总结 📝

GROUP BYHAVING 是SQL中非常重要的两个子句,掌握它们可以让你轻松地进行数据分组、汇总和筛选。 记住以下几点:

  • GROUP BY: 用来分组数据。
  • HAVING: 用来筛选分组后的数据。
  • WHERE: 作用于行,HAVING: 作用于组。
  • 先 WHERE 后 GROUP BY,再 HAVING。
  • 合理使用索引、避免不必要的排序、SQL 重写等技巧可以提高 GROUP BYHAVING 的性能。

好了,今天的分享就到这里。 希望大家能够掌握 GROUP BYHAVING 的高级用法和优化技巧,让你的SQL语句像火箭一样🚀嗖嗖地跑,数据分析报告像诗一样美! 🎉

记住,编程就像烹饪,需要不断地练习和尝试才能做出美味佳肴。 多写SQL,多思考,你也能成为SQL大师! 💪

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注