SQL详解之DQL

接下来,我们利用之前创建的学校选课系统数据库,为大家讲解 DML 中的查询操作。无论对于开发人员还是数据分析师,查询都是非常重要的,它关系着我们能否从关系数据库中获取我们需要的数据。建议大家把上上一节课中建库建表的 DDL 以及 上一节课中插入数据的 DML 重新执行一次,确保表和数据跟没有问题再执行下面的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
USE school;

-- 查询所有学生的所有信息
SELECT stu_id,
stu_name,
stu_sex,
stu_birth,
stu_addr,
col_id
FROM tb_student;

-- 查询学生的学号、姓名和籍贯(投影和别名)
SELECT stu_id AS 学号,
stu_name AS 姓名,
stu_addr AS 籍贯
FROM tb_student;

-- 查询所有课程的名称及学分(投影和别名)
SELECT cou_name AS 课程名称,
cou_credit AS 学分
FROM tb_course;

-- 查询所有女学生的姓名和出生日期(数据筛选)
SELECT stu_name,
stu_birth
FROM tb_student
WHERE stu_sex = 0;

-- 查询籍贯为“四川成都”的女学生的姓名和出生日期(数据筛选)
SELECT stu_name,
stu_birth
FROM tb_student
WHERE stu_sex = 0
AND stu_addr = '四川成都';

-- 查询籍贯为“四川成都”或者性别是女的学生(数据筛选)
SELECT stu_name,
stu_birth
FROM tb_student
WHERE stu_sex = 0
OR stu_addr = '四川成都';

-- 查询所有80后学生的姓名、性别和出生日期(数据筛选)
SELECT stu_name,
stu_sex,
stu_birth
FROM tb_student
WHERE '1980-1-1' <= stu_birth
AND stu_birth <= '1989-12-31';

SELECT stu_name,
stu_sex,
stu_birth
FROM tb_student
WHERE stu_birth BETWEEN '1980-1-1' AND '1989-12-31';

-- 查询学分大于2的课程的名称和学分(数据筛选)
SELECT cou_name,
cou_credit
FROM tb_course
WHERE cou_credit > 2;

-- 查询学分是奇数的课程的名称和学分(数据筛选)
SELECT cou_name,
cou_credit
FROM tb_course
WHERE cou_credit MOD 2 <> 0;

-- 查询选择选了1111的课程考试成绩在90分以上的学生学号(数据筛选)
SELECT stu_id
FROM tb_record
WHERE cou_id = 1111
AND score > 90;

-- 查询名字叫“杨过”的学生的姓名和性别(数据筛选)
SELECT stu_name AS 姓名,
CASE stu_sex WHEN 1 THEN '男' ELSE '女' END AS 性别
FROM tb_student
WHERE stu_name = '杨过';

SELECT stu_name AS 姓名,
IF(stu_sex, '男', '女') AS 性别
FROM tb_student
WHERE stu_name = '杨过';

-- 查询姓“杨”的学生姓名和性别(模糊匹配)
-- 通配符 % 匹配零个或任意多个字符
SELECT stu_name AS 姓名,
CASE stu_sex WHEN 1 THEN '男' ELSE '女' END AS 性别
FROM tb_student
WHERE stu_name LIKE '杨%';

-- 查询姓“杨”名字两个字的学生姓名和性别(模糊匹配)
-- 通过符 _ 匹配一个字符
SELECT stu_name AS 姓名,
CASE stu_sex WHEN 1 THEN '男' ELSE '女' END AS 性别
FROM tb_student
WHERE stu_name LIKE '杨_';

-- 查询姓“杨”名字三个字的学生姓名和性别(模糊匹配)
SELECT stu_name AS 姓名,
CASE stu_sex WHEN 1 THEN '男' ELSE '女' END AS 性别
FROM tb_student
WHERE stu_name LIKE '杨__';

-- 查询学号最后一位是3的学生的学号和姓名(模糊匹配)
SELECT stu_id,
stu_name
FROM tb_student
WHERE stu_id LIKE '%3';

-- 查询名字中有“不”字或“嫣”字的学生的学号和姓名(模糊匹配和并集运算)
SELECT stu_id,
stu_name
FROM tb_student
WHERE stu_name LIKE '%不%'
OR stu_name LIKE '%嫣%';

SELECT stu_id,
stu_name
FROM tb_student
WHERE stu_name LIKE '%不%'
UNION
SELECT stu_id,
stu_name
FROM tb_student
WHERE stu_name LIKE '%嫣%';

-- 查询姓“杨”或姓“林”名字三个字的学生的学号和姓名(正则表达式模糊匹配)
SELECT stu_id,
stu_name
FROM tb_student
WHERE stu_name REGEXP '[林杨][\\u4e00-\\u9fa5]{2}';

-- 查询没有录入籍贯的学生姓名(空值处理)
SELECT stu_name
FROM tb_student
WHERE TRIM(stu_addr) = ''
OR stu_addr is null;

-- 查询录入了籍贯的学生姓名(空值处理)
SELECT stu_name
FROM tb_student
WHERE TRIM(stu_addr) <> ''
AND stu_addr is not null;

-- 查询学生选课的所有日期(去重)
SELECT DISTINCT sel_date
FROM tb_record;

-- 查询学生的籍贯(去重)
SELECT DISTINCT stu_addr
FROM tb_student
WHERE TRIM(stu_addr) <> ''
AND stu_addr is not null;

-- 查询男学生的姓名和生日按年龄从大到小排列(排序)
SELECT stu_name,
stu_birth
FROM tb_student
WHERE stu_sex = 1
ORDER BY stu_birth ASC;

-- 补充:将上面的生日换算成年龄(日期函数、数值函数)
SELECT stu_name AS 姓名,
FLOOR(DATEDIFF(CURDATE(), stu_birth) / 365) AS 年龄
FROM tb_student
WHERE stu_sex = 1
ORDER BY 年龄 DESC;

-- 查询年龄最大的学生的出生日期(聚合函数)
SELECT MIN(stu_birth)
FROM tb_student;

-- 查询年龄最小的学生的出生日期(聚合函数)
SELECT MAX(stu_birth)
FROM tb_student;

-- 查询编号为1111的课程考试成绩的最高分(聚合函数)
SELECT MAX(score)
FROM tb_record
WHERE cou_id = 1111;

-- 查询学号为1001的学生考试成绩的最低分、最高分、平均分、标准差、方差(聚合函数)
SELECT MIN(score) AS 最低分,
MAX(score) AS 最高分,
ROUND(AVG(score), 1) AS 平均分,
STDDEV(score) AS 标准差,
VARIANCE(score) AS 方差
FROM tb_record
WHERE stu_id = 1001;

-- 查询学号为1001的学生考试成绩的平均分,如果有null值,null值算0分(聚合函数)
SELECT ROUND(SUM(score) / COUNT(*), 1) AS 平均分
FROM tb_record
WHERE stu_id = 1001;

-- 查询男女学生的人数(分组和聚合函数)
SELECT CASE stu_sex WHEN 1 THEN '男' ELSE '女' END AS 性别,
COUNT(*) AS 人数
FROM tb_student
GROUP BY stu_sex;

-- 查询每个学院学生人数(分组和聚合函数)
SELECT col_id AS 学院编号,
COUNT(*) AS 人数
FROM tb_student
GROUP BY col_id
WITH ROLLUP;

-- 查询每个学院男女学生人数(分组和聚合函数)
SELECT col_id AS 学院编号,
CASE stu_sex WHEN 1 THEN '男' ELSE '女' END AS 性别,
COUNT(*) AS 人数
FROM tb_student
GROUP BY col_id, stu_sex;

-- 查询每个学生的学号和平均成绩(分组和聚合函数)
SELECT stu_id AS 学号,
ROUND(AVG(score), 1) AS 平均分
FROM tb_record
GROUP BY stu_id;

-- 查询平均成绩大于等于90分的学生的学号和平均成绩(分组后的数据筛选)
SELECT stu_id AS 学号,
ROUND(AVG(score), 1) AS 平均分
FROM tb_record
GROUP BY stu_id
HAVING 平均分 >= 90;

-- 查询1111、2222、3333三门课程平均成绩大于等于90分的学生的学号和平均成绩(分组前后的数据筛选)
SELECT stu_id AS 学号,
ROUND(AVG(score), 1) AS 平均分
FROM tb_record
WHERE cou_id in (1111, 2222, 3333)
GROUP BY stu_id
HAVING 平均分 >= 90
ORDER BY 平均分 ASC;

-- 查询年龄最大的学生的姓名(子查询)
SELECT stu_name
FROM tb_student
WHERE stu_birth = (SELECT MIN(stu_birth)
FROM tb_student);

-- 查询选了两门以上的课程的学生姓名(子查询和集合运算)
SELECT stu_name
FROM tb_student
WHERE stu_id in (SELECT stu_id
FROM tb_record
GROUP BY stu_id
HAVING COUNT(*) > 2);

-- 查询学生的姓名、生日和所在学院名称(表连接)
SELECT stu_name,
stu_birth,
col_name
FROM tb_student AS t1, tb_college AS t2
WHERE t1.col_id = t2.col_id;

SELECT stu_name,
stu_birth,
col_name
FROM tb_student INNER JOIN tb_college
ON tb_student.col_id = tb_college.col_id;

SELECT stu_name,
stu_birth,
col_name
FROM tb_student NATURAL JOIN tb_college;

SELECT stu_name,
stu_birth,
col_name
FROM tb_student CROSS JOIN tb_college;

-- 查询学生姓名、课程名称以及成绩(表连接)
SELECT stu_name,
cou_name,
score
FROM tb_student, tb_course, tb_record
WHERE tb_student.stu_id = tb_record.stu_id
AND tb_course.cou_id = tb_record.cou_id
AND score is not null;

SELECT stu_name,
cou_name,
score
FROM tb_student
INNER JOIN tb_record
ON tb_student.stu_id = tb_record.stu_id
INNER JOIN tb_course
ON tb_course.cou_id = tb_record.cou_id
WHERE score is not null;

SELECT stu_name,
cou_name,
score
FROM tb_student
NATURAL JOIN tb_record
NATURAL JOIN tb_course
WHERE score is not null;

-- 补充:上面的查询结果取前5条数据(分页查询)
SELECT stu_name,
cou_name,
score
FROM tb_student
NATURAL JOIN tb_record
NATURAL JOIN tb_course
WHERE score is not null
ORDER BY cou_id ASC, score DESC
LIMIT 5;

-- 补充:上面的查询结果取第6-10条数据(分页查询)
SELECT stu_name,
cou_name,
score
FROM tb_student
NATURAL JOIN tb_record
NATURAL JOIN tb_course
WHERE score is not null
ORDER BY cou_id ASC, score DESC
LIMIT 5
OFFSET 5;

-- 补充:上面的查询结果取第11-15条数据(分页查询)
SELECT stu_name,
cou_name,
score
FROM tb_student
NATURAL JOIN tb_record
NATURAL JOIN tb_course
WHERE score is not null
ORDER BY cou_id ASC, score DESC
LIMIT 10, 5;

-- 查询选课学生的姓名和平均成绩(子查询和表连接)
-- Error Code: 1248. Every derived table must have its own alias
SELECT stu_name,
avg_score
FROM tb_student
NATURAL JOIN (SELECT stu_id,
ROUND(AVG(score), 1) AS avg_score
FROM tb_record
GROUP BY stu_id) as tmp;

-- 查询学生的姓名和选课的数量(子查询和表连接)
SELECT stu_name,
total
FROM tb_student
NATURAL JOIN (SELECT stu_id,
COUNT(*) AS total
FROM tb_record
GROUP BY stu_id) as tmp;

-- 查询每个学生的姓名和选课数量(子查询和左外连接)
SELECT stu_name AS 姓名,
COALESCE(total, 0) AS 选课数量
FROM tb_student AS t1
LEFT JOIN (SELECT stu_id,
COUNT(*) AS total
FROM tb_record
GROUP BY stu_id) AS t2
ON t1.stu_id = t2.stu_id;

有几个地方需要加以说明:

  1. MySQL目前的版本不支持全外连接,上面我们通过union操作,将左外连接和右外连接的结果求并集实现全外连接的效果。大家可以通过下面的图来加深对连表操作的认识。

  2. MySQL 中支持多种类型的运算符,包括:算术运算符(+-*/%)、比较运算符(=<><=><<=>>=BETWEEN...AND...、INIS NULLIS NOT NULLLIKERLIKEREGEXP)、逻辑运算符(NOTANDORXOR)和位运算符(&|^~>><<),我们可以在 DML 中使用这些运算符处理数据。

  3. 在查询数据时,可以在SELECT语句及其子句(如WHERE子句、ORDER BY子句、HAVING子句等)中使用函数,这些函数包括字符串函数、数值函数、时间日期函数、流程函数等,如下面的表格所示。

    常用字符串函数。

    函数 功能
    CONCAT 将多个字符串连接成一个字符串
    FORMAT 将数值格式化成字符串并指定保留几位小数
    FROM_BASE64 / TO_BASE64 BASE64解码/编码
    BIN / OCT / HEX 将数值转换成二进制/八进制/十六进制字符串
    LOCATE 在字符串中查找一个子串的位置
    LEFT / RIGHT 返回一个字符串左边/右边指定长度的字符
    LENGTH / CHAR_LENGTH 返回字符串的长度以字节/字符为单位
    LOWER / UPPER 返回字符串的小写/大写形式
    LPAD / RPAD 如果字符串的长度不足,在字符串左边/右边填充指定的字符
    LTRIM / RTRIM 去掉字符串前面/后面的空格
    ORD / CHAR 返回字符对应的编码/返回编码对应的字符
    STRCMP 比较字符串,返回-1、0、1分别表示小于、等于、大于
    SUBSTRING 返回字符串指定范围的子串

    常用数值函数。

    函数 功能
    ABS 返回一个数的绝度值
    CEILING / FLOOR 返回一个数上取整/下取整的结果
    CONV 将一个数从一种进制转换成另一种进制
    CRC32 计算循环冗余校验码
    EXP / LOG / LOG2 / LOG10 计算指数/对数
    POW 求幂
    RAND 返回[0,1)范围的随机数
    ROUND 返回一个数四舍五入后的结果
    SQRT 返回一个数的平方根
    TRUNCATE 截断一个数到指定的精度
    SIN / COS / TAN / COT / ASIN / ACOS / ATAN 三角函数

    常用时间日期函数。

    函数 功能
    CURDATE / CURTIME / NOW 获取当前日期/时间/日期和时间
    ADDDATE / SUBDATE 将两个日期表达式相加/相减并返回结果
    DATE / TIME 从字符串中获取日期/时间
    YEAR / MONTH / DAY 从日期中获取年/月/日
    HOUR / MINUTE / SECOND 从时间中获取时/分/秒
    DATEDIFF / TIMEDIFF / TIMESTAMPDIFF 返回两个时间日期表达式相差多少天/小时
    MAKEDATE / MAKETIME 制造一个日期/时间

    常用流程控制函数。

    函数 功能
    IF 根据条件是否成立返回不同的值
    IFNULL 如果为NULL则返回指定的值否则就返回本身
    NULLIF 两个表达式相等就返回NULL否则返回第一个表达式的值

    其他常用函数。

    函数 功能
    MD5 / SHA1 / SHA2 返回字符串对应的哈希摘要
    CHARSET / COLLATION 返回字符集/校对规则
    USER / CURRENT_USER 返回当前用户
    DATABASE 返回当前数据库名
    VERSION 返回当前数据库版本
    FOUND_ROWS / ROW_COUNT 返回查询到的行数/受影响的行数
    LAST_INSERT_ID 返回最后一个自增主键的值
    UUID / UUID_SHORT 返回全局唯一标识符