数据库系统概论(八)SQL单表查询语言超详细讲解(附带例题表格对比带你一步步掌握)
前言
- 之前我们已经简单认识了 SQL 语言(结构化查询语言),并了解了它的基本概念(比如表、元组、属性、数据库等)。
- 现在,我们正式进入 SQL 核心功能 的学习 —— SQL 查询语言,这是日常使用最频繁、也是掌握 SQL 的关键部分。
我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的数据库系统概论专栏
https://blog.csdn.net/2402_83322742/category_12911520.html?spm=1001.2014.3001.5482
一、创建表(了解一下就好,后面会详细讲)
- 在开始之前我们需要创建这三张表,以方便我们后续的查询
CREATE TABLE Student (
Sno CHAR(7) PRIMARY KEY, -- 学号,主键,假设学号固定 7 位
Sname VARCHAR(20) NOT NULL, -- 姓名,非空
Ssex CHAR(2) CHECK (Ssex IN ('男', '女')), -- 性别,限制为男或女
Sbirthdate DATE, -- 出生日期,日期类型
Smajor VARCHAR(50) -- 主修专业
);
INSERT INTO Student (Sno, Sname, Ssex, Sbirthdate, Smajor) VALUES
('20180001', '李勇', '男', '2000-03-08', '信息安全'),
('20180002', '刘晨', '女', '1999-09-01', '计算机科学与技术'),
('20180003', '王敏', '女', '2001-08-01', '计算机科学与技术'),
('20180004', '张立', '男', '2000-01-08', '计算机科学与技术'),
('20180005', '陈新奇', '男', '2001-11-01', '信息管理与信息系统'),
('20180006', '赵明', '男', '2000-06-12', '数据科学与大数据技术'),
('20180007', '王佳佳', '女', '2001-12-07', '数据科学与大数据技术');
CREATE TABLE Course (
Cno CHAR(5) PRIMARY KEY, -- 课程号,主键,假设课程号固定 5 位
Cname VARCHAR(50) NOT NULL, -- 课程名,非空
Ccredit INT, -- 学分,整数类型
Cpno CHAR(5), -- 先修课课程号
FOREIGN KEY (Cpno) REFERENCES Course(Cno) -- 外键,先修课引用自身课程号
);
INSERT INTO Course (Cno, Cname, Ccredit, Cpno) VALUES
('81001', '程序设计基础与C语言', 4, NULL),
('81002', '数据结构', 4, '81001'),
('81003', '数据库系统概论', 4, '81002'),
('81004', '信息系统概论', 4, '81003'),
('81005', '操作系统', 4, '81001'),
('81006', 'Python语言', 3, '81002'),
('81007', '离散数学', 4, NULL),
('81008', '大数据技术概论', 4, '81003');
CREATE TABLE SC (
Sno CHAR(7), -- 学号
Cno CHAR(5), -- 课程号
Grade INT, -- 成绩,整数类型
Semester CHAR(4), -- 选课学期,如 "20192"
Teachingclass VARCHAR(20), -- 教学班
PRIMARY KEY (Sno, Cno), -- 联合主键(学号 + 课程号)
FOREIGN KEY (Sno) REFERENCES Student(Sno), -- 外键,关联学生表
FOREIGN KEY (Cno) REFERENCES Course(Cno) -- 外键,关联课程表
);
INSERT INTO SC (Sno, Cno, Grade, Semester, Teachingclass) VALUES
('20180001', '81001', 85, '20192', '81001-01'),
('20180001', '81002', 96, '20201', '81002-01'),
('20180001', '81003', 87, '20202', '81003-01'),
('20180002', '81001', 80, '20192', '81001-02'),
('20180002', '81002', 98, '20201', '81002-01'),
('20180002', '81003', 71, '20202', '81003-02'),
('20180003', '81001', 81, '20192', '81001-01'),
('20180003', '81002', 76, '20201', '81002-02'),
('20180004', '81001', 56, '20192', '81001-02'),
('20180004', '81003', 97, '20201', '81002-02'),
('20180005', '81003', 68, '20202', '81003-01');
二、数据查询的概念
2.1 什么是数据查询?
你可以把 数据库 想象成一个超级大的电子表格(比如Excel),里面存满了各种数据(比如学生信息、订单记录、商品列表等)。
数据查询 就是:从这个“超级表格”里快速找到你需要的信息。
比如:
- 你想知道“数学成绩超过80分的学生有哪些?”
- 或者“昨天销量超过100件的商品有哪些?”
这些“找信息”的过程,就是数据查询。
2.2 数据查询的语句样式:
大部分数据库(比如MySQL、SQL Server、Oracle)都用一种叫 SQL(结构化查询语言) 的语句来查询数据。
它的语法很像“人话”,比如:
1. 最基本的查询语句(查“某张表的某几列数据”)
SELECT 列1, 列2, 列3 -- 你想查的“表格中的列”(比如“姓名”“分数”)
FROM 表名; -- 数据所在的“表格名称”(比如“学生表”)
例子:查“学生表”中的所有学生的姓名和数学成绩:
SELECT 姓名, 数学成绩
FROM 学生表;
2. 带条件的查询(加一个“筛选条件”,只查符合条件的数据)
如果想只查“数学成绩大于80分”的学生,就加一个 WHERE
条件:
SELECT 列1, 列2 -- 想查的列
FROM 表名 -- 表格名
WHERE 条件; -- 筛选条件(比如“数学成绩 > 80”)
例子:
SELECT 姓名, 数学成绩
FROM 学生表
WHERE 数学成绩 > 80; -- 只查数学成绩超过80分的学生
3. 其他常用“小工具”
- 查所有列:如果想查表格里的所有列,不用一个个列名写出来,直接用
*
:SELECT * -- * 代表“所有列” FROM 学生表;
- 去重:如果想查“不重复”的数据,比如“有哪些不同的班级”,用
DISTINCT
:SELECT DISTINCT 班级 -- 只显示不同的班级名称,重复的只留一个 FROM 学生表;
三、单表查询语言的种类
- 接着我们用上面刚刚所创建的表
3.1 单表查询
就是只从 一个表 里查数据,比如只查学生表(Student)、课程表(Course)或选课表(SC)中的数据。
3.1.1 查询表中的若干列
1. 查询指定的列(只选需要的信息)
- 作用:只查表中某几列的数据,比如只查学生的学号和姓名,不查其他信息(性别、年龄等)。
- 语法:
SELECT 列1, 列2, 列3 -- 想查的列,用逗号隔开 FROM 表名; -- 从哪个表查
- 例子:
- 查全体学生的学号和姓名:
SELECT Sno, Sname -- 只查这两列 FROM Student; -- 从学生表查
- 查姓名、学号、专业(列的顺序可以自己调):
SELECT Sname, Sno, Smajor -- 顺序和表中不一定一样,按需求排 FROM Student;
- 查全体学生的学号和姓名:
2. 查询全部列(查所有信息)
- 方法1:列出表的所有列(麻烦,表列多的时候不推荐):
SELECT 列1, 列2, 列3, ..., 列N -- 把所有列名写出来 FROM 表名;
- 方法2(推荐):用
*
代替所有列,一键查询所有信息:SELECT * -- * 表示“所有列” FROM 表名;
- 例子:查学生的所有详细信息:
SELECT * FROM Student; -- 简单又快捷!
3. 查询计算后的值(加工数据)
- 作用:表中没有直接的数据,但可以通过 公式、函数 计算出来。
比如表中存的是出生日期(Sbirthdate),想算年龄,就用当前日期减出生日期。 - 语法:
SELECT 列1, 计算表达式 [AS 别名] -- AS 给结果起个名字(可选) FROM 表名;
- 例子:查学生的学号、姓名和年龄(年龄用函数计算):
SELECT Sno, Sname, TIMESTAMPDIFF(YEAR, Sbirthdate, CURDATE()) AS Age -- 计算年龄,别名是Age FROM Student;
TIMESTAMPDIFF(YEAR, 出生日期, 当前日期)
:算两个日期相差多少年(年龄)。CURDATE()
:获取当前日期(2025-05-12,例子中的今天)。
4. 去重查询
- 作用:如果某列有重复数据(比如选课表SC中同个学生选多门课,学号会重复),想只看唯一的学号,就用
DISTINCT
。 - 语法:
SELECT DISTINCT 列名 -- 只保留该列的唯一值 FROM 表名;
- 例子:查所有选过课的学生学号(不重复):
SELECT DISTINCT Sno -- 不管一个学生选了多少课,学号只出现一次 FROM SC;
总结
场景 | 语法示例 | 说明 |
---|---|---|
指定列 | SELECT 列1, 列2 FROM 表名; |
只查需要的列,列名用逗号隔开。 |
所有列 | SELECT * FROM 表名; |
* 代表所有列,简单方便。 |
计算值 | SELECT 列, 表达式 AS 别名 FROM 表名; |
用公式/函数加工数据,AS 起别名(可选)。 |
去重 | SELECT DISTINCT 列 FROM 表名; |
去掉重复的行,只保留唯一值。 |
3.1.2 选择表中的若干元组
1.基础:查询条件放在哪里?
所有筛选条件都写在 WHERE
子句里(如果是分组后的筛选,用 HAVING
,这里先重点学 WHERE
)。
格式:
SELECT 列名 FROM 表名 WHERE 条件;
2.常用查询条件分类
3. 简单比较(比大小、是否相等)
- 谓词:
=、>、<、>=、<=、!=(或<>)
,以及NOT+这些符号
(取反)。 - 作用:直接对比数据是否符合某个值或范围。
- 例子:
- 查“计算机科学与技术”专业的学生姓名:
SELECT Sname FROM Student WHERE Smajor = '计算机科学与技术';
- 查成绩不及格(<60分)的学生学号(去重,避免重复):
SELECT DISTINCT Sno FROM SC WHERE Grade < 60;
- 查2001年及以后出生的学生(用
year(日期列)
提取年份):SELECT ... WHERE year(Sbirthdate) >= 2001;
- 查“计算机科学与技术”专业的学生姓名:
4. 范围查询(某个区间内/外)
- 谓词:
BETWEEN 最小值 AND 最大值
:包含两端,比如20-23岁之间。NOT BETWEEN 最小值 AND 最大值
:不在这个区间内。
- 作用:快速筛选连续区间的数据,比用
>= 最小值 AND <= 最大值
更简洁。 - 例子:
- 查年龄20-23岁的学生(用
TIMESTAMPDIFF(year, 出生日期, 当前日期)
计算年龄):SELECT ... WHERE TIMESTAMPDIFF(year, Sbirthdate, CURDATE()) BETWEEN 20 AND 23;
- 查年龄不在20-23岁之间的学生,只需加
NOT
:... WHERE ... NOT BETWEEN 20 AND 23;
- 查年龄20-23岁的学生(用
5. 集合查询(是否在指定列表中)
- 谓词:
IN (值1, 值2, ...)
:数据等于列表中的任意一个。NOT IN (值1, 值2, ...)
:数据不在列表中。
- 作用:快速筛选多个指定值,比用多个
OR
更方便。 - 例子:
- 查“计算机科学与技术”或“信息安全”专业的学生:
SELECT ... WHERE Smajor IN ('计算机科学与技术', '信息安全');
- 等价于用
OR
连接:... WHERE Smajor = '计算机科学与技术' OR Smajor = '信息安全';
- 查不在这两个专业的学生,加
NOT
:... WHERE Smajor NOT IN (...);
- 查“计算机科学与技术”或“信息安全”专业的学生:
6. 字符匹配(模糊查询,含通配符)
- 谓词:
LIKE '匹配串'
:数据符合匹配规则(支持通配符)。NOT LIKE '匹配串'
:不符合匹配规则。
- 通配符:
%
:代表任意长度的字符串(包括0个字符,即空)。_
:代表任意单个字符。
- 例子:
- 固定字符串(精确匹配,等价于
=
):WHERE Sno LIKE '20180003'; -- 等价于 Sno = '20180003'
- 以某个字符开头(比如“刘”姓):
WHERE Sname LIKE '刘%'; -- 姓名以“刘”开头,后面任意字符
- 指定长度和位置(比如学号以2023开头,课程号第5位是6,总长度5位):
WHERE Sno LIKE '2023%'; -- 学号以2023开头(后面任意长度) WHERE Cno LIKE '81__6'; -- 课程号第1-2位是81,第3-4位任意单个字符,第5位是6(共5位)
- 排除匹配(不姓刘的学生):
WHERE Sname NOT LIKE '刘%';
- 转义特殊字符(如果数据中包含
%
或_
,需要用ESCAPE
声明转义符,比如\
):-- 查名称为“DB_Design”的课程(下划线是普通字符,不是通配符) WHERE Cname LIKE 'DB\_Design' ESCAPE '\'; -- 用\转义下划线
- 固定字符串(精确匹配,等价于
5. 空值判断(数据是否为空)
- 谓词:
IS NULL
:数据为空(比如没参加考试,成绩为空)。IS NOT NULL
:数据不为空。
- 注意:不能用
=
或!=
判断空值,必须用IS
! - 例子:
- 查缺少成绩的学生(成绩列为空):
SELECT Sno, Cno FROM SC WHERE Grade IS NULL;
- 查有成绩的学生:
... WHERE Grade IS NOT NULL;
- 查缺少成绩的学生(成绩列为空):
6. 多重条件组合(多个条件一起用)
- 逻辑运算符:
AND
:多个条件同时满足(“且”)。OR
:至少满足一个条件(“或”)。NOT
:对条件取反(“非”)。
- 优先级:
NOT > AND > OR
,但建议用括号明确顺序,避免出错。 - 例子:
- 查“计算机科学与技术”专业,且2004年及以后出生的学生:
SELECT ... WHERE Smajor = '计算机科学与技术' AND year(Sbirthdate) >= 2004;
- 查“计算机科学与技术”专业 或 成绩不及格的学生(用括号区分优先级):
SELECT ... WHERE (Smajor = '计算机科学与技术') OR (Grade < 60);
- 查“计算机科学与技术”专业,且2004年及以后出生的学生:
总结:
- 精确比较(等于、大于等):用
=、>、<
等符号。 - 区间范围(20-23岁):用
BETWEEN AND
或NOT BETWEEN AND
。 - 多个指定值(两个专业):用
IN
或NOT IN
。 - 模糊匹配(姓刘、学号开头):用
LIKE
,搭配%
(任意长度)、_
(单个字符),特殊字符用ESCAPE
转义。 - 空值处理(有无成绩):用
IS NULL
或IS NOT NULL
。 - 多个条件组合:用
AND
(同时满足)、OR
(满足其一),必要时加括号。
3.1.3 ORDER BY子句
1. ORDER BY子句的作用
一句话:让查询结果按照你指定的列(或多列)排序,就像给数据“整理队形”!
2. 基本语法
SELECT 列1, 列2 FROM 表名 WHERE 条件 ORDER BY 排序列 [ASC/DESC];
ORDER BY
必须放在WHERE
子句之后(如果有WHERE的话)。ASC
(Ascending):升序(从小到大,默认)。DESC
(Descending):降序(从大到小)。
3.单字段排序:按一个列排序
例子1:查询选修了81003号课程的学生学号和成绩,按成绩降序排列(高分在前)。
SELECT Sno, Grade FROM SC WHERE Cno='81003' ORDER BY Grade DESC;
- 结果:成绩最高的学生排在最前面,最低的在最后。
例子2:查询所有学生的姓名,按姓名升序排列(默认升序,可省略ASC)。
SELECT Sname FROM Student ORDER BY Sname; -- 等价于 ORDER BY Sname ASC
- 结果:姓名按字母或拼音顺序排列(A-Z或拼音首字母)。
4.多字段排序:按多个列排序
规则:先按第一列排序,如果第一列值相同,则按第二列排序,以此类推。
例子:查询全体学生的选课情况,先按课程号升序,同一课程内按成绩降序。
SELECT * FROM SC ORDER BY Cno, Grade DESC;
- 结果:
- 课程号(Cno)从小到大排列。
- 若课程号相同(比如都是81003),则按成绩(Grade)从高到低排列。
5.注意事项
空值(NULL)的排序:
- 不同数据库系统处理不同,常见两种:
- NULL值排在最前(如MySQL)。
- NULL值排在最后(如SQL Server)。
- 若想控制NULL的位置,可结合
IS NULL
或COALESCE
函数处理。
- 不同数据库系统处理不同,常见两种:
排序性能:
- 对大量数据排序时,会消耗更多资源。
- 尽量在排序字段上创建索引,提高速度。
6.总结:
单字段排序:
ORDER BY 列名 ASC; -- 升序(默认) ORDER BY 列名 DESC; -- 降序
多字段排序:
ORDER BY 列1 [ASC/DESC], 列2 [ASC/DESC]; -- 先按列1,再按列2
常见场景:
- 成绩排名(降序)。
- 时间线(最新的在前,降序)。
- 字典序(姓名、商品名升序)。
3.1.4 聚集函数
1.什么是聚集函数?
一句话总结:聚集函数专门用来 “批量计算” 数据,比如统计有多少行、求和、求平均等。
举个生活例子:
- 想知道全班有多少人?用
COUNT
。 - 想算全班数学平均分?用
AVG
。 - 想找最高分、最低分?用
MAX
和MIN
。
2、5 个常用聚集函数
3. 统计个数:COUNT
- 功能:数“有多少个”。
- 两种用法:
COUNT(*)
:统计表格中 所有行的数量(包括有NULL
的行)。
▶ 例:查学生总人数SELECT COUNT(*) FROM Student; -- 不管学生信息是否完整,只要有记录就数进去
COUNT(列名)
:统计这一列中 非空值的数量(排除NULL
)。
▶ 例:查选修了课程且有成绩的学生人数(Grade
列非空)SELECT COUNT(Grade) FROM SC; -- 只数有成绩的行,忽略成绩为空的
COUNT(DISTINCT 列名)
:先去重,再统计非空值的数量。
▶ 例:查选修了课程的学生人数(同一个学生选多门课,只算一次)SELECT COUNT(DISTINCT Sno) FROM SC; -- 不管选了多少门课,每个学生只算1次
4. 求和:SUM(只能用于数值列)
- 功能:把某一列的数值加起来。
- 用法:
SUM(列名)
,可选DISTINCT
去重后求和(很少用,默认ALL
算所有值)。
▶ 例:计算学号 20180003 的学生选修课程的总学分(需要连接SC
和Course
表,因为学分在Course
表中)SELECT SUM(Credit) FROM SC, Course WHERE Sno='20180003' AND SC.Cno=Course.Cno; -- 通过课程号连接,把选的课的学分加起来
5. 求平均值:AVG(只能用于数值列)
- 功能:计算某一列数值的平均值(自动忽略
NULL
)。
▶ 例:算选修 81001 号课程的学生平均成绩SELECT AVG(Grade) FROM SC WHERE Cno='81001';
6. 求最值:MAX和 MIN
- 功能:
MAX(列名)
:找某一列的最大值(数值、日期、字符串都能用,字符串按字典序)。MIN(列名)
:找某一列的最小值(同上)。
▶ 例:查 81001 号课程的最高分和最低分
SELECT MAX(Grade) AS 最高分, MIN(Grade) AS 最低分 FROM SC WHERE Cno='81001';
7.关键细节
NULL值的处理
:COUNT(*)
:不管有没有NULL
,只要有行就统计。COUNT(列名)
、SUM
、AVG
、MAX
、MIN
:自动忽略NULL
(当列值是NULL
时,不参与计算)。
DISTINCT 和 ALL
:- 默认为
ALL
(计算所有值,包括重复的)。 DISTINCT
表示去重后再计算,比如COUNT(DISTINCT Sno)
只算不同的学生。
- 默认为
多表使用:
- 如果要统计的列在多个表中,需要先通过 表连接(比如
SC
和Course
用课程号连接),再用聚集函数。
- 如果要统计的列在多个表中,需要先通过 表连接(比如
8.经典示例拆解(从简单到复杂)
需求 | 代码示例 | 核心逻辑 |
---|---|---|
学生总人数 | SELECT COUNT(*) FROM Student; |
数所有学生记录,不管信息是否完整。 |
有成绩的学生选课记录数 | SELECT COUNT(Grade) FROM SC; |
只数 Grade 列不为空的行(有成绩的选课记录)。 |
选修过课程的学生人数(去重) | SELECT COUNT(DISTINCT Sno) FROM SC; |
同一个学生选多门课,只算1次(去重)。 |
某课程平均成绩 | SELECT AVG(Grade) FROM SC WHERE Cno='81001'; |
先筛选课程,再算平均成绩(自动忽略成绩为空的行)。 |
跨表求总学分 | SELECT SUM(Credit) FROM SC, Course ... |
通过课程号连接两张表,把学生选的所有课的学分加起来。 |
9.练习
查询软件工程专业的女生总数:
- 条件:
Smajor='软件工程'
且Ssex='女'
,统计人数用COUNT(*)
。
SELECT COUNT(*) FROM Student WHERE Smajor='软件工程' AND Ssex='女';
- 条件:
2023年第2学期“81003”课程的不及格人数:
- 条件:
Semester='2023-2'
(假设学期格式如此)、Cno='81003'
、Grade<60
,统计人数用COUNT(*)
。
SELECT COUNT(*) FROM SC WHERE Semester='2023-2' AND Cno='81003' AND Grade<60;
- 条件:
全部课程的总学分:
- 直接对
Course
表的Credit
列求和。
SELECT SUM(Credit) FROM Course;
- 直接对
81002号课程的最高分、最低分、平均分:
- 用
MAX
、MIN
、AVG
三个函数一起统计。
SELECT MAX(Grade) AS 最高分, MIN(Grade) AS 最低分, AVG(Grade) AS 平均分 FROM SC WHERE Cno='81002';
- 用
10.总结:
- 数数量:
- 所有行:
COUNT(*)
。 - 某列非空值:
COUNT(列名)
。 - 去重后数量:
COUNT(DISTINCT 列名)
。
- 所有行:
- 算总和/平均(仅限数值列):
SUM(列名)
、AVG(列名)
。 - 找最值:
MAX(列名)
(最大)、MIN(列名)
(最小)。
3.1.5 GROUP BY子句
1.什么是GROUP BY子句?
一句话总结:把数据按某一列或多列「分组」,然后对每个组分别进行统计(比如算每组的平均分、人数等)。
举个生活例子:
- 想按「班级」统计每个班的平均分?用
GROUP BY 班级
。 - 想按「课程号」统计每门课的及格人数?用
GROUP BY 课程号
。
2.基本语法与核心逻辑
SELECT 分组列, 聚集函数(其他列) -- 分组列必须出现在GROUP BY中
FROM 表名
[WHERE 行级条件] -- 先筛选行,再分组
GROUP BY 分组列 [, 分组列2,...] -- 按一列或多列分组
[HAVING 组级条件] -- 对分组后的结果筛选(常用聚集函数)
[ORDER BY 排序列]; -- 最后排序
3.关键逻辑步骤:
- 先过滤行:用
WHERE
筛选符合条件的行(还没分组时就过滤)。 - 再分组:按
GROUP BY
的列把数据分成多个组(同组的分组列值相同)。 - 组内统计:对每个组用聚集函数(如
AVG(Grade)
、COUNT(*)
)计算。 - 筛选组:用
HAVING
筛选掉不符合条件的组(比如平均成绩<60分的组)。 - 最后排序:用
ORDER BY
给结果排序。
4.核心用法示例(对比WHERE和HAVING)
1. 按单字段分组:统计每组的聚集结果
例:查询每个学生的平均成绩(只显示平均≥90分的学生)
SELECT Sno, AVG(Grade) AS 平均分 -- 分组列是Sno,对每个Sno的Grade求平均
FROM SC
GROUP BY Sno -- 按学号分组,每个学号一组
HAVING AVG(Grade) >= 90; -- 筛选出平均分≥90的组
- 为什么不用WHERE?:
WHERE
只能筛选「行」(比如Grade>80的行),但「平均分≥90」是针对整个组的条件,必须用HAVING
。
2. 错误示例:在WHERE中用聚集函数
下面的写法是 错误的:
SELECT Sno, AVG(Grade)
FROM SC
WHERE AVG(Grade)>=90 -- ❌ 错误!WHERE不能用聚集函数(AVG是分组后的统计结果)
GROUP BY Sno;
- 原因:
WHERE
在分组 之前 执行,此时还没有分组结果,无法计算AVG(Grade)
。 - 正确做法:用
HAVING
在分组 之后 筛选组。
3.WHERE vs HAVING:核心区别
子句 | 作用对象 | 执行时机 | 能否用聚集函数 | 典型场景 |
---|---|---|---|---|
WHERE | 原始表中的行 | 分组 之前 | ❌ 不能 | 筛选单个行(如Grade<60的行) |
HAVING | 分组后的组 | 分组 之后 | ✅ 能用 | 筛选组(如平均成绩<60的组) |
例子对比:
- 查「成绩<60分且属于2023学期的行」(行级筛选):
WHERE Semester='2023' AND Grade<60
- 查「2023学期中,平均成绩<60分的课程组」(组级筛选):
GROUP BY Cno HAVING AVG(Grade)<60
4.多字段分组:按多个列分组
例:按「课程号」和「学期」分组,统计每门课每学期的选课人数
SELECT Cno, Semester, COUNT(Sno) AS 选课人数 -- 分组列是Cno和Semester
FROM SC
GROUP BY Cno, Semester; -- 同时按两个列分组,组内这两个列的值都相同
- 结果:每个(课程号,学期)组合为一组,统计该组的选课人数。
5 练习(附正确答案)
练习1:查询平均分低于50分的课程的课程号及其平均分
需求分析:
- 按「课程号(Cno)」分组,计算每组的平均分。
- 筛选出平均分<50的组(组级条件,用HAVING)。
正确写法:
SELECT Cno, AVG(Grade) AS 平均分
FROM SC
GROUP BY Cno -- 按课程号分组
HAVING AVG(Grade) < 50; -- 筛选平均分<50的组
- 注意:如果写成
WHERE AVG(Grade)<50
会报错,因为WHERE不能用聚集函数。
练习2:查询2019学年不及格课程总数≥3门的学生,显示学号和不及格门数,按学号升序排序
需求分析:
- 行级筛选:先筛选出「2019学年(Semester含2019)」且「成绩<60(不及格)」的行。
- 分组:按「学号(Sno)」分组(每个学生一组)。
- 组内统计:统计每组的不及格课程数(用COUNT(*))。
- 组级筛选:筛选出不及格门数≥3的组(用HAVING)。
- 排序:按学号升序排列。
正确写法:
SELECT Sno, COUNT(*) AS 不及格门数
FROM SC
WHERE Semester LIKE '2019%' AND Grade < 60 -- 行级筛选:2019学年且不及格
GROUP BY Sno -- 按学号分组
HAVING COUNT(*) >= 3 -- 组级筛选:不及格门数≥3
ORDER BY Sno ASC; -- 按学号升序排序(ASC可省略,默认升序)
- 关键点:
WHERE
先排除无关的行(比如非2019学年、成绩≥60的行),再分组统计,效率更高。
6.总结:
- 确定分组列:数据按哪列/哪些列分组(如Sno、Cno)。
- 行级筛选(可选):用WHERE过滤掉不需要的行(分组前执行)。
- 分组+统计:用GROUP BY分组,对每组用聚集函数(COUNT、AVG等)。
- 组级筛选(可选):用HAVING筛选不符合条件的组(必须用聚集函数时)。
- 排序(可选):用ORDER BY对最终结果排序。
3.1.6 LIMIT子句
1.LIMIT子句的作用
一句话总结:让查询结果只显示 指定数量的行,比如只看前10条数据,或第3-5条数据。
举个生活例子:
- 成绩单太长,只想看前10名?用
LIMIT 10
。 - 分页查看数据(比如第2页,每页5条)?用
LIMIT 5 OFFSET 5
(跳过前5条,取接下来的5条)。
2.基本语法与参数解析
SELECT 列名 FROM 表名 [WHERE 条件] [ORDER BY 排序] LIMIT <数量> [OFFSET <跳过行数>];
- 核心参数:
LIMIT <n1>
:必选,指定要显示的 最大行数(从第1行开始取n1行)。OFFSET <n2>
:可选,指定 跳过前面的n2行,再取后面的n1行(默认OFFSET 0,即不跳过)。
- 执行顺序:先执行
SELECT
和WHERE
,再排序(ORDER BY
),最后用LIMIT
限制行数。
3.常用场景与示例
3.1 取前N条数据(最常用)
例:查询成绩前10名的学生学号(按成绩降序,取前10行)
SELECT Sno FROM SC, Course
WHERE Course.Cname = '数据库系统概论' AND SC.Cno = Course.Cno
ORDER BY Grade DESC -- 先按成绩从高到低排序
LIMIT 10; -- 只取前10行(第1-10名)
- 作用:快速获取“Top N”数据(如Top 5销量、Top 3分数)。
3.2 分页查询(跳过前面的行,取中间部分)
例:查询平均成绩排名3-5名的学生(第3、4、5名)
SELECT Sno, AVG(Grade) AS 平均分 FROM SC
GROUP BY Sno
ORDER BY 平均分 DESC -- 先按平均分从高到低排序
LIMIT 3 OFFSET 2; -- 跳过前2行(第1-2名),取接下来的3行(第3-5名)
- 公式:第n页,每页m条数据 →
LIMIT m OFFSET (n-1)*m
。- 比如第2页,每页5条:
LIMIT 5 OFFSET 5
(跳过前5条,取第6-10条)。
- 比如第2页,每页5条:
3.3搭配分组和排序使用
练习:查询选课人数最多的前5名课程
SELECT Cno, COUNT(*) AS 选课人数 FROM SC
GROUP BY Cno -- 按课程号分组,统计每组人数
ORDER BY 选课人数 DESC -- 按人数从多到少排序
LIMIT 5; -- 只取前5名课程
- 逻辑:先分组统计,再排序,最后用LIMIT限制结果为前5条。
4.关键细节
OFFSET
的省略:- 若只写
LIMIT n1
,等价于LIMIT n1 OFFSET 0
(从第1行开始取n1行)。 - 例:
LIMIT 5
→ 取前5行;LIMIT 5 OFFSET 0
→ 同上。
- 若只写
排序的重要性:
- 如果不先排序(
ORDER BY
),LIMIT
取的是数据库默认的无序结果(可能每次运行结果不同)。 - 最佳实践:
LIMIT
几乎总是和ORDER BY
一起使用,确保取到正确顺序的数据。
- 如果不先排序(
性能优势:
- 当数据量很大时,用
LIMIT
可以避免加载全部数据,提升查询速度(比如只需要前10条,不用查全部10万条)。
- 当数据量很大时,用
5.总结:
需求场景 | 语法示例 | 说明 |
---|---|---|
取前N条数据 | LIMIT N |
从第1行开始,取N行(如N=10,取前10行)。 |
取第M到第N条数据 | LIMIT N-M OFFSET M |
跳过前M行,取接下来的N-M行(如M=2,N=5,取第3-5行,用LIMIT 3 OFFSET 2 )。 |
分页(第n页,每页m条) | LIMIT m OFFSET (n-1)*m |
例:第2页,每页5条 → LIMIT 5 OFFSET 5 (跳过前5条,取第6-10条)。 |
取Top N数据(排序后) | ORDER BY 列 DESC LIMIT N |
先降序排序,再取前N条(如Top 5高分)。 |
以上就是这篇博客的全部内容,下一篇我们将继续探索更多精彩内容。
我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的数据库系统概论专栏
https://blog.csdn.net/2402_83322742/category_12911520.html?spm=1001.2014.3001.5482
非常感谢您的阅读,喜欢的话记得三连哦 |