一家在线教育平台有两张表:students
和 courses
。
students
表记录了学生的基本信息以及他们报名的课程。courses
表记录了课程的详细信息,包括课程的难度等级。现在需要找出报名了至少两门课程的学生,并列出他们的姓名以及报名的课程数量。
students
| studentid | name | courseid |
|------------|------------|-----------|
| 1 | Alice | 101 |
| 2 | Bob | 102 |
| 3 | Alice | 103 |
| 4 | Charlie | 104 |
| 5 | Alice | 105 |
| 6 | Bob | 106 |
courses
| courseid | coursename | difficulty |
|-----------|-------------------|------------|
| 101 | Mathematics | Medium |
| 102 | Physics | Hard |
| 103 | Chemistry | Medium |
| 104 | Biology | Easy |
| 105 | Computer Science | Hard |
| 106 | English | Easy |
找出报名了至少两门课程的学生,并列出他们的姓名以及报名的课程数量。
SELECT
s.name,
COUNT(DISTINCT s.course_id) AS course_count
FROM
students s
GROUP BY
s.name
HAVING
COUNT(DISTINCT s.course_id) >= 2;
根据示例数据,执行上述SQL语句后,结果如下:
| name | course_count |
|--------|--------------|
| Alice | 3 |
| Bob | 2 |
HAVING
的基本语法HAVING
通常与 GROUP BY
一起使用,其语法结构如下:
sql复制
SELECT column1, column2, AGG_FUNC(column3) AS alias
FROM table_name
GROUP BY column1, column2
HAVING condition;
GROUP BY
:将数据按指定列分组。
HAVING
:对分组后的结果进行筛选,筛选条件可以包含聚合函数(如 COUNT
、SUM
、AVG
等)。
HAVING
的作用HAVING
的主要作用是筛选满足特定条件的分组。它与 WHERE
的区别在于:
WHERE
:在分组之前筛选行,不能使用聚合函数。
HAVING
:在分组之后筛选分组,可以使用聚合函数。
假设我们有一个 students
表,记录学生的姓名和选修的课程编号:
name | course_id |
---|---|
Alice | 1 |
Alice | 2 |
Bob | 3 |
Charlie | 4 |
Charlie | 5 |
Charlie | 6 |
分组(GROUP BY
):
按照学生的姓名分组,每个学生是一个独立的分组。
分组后,每个分组的数据如下:
Alice:[1, 2]
Bob:[3]
Charlie:[4, 5, 6]
聚合(COUNT(DISTINCT course_id)
):
对每个分组计算选修的不同课程数量:
Alice:2
Bob:1
Charlie:3
筛选(HAVING
):
使用 HAVING COUNT(DISTINCT course_id) >= 2
筛选出课程数量大于等于 2 的分组。
结果:
Alice:2
Charlie:3
最终的 SQL 查询如下:
sql复制
SELECT
name,
COUNT(DISTINCT course_id) AS course_count
FROM
students
GROUP BY
name
HAVING
COUNT(DISTINCT course_id) >= 2;
HAVING
的关键点与聚合函数配合:HAVING
条件中可以使用聚合函数(如 COUNT
、SUM
、AVG
等),而 WHERE
条件中不能使用聚合函数。
筛选分组:HAVING
是在分组之后对分组结果进行筛选,而 WHERE
是在分组之前对行进行筛选。
灵活性:HAVING
可以结合多个聚合函数进行复杂的筛选条件。
假设需求是“找出选修了至少两门不同课程的学生”,以下两种写法的区别:
HAVING
sql复制
SELECT
name,
COUNT(DISTINCT course_id) AS course_count
FROM
students
GROUP BY
name
HAVING
COUNT(DISTINCT course_id) >= 2;
先分组,再筛选分组。
WHERE
写法sql复制
SELECT
name,
COUNT(DISTINCT course_id) AS course_count
FROM
students
WHERE
COUNT(DISTINCT course_id) >= 2 -- 错误:WHERE 不能使用聚合函数
GROUP BY
name;
这是错误的,因为 WHERE
不能使用聚合函数。
HAVING
是在分组后对分组结果进行筛选,通常与聚合函数配合使用。
WHERE
是在分组前对行进行筛选,不能使用聚合函数。
如果需要根据聚合结果进行筛选,必须使用 HAVING
。
HAVING
和 WHERE
是 SQL 中用于数据筛选的两个重要的子句,但它们在作用范围、使用场景和语法上有明显的区别。以下是它们的主要区别:
WHERE
:
作用对象:WHERE
是在分组(GROUP BY
)之前对单行数据进行筛选。
筛选时机:在数据分组之前,逐行判断是否满足条件。
使用场景:用于筛选表中的行,不能使用聚合函数(如 COUNT
、SUM
等)。
HAVING
:
作用对象:HAVING
是在分组(GROUP BY
)之后对分组结果进行筛选。
筛选时机:在数据分组并计算聚合函数之后,对分组进行筛选。
使用场景:用于筛选分组后的结果,可以使用聚合函数。
WHERE
:
不支持聚合函数:WHERE
子句中不能使用聚合函数(如 COUNT
、SUM
、AVG
等),因为这些函数需要在分组之后才有意义。
示例:
sql复制
SELECT name, COUNT(course_id) AS course_count
FROM students
WHERE COUNT(course_id) >= 2; -- 错误:WHERE 不能使用聚合函数
HAVING
:
支持聚合函数:HAVING
子句中可以使用聚合函数,因为它是对分组后的结果进行筛选。
示例:
sql复制
SELECT name, COUNT(course_id) AS course_count
FROM students
GROUP BY name
HAVING COUNT(course_id) >= 2; -- 正确:HAVING 可以使用聚合函数
WHERE
:
行级筛选:用于在分组之前筛选出符合条件的行。
示例:
sql复制
SELECT name, course_id
FROM students
WHERE course_id > 10; -- 筛选出课程编号大于10的行
HAVING
:
分组级筛选:用于在分组之后筛选出符合条件的分组。
示例:
sql复制
SELECT name, COUNT(course_id) AS course_count
FROM students
GROUP BY name
HAVING COUNT(course_id) >= 2; -- 筛选出选修了至少两门课程的学生
SQL 查询的执行顺序如下:
FROM
:确定数据来源表。
WHERE
:在分组之前筛选行。
GROUP BY
:对筛选后的数据进行分组。
聚合函数计算:对每个分组计算聚合结果。
HAVING
:在分组之后筛选分组。
SELECT
:选择需要的列并返回结果。
WHERE
:
作用于单行数据。
不能使用聚合函数。
用于在分组之前筛选行。
HAVING
:
作用于分组后的结果。
可以使用聚合函数。
用于在分组之后筛选分组。
案例二
SELECT
COUNT(*) AS first_name_count, -- 统计每个姓氏的出现次数
CASE
WHEN code LIKE '王%' THEN '王'
WHEN code LIKE '邓%' THEN '邓'
ELSE '其他' -- 添加 ELSE 部分,避免未匹配的情况
END AS code_first_name,
level
FROM
dma.dma_dop_dim_project_manager
GROUP BY
code_first_name, level -- 按姓氏和 level 分组
HAVING
COUNT(*) > 10; -- 筛选出姓氏出现次数大于10的分组
CASE
语句的:
将 CASE
语句的逻辑分支正确嵌套,并添加了 ELSE '其他'
,以避免未匹配的情况。
修正了 CASE
语句的语法错误,确保每个分支以 THEN
结束,并在最后以 END
结束。
列名冲突的解决:
将 COUNT(code_first_name)
改为 COUNT(*)
,并命名为 first_name_count
,避免与 CASE
表达式的结果列名冲突。
GROUP BY
的修正:
GROUP BY
中使用了 CASE
表达式的结果列 code_first_name
和 level
,确保分组逻辑正确。
HAVING
的修正:
HAVING
子句中使用了 COUNT(*) > 10
,筛选出姓氏出现次数大于 10 的分组。
查询的目的是统计每个姓氏(如“王”、“邓”)的出现次数,并筛选出出现次数大于 10 的姓氏。
如果 code
不属于“王”或“邓”,则归类为“其他”。
分组基于姓氏和 level
,并筛选出满足条件的分组。