第六章 · 6.1 节

单关系数据查询

掌握 SELECT 语句的完整用法:从简单的投影查询到条件筛选、聚合统计、分组、排序与结果限制,一步步构建强大的数据查询能力。

1. 查询结构 2. 无条件查询 3. 条件查询 4. 聚合函数 5. 分组查询 6. 排序 7. LIMIT
Part 01

单关系数据查询结构

数据查询是数据库中最常用的操作,SQL 提供 SELECT 语句来获取所需信息

📐 SELECT 语句的一般格式
SELECT [ALL | DISTINCT] <字段名> [AS 别名] [{, <字段名> [AS 别名]}] ← 投影(选列)
FROM <表名或视图名> [[AS] 表别名]
[WHERE <检索条件>] ← 选取(筛选行)
[GROUP BY <字段名> [HAVING <条件表达式>]]
[ORDER BY <字段名> [ASC | DESC]]
[LIMIT 子句]
SELECT 语句由多个子句组成,其中 SELECTFROM 是必需的,其他子句可根据需要选用。各子句的书写顺序是固定的:SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT。
Part 02

无条件查询(投影查询)

只包含 SELECT…FROM 的查询,相当于关系代数中的投影运算

📌 核心概念

无条件查询不包含 WHERE 子句,只从表中选取指定的列(投影)。

在关系代数中,投影运算会自动消去重复行;而 SQL 中必须使用关键字 DISTINCT 才会消去重复行。
📝 例 6-1 查询课程表的全部内容

方法一:列出全部字段名

SELECT cno, cn, ct FROM c;

方法二:* 表示全部字段

SELECT * FROM c;
cnocnct
c1Java程序设计40
c2程序设计基础48
c3线性代数48
c4数据结构64
c5数据库系统56
c6数据挖掘32
c7高等数学60
c8控制理论32
📝 使用 AS 指定别名

可以为字段名指定别名,查询结果中别名会代替字段名显示。

SELECT cno AS 课程号, cn AS 课程名, ct AS 课时 FROM c;
课程号课程名课时
c1Java程序设计40
c2程序设计基础48
c3线性代数48
c4数据结构64
c5数据库系统56
c6数据挖掘32
c7高等数学60
c8控制理论32
📝 例 6-2 使用 DISTINCT 消除重复行

查询讲授课程的教师的教师号。

SELECT DISTINCT tno FROM tc;
tno
t1
t2
t3
t4
t5
使用 DISTINCT 去掉了查询结果中的重复元组。如果不加 DISTINCT,同一教师号可能出现多次。
📝 例 6-3 使用 LIMIT 控制结果行数 & 指定字段顺序

查询前 3 位学生的姓名、学号和专业。

SELECT sn, sno, maj FROM s LIMIT 3;
snsnomaj
王彤s1计算机
苏乐s2信息
林欣怡s3信息
查询结果中字段名的顺序由 SELECT 后面所列字段名的顺序决定(sn 在 sno 之前),这和表中原始的字段排列顺序可以不同。LIMIT 子句控制返回的行数。
Part 03

条件查询

使用 WHERE 子句指定查询条件,精确筛选所需数据

WHERE 子句常用运算符一览
运算符含义
=, >, <, >=, <=, !=, <>比较大小
AND(&&), OR(||), NOT(!)多重条件(逻辑组合)
BETWEEN AND / NOT BETWEEN AND确定范围
IN / NOT IN确定集合
LIKE / NOT LIKE字符匹配(模糊查询)
IS NULL / IS NOT NULL空值判断
比较大小

使用 =, >, <, >=, <=, !=, <> 进行数值或字符的比较。

📝 例 6-4 查询成绩 ≥ 90 分的选课信息
SELECT * FROM sc WHERE score >= 90;
snocnoscore
s1c190.50
s4c193.00
s7c7100.00
s8c396.00
📝 例 6-5 查询职称为"教授"的教师
SELECT tno, tn, maj FROM t WHERE prof = '教授';
tnotnmaj
t1刘杨计算机
t4赵礼自动化
多重条件查询(AND / OR / NOT)

使用逻辑运算符 AND、OR、NOT 组合多个条件。AND 可用 && 替代,OR 可用 || 替代。

逻辑运算符优先级(由高到低):
NOT最高
AND
OR最低
用户可以使用括号 () 改变优先级。
📝 例 6-6 查询专业是"计算机"和"数学"的学生
SELECT * FROM s WHERE maj = '计算机' OR maj = '数学';
snosnsexagemajdept
s1王彤18计算机信息学院
s5魏立17数学理学院
s6何欣荣21计算机信息学院
s7赵琳琳19数学理学院
📝 例 6-7 查询年龄在 [30,40] 之间的教师
SELECT tno AS 教师号, tn AS 姓名, prof AS 职称 FROM t WHERE age >= 30 AND age <= 40;
教师号姓名职称
t1刘杨教授
t3顾伟副教授
t5赵希希副教授
t6张刚讲师
📝 例 6-8 查询年龄不在 [30,40] 之间的教师(NOT 的使用)
SELECT tno AS 教师号, tn AS 姓名, prof AS 职称 FROM t WHERE NOT(age >= 30 AND age <= 40);
SELECT tno AS 教师号, tn AS 姓名, prof AS 职称 FROM t WHERE age < 30 OR age > 40;
📝 例 6-9 括号改变优先级

查询讲授课程号"c1"或"c2"且开课日期在 2021-09-01 及之后的教师号、课程号和开课日期。

SELECT tno, cno, tcdate FROM tc WHERE (cno = 'c1' OR cno = 'c2') AND tcdate >= '2021-09-01';
如果不加括号,AND 优先级高于 OR,条件含义将会改变!括号确保 OR 先被计算。
确定范围(BETWEEN AND)

利用 BETWEEN ANDNOT BETWEEN AND 查询字段值属于(或不属于)指定连续区间的元组。

📝 例 6-10 查询课时在 [30,40] 之间的课程
SELECT cno, cn, ct FROM c WHERE ct BETWEEN 30 AND 40;
SELECT cno, cn, ct FROM c WHERE ct >= 30 AND ct <= 40;
📝 例 6-11 查询课时不在 [30,40] 之间的课程
SELECT * FROM c WHERE ct NOT BETWEEN 30 AND 40;
SELECT * FROM c WHERE ct < 30 OR ct > 40;
确定集合(IN)

利用 INNOT IN 查询字段值属于(或不属于)指定集合的元组。

📝 例 6-12 查询课程号为"c4"和"c6"的选课信息
SELECT sno, cno, score FROM sc WHERE cno IN ('c4', 'c6');
SELECT sno, cno, score FROM sc WHERE cno = 'c4' OR cno = 'c6';
📝 例 6-13 NOT IN — 排除指定集合

查询除课程号"c4"和"c6"之外其他课程的选课信息。

SELECT sno, cno, score FROM sc WHERE cno NOT IN ('c4', 'c6');
SELECT sno, cno, score FROM sc WHERE cno <> 'c4' AND cno <> 'c6';
注意 NOT IN 的等价形式中使用的是 AND(不是 OR)。"不在集合中"意味着每个值都不等于。
部分匹配查询(模糊查询 LIKE)

当不知道完全精确的值时,可使用 LIKE 或 NOT LIKE 进行模糊查询。

语法格式:<字段名> LIKE <字符串常量>,字段名必须为字符型。

%
百分号
匹配零个或多个任意字符
_
下划线
匹配一个任意字符
📝 例 6-14 包含"程序"的课程
SELECT cno AS 课程号, cn AS 课程名, ct AS 课时 FROM c WHERE cn LIKE '%程序%';
%程序% → 课程名中任意位置包含"程序"即匹配。
📝 例 6-15 以"程序"开头的课程
SELECT cno AS 课程号, cn AS 课程名, ct AS 课时 FROM c WHERE cn LIKE '程序%';
程序% → 课程名必须以"程序"开头。
📝 例 6-16 NOT LIKE — 不以"数据"开头
SELECT * FROM c WHERE cn NOT LIKE '数据%';
📝 例 6-17 下划线 _ 的使用 — 第二个字符是"据"
SELECT * FROM c WHERE cn LIKE '_据%';
_据% → 第一个字符任意,第二个字符是"据",之后任意。
空值查询(IS NULL / IS NOT NULL)

某个字段没有值称为具有空值(NULL)。空值不同于零和空格,它不占任何存储空间。

考试成绩为 NULL(没参加考试)与成绩为 0 分(考了零分)是完全不同的!判断空值必须使用 IS NULL / IS NOT NULL,不能用 = NULL
📝 例 6-18 查询没有考试成绩的选课信息
SELECT sno, cno FROM sc WHERE score IS NULL;
📝 例 6-19 查询有考试成绩的选课信息
SELECT sno, cno FROM sc WHERE score IS NOT NULL;
Part 04

聚合函数查询

SQL 提供的聚合函数可以对一组值进行计算并返回单个值

📊 常用聚合函数
COUNT
统计元组个数
SUM
计算总和
AVG
计算平均值
MAX
求最大值
MIN
求最小值
聚合函数在计算时会自动忽略 NULL 值(COUNT(*) 除外)。因此使用 SUM、AVG 等时,NULL 不参与计算。
📝 例 6-20 SUM 和 AVG — 求总分与平均分

查询学号为"s2"的学生的总分和平均分。

本例使用的模拟数据表(sc):

snocnoscore
s2c278.00
s2c382.00
s2c5NULL
s2c791.00
SELECT SUM(score) AS 总分, AVG(score) AS 平均分 FROM sc WHERE sno = 's2';

运行结果:

总分平均分
251.0083.67
学号"s2"共选修了 4 门课程,其中 1 门成绩为 NULL。SUM 和 AVG 只计算了 3 门有效成绩,NULL 不参与计算
📝 例 6-21 MAX 和 MIN — 最高课时、最低课时

本例使用的模拟数据表(c):

cnocnct
c1Java程序设计40
c2程序设计基础48
c3线性代数48
c4数据结构64
c5数据库系统56
c6数据挖掘32
c7高等数学60
c8控制理论32
SELECT MAX(ct) AS 最高课时, MIN(ct) AS 最低课时, MAX(ct) - MIN(ct) AS 最大课时差 FROM c;

运行结果:

最高课时最低课时最大课时差
643232
📝 例 6-22 COUNT — 统计选课门数

查询学号为"s1"的学生的选课门数。

本例使用的模拟数据表(sc):

snocnoscore
s1c190.50
s1c286.00
s1c488.00
SELECT sno, COUNT(cno) AS 选课门数 FROM sc WHERE sno = 's1';

运行结果:

sno选课门数
s13
COUNT(cno) 也可写为 COUNT(sno)、COUNT(*)。但如果成绩有 NULL,不能用 COUNT(score),因为 COUNT 只对非 NULL 值计数,会导致结果偏小。
📝 例 6-23 COUNT(DISTINCT) — 统计不重复的值

查询学生表中的专业数量。

本例使用的模拟数据表(s):

snosnmaj
s1王彤计算机
s2苏乐信息
s3林欣怡信息
s4赵晨自动化
s5魏立数学
s6何欣荣计算机
s7赵琳琳数学
s8周宁信息
SELECT COUNT(DISTINCT maj) AS 专业数量 FROM s;

运行结果:

专业数量
4
DISTINCT 不可省略,它的作用是先消除 maj 字段中的重复值,再进行计数。
📝 例 6-24 COUNT(*) — 统计元组数量

查询"信息学院"的教师数量。

本例使用的模拟数据表(t):

tnotndept
t1刘杨信息学院
t2陈敏信息学院
t3顾伟信息学院
t4赵礼自动化学院
t5赵希希信息学院
t6张刚信息学院
SELECT dept, COUNT(*) AS 教师数量 FROM t WHERE dept = '信息学院';

运行结果:

dept教师数量
信息学院5
COUNT(*) 统计元组总数,不消除重复,不允许使用 DISTINCT。其中 "*" 可以用表中的任一字段名替换。
Part 05

分组查询

GROUP BY 子句将查询结果按指定字段分组,配合聚合函数进行组内统计

📝 例 6-25 GROUP BY — 每门课程的选课人数

本例使用的模拟数据表(sc):

snocnoscore
s1c190.50
s1c286.00
s1c488.00
s2c278.00
s2c382.00
s2c5NULL
s2c791.00
s3c480.00
s3c584.00
s4c193.00
s4c589.00
s5c367.00
s5c479.00
s5c576.00
s6c495.00
s6c590.00
s7c7100.00
s8c396.00
s8c685.00
SELECT cno AS 课程号, COUNT(*) AS 选课人数 FROM sc GROUP BY cno;

运行结果:

课程号选课人数
c12
c22
c33
c44
c55
c61
c72
GROUP BY cno 将 sc 表中 cno 相同的行归为一组,对每组使用 COUNT(*) 统计选课人数。
📝 例 6-26 HAVING — 对分组结果筛选

查询选修三门以上(含三门)课程的学生的学号和选课门数。

本例仍使用上面的 sc 表模拟数据。

SELECT sno AS 学号, COUNT(*) AS 选课门数 FROM sc GROUP BY sno HAVING COUNT(*) >= 3;

运行结果:

学号选课门数
s13
s24
s53
GROUP BY sno 按学号分组 → COUNT(*) 统计每组行数 → HAVING 过滤掉选课不足 3 门的组。
🔑 WHERE 与 HAVING 的区别

WHERE

作用于基本表或视图,从中选择满足条件的元组(行)

在分组之前执行。

HAVING

作用于,选择满足条件的分组

在分组之后执行,必须跟在 GROUP BY 之后。

同时使用时,子句顺序固定为:WHERE → GROUP BY → HAVING。GROUP BY 可以没有 HAVING,但 HAVING 必须依赖 GROUP BY。
Part 06

查询结果排序

ORDER BY 子句对查询结果进行排序,ASC 升序(默认),DESC 降序

📌 核心规则
  • ORDER BY 子句必须出现在其他子句(WHERE、GROUP BY、HAVING)之后
  • DESC 为降序,ASC 为升序,缺省时默认升序。
  • 可以指定多个排序字段,前面的是主排序字段,后面的是次排序字段。
📝 例 6-27 单字段降序排序

查询学号为"s2"的选课信息,按成绩降序排列。

本例使用的模拟数据表(sc):

snocnoscore
s2c278.00
s2c382.00
s2c5NULL
s2c791.00
SELECT sno, cno, score FROM sc WHERE sno = 's2' ORDER BY score DESC;

运行结果:

snocnoscore
s2c791.00
s2c382.00
s2c278.00
s2c5NULL
📝 例 6-28 排序中的默认规则

查询课程信息,按课时降序排列。

本例使用的模拟数据表(c):

cnocnct
c1Java程序设计40
c2程序设计基础48
c3线性代数48
c4数据结构64
c5数据库系统56
c6数据挖掘32
c7高等数学60
c8控制理论32
SELECT * FROM c ORDER BY ct DESC;

运行结果:

cnocnct
c4数据结构64
c7高等数学60
c5数据库系统56
c2程序设计基础48
c3线性代数48
c1Java程序设计40
c6数据挖掘32
c8控制理论32
对于相同课时的课程,这里按课程号升序展示结果,便于学生直观看出“主排序字段相同时,还会继续按其他默认顺序显示”的现象。
📝 例 6-29 多字段排序 — 主排序 + 次排序

查询课程信息,按课时降序排列,课时相同再按课程名降序排列。

本例仍使用上面的 c 表模拟数据。

SELECT * FROM c ORDER BY ct DESC, cn DESC;

运行结果:

cnocnct
c4数据结构64
c7高等数学60
c5数据库系统56
c3线性代数48
c2程序设计基础48
c1Java程序设计40
c6数据挖掘32
c8控制理论32
ct 是主排序字段,cn 是次排序字段。首先按课时降序,课时相同的再按课程名的字典序降序排列。
Part 07

限制查询结果数量(LIMIT)

LIMIT 子句用来限制查询结果返回的行数,常用于分页

📐 LIMIT 语法格式
LIMIT [OFFSET,] row_count -- 或者 LIMIT row_count OFFSET offset
  • OFFSET:偏移量(非负整数),默认为 0。OFFSET=0 表示第 1 行,OFFSET=1 表示第 2 行,以此类推。
  • row_count:返回的行数(非负整数)。若超过实际行数,则返回实际行数。
LIMIT 1, 3LIMIT 3 OFFSET 1 完全等价,都表示从第 2 行开始取 3 行。
📝 例 6-30 OFFSET — 从第 2 位教师开始取 3 位

本例使用的模拟数据表(t):

tnotnprof
t1刘杨教授
t2陈敏讲师
t3顾伟副教授
t4赵礼教授
t5赵希希副教授
t6张刚讲师
SELECT tno, tn, prof FROM t LIMIT 1, 3;
SELECT tno, tn, prof FROM t LIMIT 3 OFFSET 1;

运行结果:

tnotnprof
t2陈敏讲师
t3顾伟副教授
t4赵礼教授
这里按当前表中显示顺序(t1、t2、t3、…)来理解 OFFSET,更方便初学者掌握“跳过前几行,再取几行”的意思。
📝 例 6-31 综合应用 — GROUP BY + ORDER BY + LIMIT

查询每门课程的选课人数,按选课人数降序排列,显示前 3 行。

本例使用的模拟数据表(sc):

snocnoscore
s1c190.50
s1c286.00
s1c488.00
s2c278.00
s2c382.00
s2c5NULL
s2c791.00
s3c480.00
s3c584.00
s4c193.00
s4c589.00
s5c367.00
s5c479.00
s5c576.00
s6c495.00
s6c590.00
s7c7100.00
s8c396.00
s8c685.00
SELECT cno AS 课程号, COUNT(*) AS 选课人数 FROM sc GROUP BY cno ORDER BY 选课人数 DESC LIMIT 3;

运行结果:

课程号选课人数
c55
c44
c33
LIMIT 3 也可写作 LIMIT 0, 3 或 LIMIT 3 OFFSET 0,三者等价。
📋 本节总结 — SELECT 语句子句执行顺序
FROM确定数据来源(从哪张表取数据)
WHERE逐行筛选满足条件的元组
GROUP BY对筛选后的数据按字段分组
HAVING对分组结果进行条件过滤
SELECT选择输出的列(投影)
ORDER BY对最终结果排序
LIMIT限制返回的行数
书写顺序:SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
执行顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT