想快速入门 SQL 吗?这 50 条必背的常用语句由易到难,覆盖了从基础查询、条件筛选、排序分页,到统计分组、多表关联,以及数据增删改操作。文末的附录还贴心地准备了学生、教师、课程、成绩表的模拟表结构和测试数据。不管你是刚接触 数据库 的新手,还是想巩固基础的同学,跟着这篇文章一步步动手练习,相信你很快就能摸清 SQL 的门道。赶紧往下看吧!
一、基础查询(1-8条):掌握“查什么、从哪查、怎么筛”
1、查询表中所有数据
需求:查询 tb_student 表中的所有字段和所有记录。
SQL实现:
-- SELECT * 表示查询所有字段,FROM 指定表名
SELECT * FROM tb_student;
解析:* 是通配符,代表表中所有列;FROM tb_student 指定数据来源是 tb_student 学生表。这是最基础的“全表查询”,适合快速查看表中所有数据。
练习:查询 tb_teacher 教师表的所有数据:SELECT * FROM tb_teacher;
2、查询指定字段
需求:查询 tb_student 表中的 stu_id(学生ID)、stu_name(学生姓名)、stu_age(学生年龄)三个字段。
SQL实现:
-- 明确写出要查询的字段名,用逗号分隔
SELECT stu_id, stu_name, stu_age FROM tb_student;
解析:实际开发中很少用 SELECT *,明确字段能减少无效数据传输。字段顺序可自定义(如:SELECT stu_name, stu_id, stu_age ...),不影响结果。
练习:查询 tb_course 课程表的 course_id(课程ID)和 course_name(课程名称):SELECT course_id, course_name FROM tb_course;
3、给字段起别名
需求:查询 tb_student 表的 stu_name 和 stu_age,并将字段名显示为“学生姓名”、“年龄”。
SQL实现:
-- AS 用于给字段起别名,AS可省略(直接用空格分隔)
SELECT stu_name AS 学生姓名, stu_age 年龄 FROM tb_student;
解析:别名让查询结果更易读,尤其字段名较简短时。若别名含空格或特殊字符,需用引号包裹(如:stu_name AS "学生 姓名")。
练习:给 tb_course 表的 course_name 起别名“课程名称”:SELECT course_name AS 课程名称 FROM tb_course;
4、去重查询
需求:查询 tb_student 表中所有不重复的 stu_class(学生班级)。
SQL实现:
-- DISTINCT 用于去除查询结果中的重复行
SELECT DISTINCT stu_class FROM tb_student;
解析:DISTINCT 作用于所有查询字段的组合,若写 SELECT DISTINCT stu_class, stu_age ...,会对“班级+年龄”的组合去重。
练习:查询 tb_score 成绩表中不重复的 course_id:SELECT DISTINCT course_id FROM tb_score;
5、条件筛选(等于/不等于)
需求:查询 tb_student 表中“班级为高一1班”的学生姓名和年龄。
SQL实现:
-- WHERE 用于添加筛选条件,= 表示“等于”
SELECT stu_name, stu_age FROM tb_student WHERE stu_class = '高一1班';
-- 扩展:查询“班级不是高一1班”的学生(!= 或 <> 都表示不等于)
-- SELECT stu_name, stu_age FROM tb_student WHERE stu_class != '高一1班';
解析:WHERE 子句必须跟在 FROM 之后,用于过滤符合条件的记录。字符型值(如:'高一1班')必须加单引号,数值型可省略(如:stu_age = 18)。
练习:查询 tb_teacher 表中“教授科目为数学”的老师姓名:SELECT tea_name FROM tb_teacher WHERE tea_subject = '数学';
6、条件筛选(范围)
需求:查询 tb_student 表中“年龄在15到17岁之间”的学生信息。
SQL实现:
-- 方式1:用 BETWEEN ... AND ... 表示闭区间(包含首尾值)
SELECT * FROM tb_student WHERE stu_age BETWEEN 15 AND 17;
-- 方式2:用 >= 和 <= 组合(与方式1效果相同)
-- SELECT * FROM tb_student WHERE stu_age >= 15 AND stu_age <= 17;
解析:BETWEEN 适合连续范围查询(日期、数值均可),例如查询出生日期范围:stu_birthday BETWEEN '2007-01-01' AND '2009-12-31'。
练习:查询 tb_score 表中“分数在75到85分之间”的记录:SELECT * FROM tb_score WHERE score BETWEEN 75 AND 85;
7、条件筛选(多条件)
需求:查询 tb_student 表中“高一1班且年龄大于16岁”的学生姓名。
SQL实现:
-- AND 表示“并且”(所有条件都满足才筛选);OR 表示“或者”(满足任一即可)
SELECT stu_name FROM tb_student WHERE stu_class = '高一1班' AND stu_age > 16;
解析:AND 和 OR 可组合使用,若有优先级需求,用括号包裹(如:stu_class = '高一1班' AND (stu_age > 16 OR stu_gender = '女'))。
练习:查询 tb_score 表中“课程ID为2且分数>85”的记录:SELECT * FROM tb_score WHERE course_id = 2 AND score > 85;
8、条件筛选(模糊匹配)
需求:查询 tb_student 表中“姓名以‘张’开头”的学生信息。
SQL实现:
-- LIKE 用于模糊匹配,% 表示“任意长度的字符”,_ 表示“单个字符”
SELECT * FROM tb_student WHERE stu_name LIKE '张%';
-- 扩展1:姓名含“晓”字:LIKE '%晓%'
-- 扩展2:姓名第二个字是“伟”:LIKE '_伟%'
解析:% 和 _ 是通配符,LIKE '张%' 匹配“张”、“张三”、“张伟”等;LIKE '_伟%' 匹配“李伟”、“张伟明”等(第二个字固定为“伟”)。
练习:查询 tb_course 表中“课程名含‘英语’”的记录:SELECT * FROM tb_course WHERE course_name LIKE '%英语%';
二、排序与限制(9-12条):控制结果的顺序和数量
9、按单个字段排序
需求:查询 tb_student 表的所有学生,按“年龄”从小到大排序。
SQL实现:
-- ORDER BY 指定排序字段,ASC 升序(默认,可省略),DESC 降序
SELECT * FROM tb_student ORDER BY stu_age ASC;
-- 扩展:按年龄从大到小排序:ORDER BY stu_age DESC
解析:ORDER BY 默认是升序(ASC),若要降序需明确写 DESC。排序字段可以是数值、字符、日期类型(字符按字典序,日期按时间先后)。
练习:查询 tb_score 表,按“分数”从高到低排序:SELECT * FROM tb_score ORDER BY score DESC;
10、按多个字段排序
需求:查询 tb_student 表,先按“班级”升序,同班级再按“年龄”降序。
SQL实现:
-- 多个排序字段用逗号分隔,优先级按顺序排列
SELECT * FROM tb_student ORDER BY stu_class ASC, stu_age DESC;
解析:先按第一个字段排序,若第一个字段值相同,再按第二个字段排序。例如:高一1班的学生先排,高一1班内年龄大的在前。
练习:查询 tb_score 表,先按“课程ID”升序,同课程再按“分数”降序:SELECT * FROM tb_score ORDER BY course_id ASC, score DESC;
11、限制结果条数
需求:查询 tb_student 表中“年龄最大的3个学生”。
SQL实现:
-- LIMIT 用于限制返回的记录数,写在最后
SELECT * FROM tb_student ORDER BY stu_age DESC LIMIT 3;
解析:LIMIT n 表示只返回前 n 条记录,必须配合 ORDER BY 使用才有意义(否则顺序随机)。
练习:查询 tb_score 表中“分数最高的5条记录”:SELECT * FROM tb_score ORDER BY score DESC LIMIT 5;
12、分页查询
需求:查询 tb_student 表,每页显示2条,查询第2页(即第3-4条记录)。
SQL实现:
-- 以下为MySQL环境语法
-- LIMIT 偏移量, 每页条数;偏移量 = (页码-1)*每页条数
SELECT * FROM tb_student ORDER BY stu_id LIMIT 2, 2;
解析:第2页的偏移量是 (2-1)*2=2,表示跳过前2条,取后面2条。分页是实际开发中常用的功能(如:列表页翻页)。
说明:LIMIT 偏移量, 条数 是MySQL特有语法,但SQL Server、Oracle等主流数据库不支持该写法(如:SQL Server用 OFFSET ... FETCH NEXT ...,Oracle用 ROWNUM),示例:
-- MySQL:分页查询:每页2条,查第2页(偏移量=(2-1)*2=2)
SELECT * FROM tb_student ORDER BY stu_id LIMIT 2, 2;
-- SQL Server等价写法
-- SELECT * FROM tb_student ORDER BY stu_id OFFSET 2 ROWS FETCH NEXT 2 ROWS ONLY;
-- Oracle等价写法(需借助子查询)
-- SELECT * FROM (SELECT *, ROWNUM rn FROM tb_student ORDER BY stu_id) WHERE rn BETWEEN 3 AND 4;
练习:查询 tb_score 表,每页3条,查第3页:SELECT * FROM tb_score ORDER BY score_id LIMIT 6, 3;
三、聚合函数(13-18条):对数据进行统计计算
13、统计记录总数
需求:统计 tb_student 表中“高一1班学生的总人数”。
SQL实现:
-- COUNT() 统计非NULL值的数量,COUNT(*) 统计所有记录(包括NULL)
SELECT COUNT(*) AS 高一1班人数 FROM tb_student WHERE stu_class = '高一1班';
解析:COUNT(*) 是最常用的统计方式,无论字段是否为 NULL 都计算;COUNT(stu_age) 会忽略 stu_age 为 NULL 的记录。AS 起别名让结果更清晰。
练习:统计 tb_score 表中“课程ID为3的记录数”:SELECT COUNT(*) AS 课程3分数记录数 FROM tb_score WHERE course_id = 3;
14、计算总和
需求:计算 tb_score 表中“课程ID为2的所有学生的分数总和”。
SQL实现:
-- SUM() 计算数值字段的总和,自动忽略NULL值
SELECT SUM(score) AS 课程2总分 FROM tb_score WHERE course_id = 2;
解析:SUM() 仅适用于数值类型字段(如:分数、年龄),若用于字符类型会报错。
练习:计算 tb_student 表中“高一2班学生的年龄总和”:SELECT SUM(stu_age) AS 高一2班年龄总和 FROM tb_student WHERE stu_class = '高一2班';
15、计算平均值
需求:计算 tb_score 表中“课程ID为1的平均分”。
SQL实现:
-- AVG() 计算数值字段的平均值,自动忽略NULL值
SELECT AVG(score) AS 课程1平均分 FROM tb_score WHERE course_id = 1;
解析:AVG(score) 等价于 SUM(score)/COUNT(score),若所有 score 都是 NULL,返回 NULL。
练习:计算 tb_student 表中“高一3班学生的平均年龄”:SELECT AVG(stu_age) AS 高一3班平均年龄 FROM tb_student WHERE stu_class = '高一3班';
16、查找最大值
需求:查找 tb_score 表中“课程ID为4的最高分数”。
SQL实现:
-- MAX() 查找字段的最大值
SELECT MAX(score) AS 课程4最高分 FROM tb_score WHERE course_id = 4;
解析:MAX() 不仅用于数值,也可用于日期(如:MAX(stu_birthday) 查最新生日)、字符(如:MAX(stu_name) 查字典序最大的姓名)。
练习:查找 tb_student 表中“最大的年龄”:SELECT MAX(stu_age) AS 最大年龄 FROM tb_student;
17、查找最小值
需求:查找 tb_score 表中“课程ID为5的最低分数”。
SQL实现:
-- MIN() 查找字段的最小值
SELECT MIN(score) AS 课程5最低分 FROM tb_score WHERE course_id = 5;
解析:与 MAX() 用法完全一致,只是功能相反(找最小值)。
练习:查找 tb_student 表中“最小的年龄”:SELECT MIN(stu_age) AS 最小年龄 FROM tb_student;
18、聚合函数组合使用
需求:统计 tb_score 表中“课程ID为3”的总分、平均分、最高分、最低分和参与人数。
SQL实现:
-- 多个聚合函数可同时使用,用逗号分隔
SELECT SUM(score) AS 总分,
AVG(score) AS 平均分,
MAX(score) AS 最高分,
MIN(score) AS 最低分,
COUNT(*) AS 参与人数
FROM tb_score WHERE course_id = 3;
解析:一次查询可完成多维度统计,无需多次执行SQL,效率更高。
练习:统计 tb_student 表中“高一4班”的人数、年龄总和、平均年龄:SELECT COUNT(*) 人数, SUM(stu_age) 年龄总和, AVG(stu_age) 平均年龄 FROM tb_student WHERE stu_class = '高一4班';
四、分组查询(19-22条):按类别统计数据
19、基础分组统计
需求:按“班级”分组,统计每个班级的学生人数。
SQL实现:
-- GROUP BY 按指定字段分组,每组进行聚合计算
SELECT stu_class AS 班级, COUNT(*) AS 人数 FROM tb_student GROUP BY stu_class;
解析:GROUP BY 必须与聚合函数配合使用,分组字段(stu_class)必须出现在 SELECT 中(非聚合字段)。例如:高一1班、高一2班分别统计人数。
练习:按“课程ID”分组,统计每门课程的分数总和:SELECT course_id 课程ID, SUM(score) 总分 FROM tb_score GROUP BY course_id;
20、分组后筛选
需求:按“班级”分组,统计人数大于10的班级。
SQL实现:
-- HAVING 用于分组后的筛选(WHERE 用于分组前的筛选)
SELECT stu_class 班级, COUNT(*) 人数
FROM tb_student
GROUP BY stu_class
HAVING COUNT(*) > 10;
解析:WHERE 和 HAVING 的区别:WHERE 筛选原始数据(如:只统计高一1班的人数),HAVING 筛选分组后的结果(如:只保留人数>10的班级)。
练习:按“课程ID”分组,统计平均分大于82的课程:SELECT course_id 课程ID, AVG(score) 平均分 FROM tb_score GROUP BY course_id HAVING AVG(score) > 82;
21、多字段分组
需求:按“班级”和“性别”分组,统计每个组合的学生人数。
SQL实现:
-- 多个字段分组,按组合统计(如:高一1班男生、高一1班女生、高一2班男生等)
SELECT stu_class 班级, stu_gender 性别, COUNT(*) 人数
FROM tb_student
GROUP BY stu_class, stu_gender;
解析:多字段分组时,只有所有分组字段的值都相同,才会被分到同一组。例如:“高一1班+男”是一个组,“高一1班+女”是另一个组。
练习:按“课程ID”和“学生ID”分组(实际是每个学生每门课的分数),统计总分:SELECT course_id, stu_id, SUM(score) 总分 FROM tb_score GROUP BY course_id, stu_id;
22、分组+排序
需求:按“班级”分组统计人数,结果按人数从多到少排序。
SQL实现:
-- GROUP BY 后可接 ORDER BY 对分组结果排序
SELECT stu_class 班级, COUNT(*) 人数
FROM tb_student
GROUP BY stu_class
ORDER BY 人数 DESC;
解析:分组后的排序字段可以是聚合函数的结果(如:人数),也可以是分组字段(如:stu_class)。
练习:按“课程ID”分组统计平均分,按平均分降序排序:SELECT course_id 课程ID, AVG(score) 平均分 FROM tb_score GROUP BY course_id ORDER BY 平均分 DESC;
五、多表连接(23-28条):关联多张表查询数据
23、内连接(INNER JOIN)
需求:查询“学生姓名”和对应的“分数”(关联 tb_student 和 tb_score 表,只显示有分数的学生)。
SQL实现:
-- INNER JOIN 只返回两表中匹配条件的记录(交集)
-- ON 指定连接条件(两表的关联字段,如:tb_student.stu_id = tb_score.stu_id)
SELECT s.stu_name 学生姓名, sc.score 分数
FROM tb_student s -- s 是 tb_student 表的别名,简化代码
INNER JOIN tb_score sc -- sc 是 tb_score 表的别名
ON s.stu_id = sc.stu_id;
解析:内连接是最常用的连接方式,只保留两表中“学生ID”匹配的记录(即有分数的学生,没有分数的学生不显示)。表别名(s、sc)可简化代码,避免字段名冲突。
练习:关联 tb_student 和 tb_score,查询高一1班学生的姓名和分数:
SELECT s.stu_name 学生姓名, sc.score 分数
FROM tb_student s
INNER JOIN tb_score sc ON s.stu_id = sc.stu_id
WHERE s.stu_class = '高一1班'; -- 增加筛选条件
24、左连接(LEFT JOIN)
需求:查询“所有学生的姓名”和对应的“分数”(即使学生没有分数,也要显示姓名,分数为空)。
SQL实现:
-- LEFT JOIN 以左表(tb_student)为基准,右表(tb_score)匹配则显示,不匹配则显示NULL
SELECT s.stu_name 学生姓名, sc.score 分数
FROM tb_student s
LEFT JOIN tb_score sc ON s.stu_id = sc.stu_id;
解析:左连接会保留左表的所有记录,右表没有匹配时,对应字段显示 NULL(如:没有参加考试的学生,分数为 NULL)。
练习:查询所有学生的姓名和课程名(关联 tb_student、tb_score、tb_course 三表):
SELECT s.stu_name 学生姓名, c.course_name 课程名
FROM tb_student s
LEFT JOIN tb_score sc ON s.stu_id = sc.stu_id
LEFT JOIN tb_course c ON sc.course_id = c.course_id;
25、右连接(RIGHT JOIN)
需求:查询“所有分数记录”对应的“学生姓名”(即使学生信息已删除,也要显示分数)。
SQL实现:
-- RIGHT JOIN 以右表(tb_score)为基准,左表(tb_student)匹配则显示,不匹配则显示NULL
SELECT s.stu_name 学生姓名, sc.score 分数
FROM tb_student s
RIGHT JOIN tb_score sc ON s.stu_id = sc.stu_id;
解析:右连接与左连接逻辑相反,保留右表所有记录,左表无匹配时显示 NULL(如:学生信息被删除,但分数记录还在,姓名为 NULL)。
练习:查询所有课程名和对应的分数(关联 tb_course 和 tb_score 右连接):
SELECT c.course_name 课程名, sc.score 分数
FROM tb_score sc
RIGHT JOIN tb_course c ON sc.course_id = c.course_id;
26、三表连接
需求:查询“学生姓名、课程名、分数”(关联 tb_student、tb_score、tb_course 三张表)。
SQL实现:
-- 三表连接:先关联tb_student和tb_score,再关联tb_course
SELECT s.stu_name 学生姓名, c.course_name 课程名, sc.score 分数
FROM tb_student s
INNER JOIN tb_score sc ON s.stu_id = sc.stu_id
INNER JOIN tb_course c ON sc.course_id = c.course_id;
解析:多表连接需依次通过 ON 指定关联关系,通常按“主表→从表”的顺序(如:tb_student→tb_score→tb_course)。
练习:查询高一2班学生的姓名、课程名和分数(增加班级筛选):
SELECT s.stu_name 学生姓名, c.course_name 课程名, sc.score 分数
FROM tb_student s
INNER JOIN tb_score sc ON s.stu_id = sc.stu_id
INNER JOIN tb_course c ON sc.course_id = c.course_id
WHERE s.stu_class = '高一2班';
27、连接时字段冲突处理
需求:查询学生的“创建时间”和分数的“录入时间”(两表都有 create_time 字段)。
SQL实现:
-- 当两表字段名相同时,需用“表别名.字段名”区分
SELECT s.stu_name 学生姓名,
s.create_time 学生创建时间,
sc.create_time 分数录入时间
FROM tb_student s
INNER JOIN tb_score sc ON s.stu_id = sc.stu_id;
解析:多表连接时,若不同表有同名字段,必须用“表别名.字段名”指定,否则SQL会报错“字段不明确”。
练习:查询老师和对应课程的创建时间(tb_teacher 和 tb_course 都有 create_time):
SELECT t.tea_name 老师姓名, c.course_name 课程名,
t.create_time 老师创建时间, c.create_time 课程创建时间
FROM tb_teacher t
INNER JOIN tb_course c ON t.tea_id = c.tea_id;
28、自连接(同表连接)
需求:查询“学生姓名”和他的“同班同学姓名”(用 tb_student 表自身连接)。
SQL实现:
-- 自连接:将一张表当作两张表用,需用不同别名区分
SELECT s1.stu_name 学生姓名, s2.stu_name 同班同学姓名
FROM tb_student s1
INNER JOIN tb_student s2 ON s1.stu_class = s2.stu_class -- 关联条件:班级相同
WHERE s1.stu_id != s2.stu_id; -- 排除自己和自己匹配的情况
解析:自连接适用于查询表内“关联数据”(如:同班同学、上下级关系),主要是给同一表起不同别名,当作两张表处理。
练习:查询分数大于85的学生及其同班同学(增加分数筛选):
SELECT s1.stu_name 高分学生, s2.stu_name 同班同学
FROM tb_student s1
INNER JOIN tb_score sc ON s1.stu_id = sc.stu_id
INNER JOIN tb_student s2 ON s1.stu_class = s2.stu_class
WHERE sc.score > 85 AND s1.stu_id != s2.stu_id;
六、子查询(29-36条):嵌套查询,用一个结果作为另一个条件
29、子查询(WHERE子句-单值)
需求:查询“分数等于课程2最高分”的学生ID和分数。
SQL实现:
-- 子查询返回单值(课程2的最高分),用=匹配
SELECT stu_id, score
FROM tb_score
WHERE course_id = 2
AND score = (SELECT MAX(score) FROM tb_score WHERE course_id = 2);
解析:子查询写在 WHERE 子句中,若返回单个值(一行一列),可用 =、>、< 等运算符匹配。
练习:查询年龄等于高一1班最大年龄的学生:
SELECT stu_name, stu_age FROM tb_student
WHERE stu_class = '高一1班'
AND stu_age = (SELECT MAX(stu_age) FROM tb_student WHERE stu_class = '高一1班');
30、子查询(WHERE子句-多值)
需求:查询“高一3班学生的所有分数记录”。
SQL实现:
-- 子查询返回多值(高一3班学生的ID),用IN匹配
SELECT stu_id, score
FROM tb_score
WHERE stu_id IN (SELECT stu_id FROM tb_student WHERE stu_class = '高一3班');
解析:若子查询返回多个值(一列多行),需用 IN(匹配任意一个)或 NOT IN(不匹配任何一个),不能用 =。
练习:查询不是高一1班和高一2班学生的分数:
SELECT stu_id, score FROM tb_score
WHERE stu_id NOT IN (SELECT stu_id FROM tb_student WHERE stu_class IN ('高一1班','高一2班'));
31、子查询(FROM子句-派生表)
需求:统计“每个班级的平均年龄”,并显示平均年龄大于16的班级。
SQL实现:
-- 子查询作为派生表(临时表),需起别名
SELECT 班级, 平均年龄
FROM (
-- 内层子查询:计算每个班级的平均年龄
SELECT stu_class 班级, AVG(stu_age) 平均年龄
FROM tb_student
GROUP BY stu_class
) AS temp -- 派生表必须起别名
WHERE 平均年龄 > 16;
解析:FROM 子句中的子查询称为“派生表”,本质是将查询结果当作一张临时表,外层再对其筛选或排序。
练习:统计每门课程的最高分,显示最高分大于92的课程:
SELECT 课程ID, 最高分 FROM (
SELECT course_id 课程ID, MAX(score) 最高分 FROM tb_score GROUP BY course_id
) AS temp WHERE 最高分 > 92;
32、子查询(SELECT子句-标量子查询)
需求:查询“每个学生的姓名”和“他的最高分数”。
SQL实现:
-- 子查询在SELECT中,返回单值(每个学生的最高分)
SELECT s.stu_name 学生姓名,
(SELECT MAX(score) FROM tb_score sc WHERE sc.stu_id = s.stu_id) 最高分数
FROM tb_student s;
解析:SELECT 中的子查询需返回单值(标量),为主查询的每一行补充对应数据(如:每个学生的最高分),无匹配时返回 NULL。
练习:查询每门课程的名称和对应的平均分:
SELECT c.course_name 课程名,
(SELECT AVG(score) FROM tb_score sc WHERE sc.course_id = c.course_id) 平均分
FROM tb_course c;
33、EXISTS子查询(判断存在)
需求:查询“有分数记录的学生姓名”(只要有一条分数记录就显示)。
SQL实现:
-- EXISTS判断子查询是否有结果,有则返回TRUE,无则FALSE
SELECT s.stu_name 学生姓名
FROM tb_student s
WHERE EXISTS (
SELECT 1 -- 子查询内容不影响结果,用1更高效
FROM tb_score sc
WHERE sc.stu_id = s.stu_id
);
解析:EXISTS 只关注子查询是否有返回行,不关心具体内容,因此内层常用 SELECT 1(比 SELECT * 高效)。
练习:查询教过课程的老师姓名:
SELECT t.tea_name 老师姓名 FROM tb_teacher t
WHERE EXISTS (SELECT 1 FROM tb_course c WHERE c.tea_id = t.tea_id);
34、NOT EXISTS子查询(判断不存在)
需求:查询“没有分数记录的学生姓名”(完全没有参加考试的学生)。
SQL实现:
-- NOT EXISTS判断子查询无结果,无则返回TRUE
SELECT s.stu_name 学生姓名
FROM tb_student s
WHERE NOT EXISTS (
SELECT 1
FROM tb_score sc
WHERE sc.stu_id = s.stu_id
);
解析:NOT EXISTS 与 EXISTS 相反,子查询无匹配时返回 TRUE,适合查询“无关联数据”的记录。
练习:查询没有分配课程的老师姓名:
SELECT t.tea_name 老师姓名 FROM tb_teacher t
WHERE NOT EXISTS (SELECT 1 FROM tb_course c WHERE c.tea_id = t.tea_id);
35、子查询与连接的转换(IN转JOIN)
需求:将“查询高一1班学生分数”的IN子查询改为JOIN查询。
SQL实现:
-- 原IN子查询:SELECT * FROM tb_score WHERE stu_id IN (SELECT stu_id FROM tb_student WHERE stu_class='高一1班')
-- 转换为INNER JOIN(性能通常更优)
SELECT sc.*
FROM tb_score sc
INNER JOIN tb_student s ON sc.stu_id = s.stu_id
WHERE s.stu_class = '高一1班';
解析:当子查询结果集较大时,JOIN 通常比 IN 性能更好,因为 IN 可能需要多次匹配,而 JOIN 是表关联匹配。
练习:将“查询有分数的学生”的EXISTS子查询改为JOIN:
SELECT DISTINCT s.stu_name FROM tb_student s
INNER JOIN tb_score sc ON s.stu_id = sc.stu_id;
36、多层子查询
需求:查询“课程1中分数大于该课程平均分的学生姓名”。
SQL实现:
-- 两层子查询:先查课程1的平均分,再查分数大于平均分的学生ID,最后查姓名
SELECT stu_name 学生姓名
FROM tb_student
WHERE stu_id IN (
SELECT stu_id
FROM tb_score
WHERE course_id = 1
AND score > (SELECT AVG(score) FROM tb_score WHERE course_id = 1)
);
解析:多层子查询需从内层向外层分析,内层结果作为外层条件。但层数越多性能可能越差,复杂场景建议用JOIN替代。
练习:查询高一2班中年龄大于班级平均分的学生姓名:
SELECT stu_name FROM tb_student
WHERE stu_class = '高一2班'
AND stu_age > (SELECT AVG(stu_age) FROM tb_student WHERE stu_class = '高一2班');
七、数据操作(插入、更新、删除)(37-45条):修改表中数据
37、插入单条数据
需求:向 tb_student 表插入一条学生数据(stu_id=201,stu_name=王磊,stu_age=16,stu_class=高一1班)。
SQL实现:
-- INSERT INTO 表名(字段1,字段2...) VALUES(值1,值2...)
INSERT INTO tb_student(stu_id, stu_name, stu_age, stu_class)
VALUES(201, '王磊', 16, '高一1班');
解析:插入时字段顺序需与值顺序一致;字符型值用单引号包裹,数值型直接写;若字段配置了自增(如:stu_id 设为 AUTO_INCREMENT)或默认值,插入时可省略该字段。
练习:向 tb_score 表插入一条分数记录(stu_id=201,course_id=1,score=88):
INSERT INTO tb_score(stu_id, course_id, score) VALUES(201, 1, 88);
38、插入多条数据
需求:向 tb_student 表批量插入3条学生数据。
SQL实现:
-- 多条数据用逗号分隔,一次插入,效率高于单条多次插入
INSERT INTO tb_student(stu_id, stu_name, stu_age, stu_class)
VALUES(202, '刘佳', 17, '高一1班'),
(203, '陈明', 16, '高一2班'),
(204, '赵雅', 17, '高一2班');
解析:批量插入减少了与数据库的交互次数,大幅提升效率。注意最后一条数据后没有逗号。
练习:向 tb_score 表批量插入3条分数记录:
INSERT INTO tb_score(stu_id, course_id, score)
VALUES(202,1,92),(203,2,85),(204,1,79);
39、插入查询结果(INSERT SELECT)
需求:将 tb_student 表中高一1班的学生复制到 tb_student_backup 备份表。
SQL实现:
-- 先确保tb_student_backup表结构与tb_student一致(字段名、类型相同)
INSERT INTO tb_student_backup(stu_id, stu_name, stu_age, stu_class)
SELECT stu_id, stu_name, stu_age, stu_class FROM tb_student WHERE stu_class = '高一1班';
解析:INSERT SELECT 直接将查询结果插入目标表,无需手动输入值,适合数据备份、迁移场景。
练习:将 tb_score 表中分数>90的记录复制到 tb_high_score 高分表:
INSERT INTO tb_high_score(stu_id, course_id, score)
SELECT stu_id, course_id, score FROM tb_score WHERE score > 90;
40、更新单条数据
需求:将 tb_student 表中stu_id=201的学生年龄改为17。
SQL实现:
-- UPDATE 表名 SET 字段=新值 WHERE 条件(条件必须明确,避免全表更新)
UPDATE tb_student
SET stu_age = 17
WHERE stu_id = 201;
解析:UPDATE 的核心是 WHERE 子句,若省略 WHERE,会更新表中所有记录(极其危险!)。更新前建议先用 SELECT * FROM tb_student WHERE stu_id = 201; 验证条件是否只匹配目标记录。
练习:将 tb_score 表中stu_id=201、course_id=1的分数改为90:
UPDATE tb_score SET score=90 WHERE stu_id=201 AND course_id=1;
41、更新多条数据
需求:将 tb_student 表中高一2班的学生年龄都增加1岁。
SQL实现:
-- 用WHERE筛选多条记录,批量更新;数值字段可直接进行算术运算
UPDATE tb_student
SET stu_age = stu_age + 1
WHERE stu_class = '高一2班';
解析:批量更新通过 WHERE 精准定位目标群体,支持字段自运算(如:score=score*1.05 表示分数提高5%)。
练习:将 tb_score 表中course_id=3的分数都减去3分(最低不低于0):
UPDATE tb_score SET score = GREATEST(score - 3, 0) WHERE course_id = 3;
42、更新多字段
需求:将 tb_student 表中stu_id=201的学生姓名改为“王大力”,班级改为“高一3班”。
SQL实现:
-- 多字段用逗号分隔,同时更新;所有操作原子性执行(要么全成功,要么全失败)
UPDATE tb_student
SET stu_name = '王大力',
stu_class = '高一3班'
WHERE stu_id = 201;
解析:多字段更新时,SET 后需按“字段=值”的格式用逗号分隔,避免遗漏或多余逗号。
练习:将 tb_score 表中stu_id=202的分数改为95,同时添加备注“卷面加分”:
UPDATE tb_score
SET score=95, score_remark='卷面加分'
WHERE stu_id=202 AND course_id=1;
43、删除单条数据
需求:删除 tb_student 表中stu_id=204的学生记录。
SQL实现:
-- DELETE FROM 表名 WHERE 条件(条件必须精确,避免误删)
DELETE FROM tb_student
WHERE stu_id = 204;
解析:DELETE 仅删除表中记录,不删除表结构。执行前必须用 SELECT 验证条件,确认无误后再执行删除。
练习:删除 tb_score 表中stu_id=204的所有分数记录:
DELETE FROM tb_score WHERE stu_id=204;
44、删除多条数据
需求:删除 tb_student 表中“高一3班且年龄小于16岁”的学生记录。
SQL实现:
-- 用多条件组合筛选目标记录,批量删除
DELETE FROM tb_student
WHERE stu_class = '高一3班' AND stu_age < 16;
解析:批量删除需通过 AND/OR 组合条件缩小范围,避免误删无关数据。
练习:删除 tb_score 表中“course_id=2且分数低于60分”的不及格记录:
DELETE FROM tb_score WHERE course_id=2 AND score < 60;
45、清空表数据(TRUNCATE)
需求:清空 tb_student_test 测试表中的所有数据(保留表结构)。
SQL实现:
-- TRUNCATE 清空表所有数据,重置自增主键,效率高于DELETE
TRUNCATE TABLE tb_student_test;
解析:TRUNCATE 与 DELETE FROM tb_student_test 的核心区别如下:
| 特性 |
TRUNCATE |
DELETE(无WHERE) |
| 执行效率 |
高(不逐行删除,释放存储空间) |
低(逐行删除,记录日志) |
| 自增主键 |
重置(下次插入从初始值开始) |
不重置(下次插入延续序列) |
| 事务支持 |
部分支持(InnoDB引擎可回滚,MyISAM不可回滚;DDL语句) |
完全支持(DML语句,任何引擎均可回滚) |
| 触发删除触发器 |
不触发 |
触发 |
注意:TRUNCATE 无法恢复数据,仅用于清空测试表或无重要数据的表。
练习:清空 tb_score_test 测试表:TRUNCATE TABLE tb_score_test;
八、基础函数(46-50条):处理数据格式与逻辑
46、字符串拼接函数(CONCAT/CONCAT_WS)
需求1:查询学生的“姓名-班级”组合(如:“王磊-高一1班”)。
SQL实现(基础通用版:CONCAT):
-- CONCAT(参数1,参数2...):拼接多个字符串,参数可为字段或固定值
-- 注意:若任一参数为NULL,整体结果为NULL
SELECT CONCAT(stu_name, '-', stu_class) AS 姓名班级组合
FROM tb_student;
需求2:查询学生的“姓名-电话”组合,若电话为空则仅显示姓名(不显示多余分隔符)。
SQL实现(MySQL特有优化版:CONCAT_WS):
-- CONCAT_WS(分隔符, 参数1, 参数2...):自动忽略NULL值,仅拼接非空参数
-- 优势:无需手动处理NULL,避免出现“赵雅-”这类多余分隔符的结果
SELECT CONCAT_WS('-', stu_name, stu_phone) AS 姓名电话组合
FROM tb_student;
解析:
(1)CONCAT基础特性:支持多个参数(字段、字符串、数值均可),但只要有一个参数为 NULL,最终结果就为 NULL。例如:赵雅的 stu_phone 为 NULL,用 CONCAT(stu_name, '-', stu_phone) 会返回 NULL,而非预期的“赵雅”。若需处理空值,需手动搭配空值函数,例如:
-- 通用写法(兼容所有数据库):处理NULL值避免结果为空
SELECT CONCAT(stu_name, IFNULL('-' + stu_phone, '')) AS 姓名电话组合
FROM tb_student;
(2)MySQL特有函数CONCAT_WS的优势:专为“带分隔符的拼接”设计,第一个参数固定为分隔符,后续参数中若有 NULL 会被自动忽略,且不会生成多余分隔符。例如:赵雅的 stu_phone 为 NULL,CONCAT_WS('-', stu_name, stu_phone) 会直接返回“赵雅”(忽略 NULL 的电话参数,不显示多余的“-”),比 CONCAT+IFNULL 更简洁高效。
扩展用法:支持3个及以上参数,同样忽略 NULL。例如:拼接“姓名-班级-电话”,班级或电话为空时仅保留非空部分:
SELECT CONCAT_WS('-', stu_name, stu_class, stu_phone) AS 学生信息组合
FROM tb_student;
-- 结果示例:王磊-高一1班-13800*38001、陈明-高一2班-13700*37003、赵雅-高一2班
练习1:拼接课程ID、课程名和老师姓名(如“1-数学-张老师”):
SELECT CONCAT(c.course_id, '-', c.course_name, '-', t.tea_name) AS 课程老师组合
FROM tb_course c
INNER JOIN tb_teacher t ON c.tea_id = t.tea_id;
练习2:拼接课程的“ID-名称-授课教师”(关联 tb_course 和 tb_teacher),若教师信息为空则仅显示课程信息:
SELECT
CONCAT_WS('-', c.course_id, c.course_name, t.tea_name) AS 课程教师组合
FROM tb_course c
LEFT JOIN tb_teacher t ON c.tea_id = t.tea_id; -- 保留无教师的课程
需求:查询学生的“出生日期”,并格式化为“2008-06-15”格式。
SQL实现:
-- DATE_FORMAT(日期字段, 格式字符串):将日期转换为指定格式的字符串
SELECT stu_name, DATE_FORMAT(stu_birthday, '%Y-%m-%d') AS 出生日期
FROM tb_student;
解析:常用日期格式符及含义:
%Y:4位年份(如:2008);%y:2位年份(如:08)
%m:2位月份(01-12);%c:1位月份(1-12)
%d:2位日期(01-31);%e:1位日期(1-31)
%H:24小时制(00-23);%h:12小时制(01-12)
%i:2位分钟(00-59);%s:2位秒(00-59)
练习:格式化分数录入时间为“2024-09-01 14:30”:
SELECT stu_id, DATE_FORMAT(score_create_time, '%Y-%m-%d %H:%i') AS 分数录入时间
FROM tb_score;
48、空值处理函数(IFNULL)
需求:查询学生的“联系电话”,若为空则显示“未填写”。
SQL实现:
-- IFNULL(字段, 替代值):字段为NULL时返回替代值,否则返回字段本身
SELECT stu_name, IFNULL(stu_phone, '未填写') AS 联系电话
FROM tb_student;
解析:IFNULL 是MySQL特有函数,仅支持两个参数。其他数据库对应函数:Oracle用 NVL(字段, 替代值),SQL Server用 ISNULL(字段, 替代值),功能完全一致。
练习:查询分数的“备注信息”,为空则显示“无特殊备注”:
SELECT score, IFNULL(score_remark, '无特殊备注') AS 备注信息
FROM tb_score;
49、条件判断函数(CASE WHEN)
需求:根据学生“年龄”划分年龄段(15岁及以下:少年;16-17岁:青少年;18岁及以上:成年)。
SQL实现:
-- CASE WHEN 条件1 THEN 结果1 WHEN 条件2 THEN 结果2 ... ELSE 默认结果 END:多分支条件判断
SELECT stu_name, stu_age,
CASE
WHEN stu_age <= 15 THEN '少年'
WHEN stu_age BETWEEN 16 AND 17 THEN '青少年'
ELSE '成年'
END AS 年龄段
FROM tb_student;
解析:CASE WHEN 支持灵活的多条件判断,条件顺序会影响结果(需将优先级高的条件放在前面)。
练习:根据分数划分等级(>=90:优秀;80-89:良好;60-79:及格;<60:不及格):
-- 注意:CASE WHEN条件按“自上而下”匹配,需将优先级高的条件放在前面
-- 正确示例(从高到低判断)
SELECT stu_id, score,
CASE
WHEN score >= 90 THEN '优秀' -- 先匹配高分段
WHEN score >= 80 THEN '良好'
WHEN score >= 60 THEN '及格'
ELSE '不及格' -- 最后匹配低分段
END AS 成绩等级
FROM tb_score;
50、数值计算函数(ROUND/聚合函数组合)
需求:计算 tb_score 表中“课程1的分数总和、平均分、最高分、最低分”,并将平均分保留2位小数。
SQL实现:
-- ROUND(数值, 小数位数):对数值四舍五入,保留指定小数位;配合聚合函数优化统计结果显示
SELECT SUM(score) AS 课程1总分,
ROUND(AVG(score), 2) AS 课程1平均分, -- 平均分保留2位小数
MAX(score) AS 课程1最高分,
MIN(score) AS 课程1最低分
FROM tb_score
WHERE course_id = 1;
解析:常用数值函数补充:
CEIL(数值):向上取整(如:CEIL(85.1) 返回86)
FLOOR(数值):向下取整(如:FLOOR(85.9) 返回85)
ABS(数值):取绝对值(如:ABS(-5) 返回5)
这些函数常与聚合函数、算术运算结合,满足数值处理需求。
练习:计算高一1班学生年龄的平均值,保留1位小数,并计算年龄总和:
SELECT SUM(stu_age) AS 高一1班年龄总和,
ROUND(AVG(stu_age), 1) AS 高一1班平均年龄
FROM tb_student
WHERE stu_class = '高一1班';
附录:创建模拟数据表及插入模拟数据
1、学生表(tb_student)
-- 创建模拟数据表
CREATE TABLE tb_student (
stu_id INT PRIMARY KEY COMMENT '学生ID(主键)',
stu_name VARCHAR(50) NOT NULL COMMENT '学生姓名',
stu_age INT COMMENT '学生年龄',
stu_gender CHAR(2) COMMENT '学生性别(男/女)',
stu_class VARCHAR(20) COMMENT '学生班级',
stu_birthday DATE COMMENT '学生出生日期',
stu_phone VARCHAR(20) COMMENT '学生联系电话',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间'
) COMMENT '学生信息表';
-- 插入模拟数据
INSERT INTO tb_student(stu_id, stu_name, stu_age, stu_gender, stu_class, stu_birthday, stu_phone)
VALUES
(201, '王磊', 16, '男', '高一1班', '2008-03-15', '13800*38001'),
(202, '刘佳', 17, '女', '高一1班', '2007-05-20', '13900*39002'),
(203, '陈明', 16, '男', '高一2班', '2008-09-10', '13700*37003'),
(204, '赵雅', 17, '女', '高一2班', '2007-11-05', NULL),
(205, '李想', 15, '男', '高一3班', '2009-01-25', '13600*36005');
2、教师表(tb_teacher)
CREATE TABLE tb_teacher (
tea_id INT PRIMARY KEY COMMENT '教师ID(主键)',
tea_name VARCHAR(50) NOT NULL COMMENT '教师姓名',
tea_subject VARCHAR(30) NOT NULL COMMENT '教授科目',
tea_age INT COMMENT '教师年龄',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间'
) COMMENT '教师信息表';
INSERT INTO tb_teacher(tea_id, tea_name, tea_subject, tea_age)
VALUES
(101, '张老师', '数学', 35),
(102, '李老师', '英语', 28),
(103, '王老师', '语文', 42);
3、课程表(tb_course)
CREATE TABLE tb_course (
course_id INT PRIMARY KEY COMMENT '课程ID(主键)',
course_name VARCHAR(50) NOT NULL COMMENT '课程名称',
tea_id INT COMMENT '授课教师ID(关联tb_teacher)',
course_credit INT COMMENT '课程学分',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
FOREIGN KEY (tea_id) REFERENCES tb_teacher(tea_id) ON DELETE SET NULL
) COMMENT '课程信息表';
INSERT INTO tb_course(course_id, course_name, tea_id, course_credit)
VALUES
(1, '高一数学', 101, 4),
(2, '高一英语', 102, 3),
(3, '高一语文', 102, 4),
(4, '高一物理', 101, 3),
(5, '高一化学', 103, 3);
4、成绩表(tb_score)
CREATE table tb_score (
score_id int primary key auto_increment comment '成绩记录ID(自增主键)',
stu_id int comment '学生ID(关联tb_student)',
course_id int comment '课程ID(关联tb_course)',
score decimal(5,2) comment '分数(保留2位小数)',
score_remark varchar(100) comment '分数备注',
score_create_time datetime default current_timestamp comment '分数录入时间',
foreign key (stu_id) references tb_student(stu_id) on delete cascade,
foreign key (course_id) references tb_course(course_id) on delete cascade
) comment '学生成绩表';
insert into tb_score(stu_id, course_id, score, score_remark)
values
(201, 1, 88.50, '正常考试'),
(201, 2, 92.00, ''),
(202, 1, 95.00, '优秀'),
(202, 3, 82.50, null),
(203, 2, 78.00, ''),
(203, 4, 90.00, '进步明显'),
(204, 1, 79.50, ''),
(204, 5, 65.00, '及格'),
(205, 3, 58.00, '不及格');
说明:我们用4张表(学生表、教师表、课程表、成绩表)构建了一套教学管理基础数据的模拟系统,所有50条SQL语句都是基于这个系统设计,可以直接在MySQL环境中模拟执行验证。SQL学习的关键在于动手实操,把这些基础语句吃透,就能逐步掌握查询、统计、关联、数据操作等基本技能。以后再遇到更复杂的业务需求,也一定能轻松应对。