分组聚合

SQL 笔试与手撕题教程

GROUP BY 与 HAVING

GROUP BY 把数据按指定列分组,配合聚合函数(SUM、COUNT、AVG 等)计算每组的统计值。

HAVING 对分组后的结果做过滤,和 WHERE 的区别是:WHERE 在分组前过滤行,HAVING 在分组后过滤组

SELECT product_id, SUM(amount) AS total_sales
FROM sales
GROUP BY product_id
HAVING total_sales > 1000;

SQL 执行顺序

理解执行顺序能帮你判断"这个条件该写在 WHERE 还是 HAVING":

FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY

所以 WHERE 里不能用聚合函数(还没分组),HAVING 里可以。SELECT 里的别名在 HAVING 中能否使用取决于数据库——MySQL 允许,但标准 SQL 不允许。

实战:每月销量冠军

典型的分组聚合 + 窗口函数组合拳:

WITH monthly_sales AS (
    SELECT product_id,
        DATE_FORMAT(sale_date, '%Y-%m') AS month,
        SUM(amount) AS total_sales
    FROM sales
    GROUP BY product_id, month
),
ranked AS (
    SELECT *,
        RANK() OVER(PARTITION BY month ORDER BY total_sales DESC) AS rnk
    FROM monthly_sales
)
SELECT product_id, month, total_sales
FROM ranked
WHERE rnk = 1;

先用 GROUP BY 聚合每产品每月销量,再用 RANK() 排名取第一。这种 CTE + 窗口函数的套路在面试中出现频率非常高。

练习

练习

消费额 Top 3 用户

给定 orders 表,找出总消费金额最高的前 3 个用户及其总金额。
SQL 编辑器

点击「运行」查看查询结果

查看答案
参考答案
SELECT user_id, SUM(amount) AS total
FROM orders
GROUP BY user_id
ORDER BY total DESC
LIMIT 3;
解析

简单的 GROUP BY + ORDER BY + LIMIT。注意如果题目要求「并列也要保留」,就不能用 LIMIT,得用 DENSE_RANK()

练习

连续两月达标产品

给定 sales 表,找出连续两个月销量都超过 500 的产品。

提示:先按产品+月分组求总量,再用 LAG 取上月销量,两个月都 > 500 的就是目标

SQL 编辑器

点击「运行」查看查询结果

查看答案
参考答案
WITH monthly AS (
  SELECT product_id,
    DATE_FORMAT(sale_date, '%Y-%m') AS month,
    SUM(amount) AS total
  FROM sales
  GROUP BY product_id, DATE_FORMAT(sale_date, '%Y-%m')
),
with_prev AS (
  SELECT *,
    LAG(total) OVER(PARTITION BY product_id ORDER BY month) AS prev_total,
    LAG(month) OVER(PARTITION BY product_id ORDER BY month) AS prev_month
  FROM monthly
)
SELECT DISTINCT product_id
FROM with_prev
WHERE total > 500 AND prev_total > 500;
解析

先按月聚合,DATE_FORMAT(sale_date, '%Y-%m') 提取年月。再用 LAG 取上月销量,当月和上月都超过 500 就满足条件。如果要严格判断「连续」(月份之间没有跳月),还需要校验 prev_month 和当前 month 确实相邻。