当前位置: 首页 > news >正文

【MySQL SQL语句实战】设计表,建表语句,数据插入,实战案例包括:简单查询、汇总统计、分组分析、多表关联、窗口函数

文章目录

  • 前言
  • SQL实战学习教程(MySQL版)
    • 项目介绍
    • 一、数据模型说明
    • 二、环境准备
    • 三、建表语句(DDL)
    • 四、数据插入(DML)
    • 五、SQL实战案例
      • 5.1 简单查询(单表模糊/精准查询)
        • 案例1:查询姓“曹”的学生名单
        • 案例2:查询姓名最后一个字是“玉”的学生名单
        • 案例3:查询姓名中包含“星”的学生名单
        • 案例4:查询1996年出生的学生名单
        • 案例5:查询各学生的年龄(按当前日期计算)
      • 5.2 汇总查询(聚合函数)
        • 案例1:查询课程编号为“0002”(数学)的总成绩
        • 案例2:查询选了课程的学生人数(去重)
        • 案例3:查询所有课程成绩小于85分的学生的学号、姓名
        • 案例4:查询没有学全所有课程的学生的学号、姓名
      • 5.3 分组查询(GROUP BY)
        • 案例1:查询各科成绩最高和最低得分
        • 案例2:查询每门课程被选修的学生数
        • 案例3:查询学生中男、女人数
        • 案例4:查询同名同姓学生名单并统计同名人数
      • 5.4 带条件的分组查询(HAVING)
        • 案例1:查询平均成绩大于60分学生的学号和平均成绩
        • 案例2:查询至少选修两门课程的学生学号以及课程数目
        • 案例3:查询不及格的课程并按照课程号从大到小排序
        • 案例4:查询两门以上成绩不满85分的同学的学号及其平均成绩
      • 5.5 多表查询(JOIN关联)
        • 案例1:查询所有学生的学号、姓名、选课数、总成绩
        • 案例2:查询平均成绩大于85分的所有学生的学号、姓名、平均成绩
        • 案例3:查询学生的选课情况:学号、姓名、课程号、课程名称
        • 案例4:查询出每门课程的大于80分人数和不大于80分的人数
        • 案例5:按分数段统计各科成绩([90,100]、[80,90)、[70,80)、[60,70)、<60)
        • 案例6:数据的行列互换(将课程名转为列,显示学生各科成绩)
      • 5.6 多表连接查询(复杂关联)
        • 案例1:查询课程号为“0001”(语文)且分数小于90的学生信息,按分数降序排列
        • 案例2:查询不同老师所教的不同课程的平均分,从高到低显示
        • 案例3:查询课程名称为“数学”且分数低于90的学生姓名和分数
        • 案例4:查询学过“陈独秀”老师所教的所有课的同学的学号、姓名
        • 案例5:查询至少有一门课与学号为“0001”的学生所学课程相同的学生的学号和姓名
      • 5.7 窗口函数(MySQL 8.0+)
        • 案例1:查询学生平均成绩及其名次
        • 案例2:按照各科成绩进行排序,并显示排名(不跳号)
        • 案例3:查询每门成绩最好的前两名学生的姓名
        • 案例4:按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩
    • 六、经典面试题:查找A表存在而B表不存在的ID
      • 方法1:LEFT JOIN + IS NULL(兼容性最好)
      • 方法2:NOT IN(简洁,但需注意NULL)
      • 方法3:NOT EXISTS(性能最优,推荐)
    • 七、总结

前言

  • 若对您有帮助的话,请点赞收藏加关注哦,您的关注是我持续创作的动力!有问题请私信或联系邮箱:funian.gm@gmail.com

SQL实战学习教程(MySQL版)

项目介绍

本项目专注于MySQL SQL语句的实战练习,涵盖简单查询、汇总统计、分组分析、多表关联、窗口函数等几乎所有主流使用场景。所有代码均可直接复制到MySQL客户端(如Navicat、MySQL Workbench)执行,适合SQL初学者夯实基础,也适合开发/测试人员复习巩固。

项目包含4个核心数据表(学生表、成绩表、课程表、教师表),通过“数据准备→实战案例→面试题”的结构,帮助读者循序渐进掌握SQL技能。
在这里插入图片描述

一、数据模型说明

首先明确数据表结构及关联关系,为后续查询打下基础。4个表的核心关联如下:

  • 学生表(student)与成绩表(score):通过学号(id/stu_id 关联
  • 课程表(course)与成绩表(score):通过课程号(id/course_id 关联
  • 教师表(teacher)与课程表(course):通过教师号(id/teacher_id 关联

各表字段详情如下:

表名字段名数据类型说明约束
studentidVARCHAR(20)学号主键(非空唯一)
studentnameVARCHAR(20)学生姓名非空
studentbirthDATE出生日期非空
studentsexVARCHAR(5)性别(男/女)非空
scorestu_idVARCHAR(20)学号(关联student.id联合主键(非空)
scorecourse_idVARCHAR(20)课程号(关联course.id联合主键(非空)
scoregradeFLOAT(3)成绩(0-100)非空
courseidVARCHAR(20)课程号主键(非空唯一)
coursenameVARCHAR(20)课程名称非空
courseteacher_idVARCHAR(20)教师号(关联teacher.id非空
teacheridVARCHAR(20)教师号主键(非空唯一)
teachernameVARCHAR(20)教师姓名非空

二、环境准备

  • MySQL版本:推荐5.7+或8.0+(窗口函数需8.0+支持)
  • 字符集:默认utf8mb4(支持中文及特殊字符)
  • 引擎InnoDB(支持事务和外键,确保数据完整性)

三、建表语句(DDL)

执行以下SQL创建4个核心数据表,语句包含字段注释和索引约束,可直接复制执行:

-- 1. 学生表(student):存储学生基本信息
DROP TABLE IF EXISTS student;
CREATE TABLE student (`id` VARCHAR(20) NOT NULL COMMENT '学号(主键)',`name` VARCHAR(20) NOT NULL COMMENT '学生姓名',`birth` DATE NOT NULL COMMENT '出生日期',`sex` VARCHAR(5) NOT NULL COMMENT '性别(男/女)',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生基本信息表';-- 2. 成绩表(score):存储学生课程成绩(学号+课程号联合主键,避免重复成绩)
DROP TABLE IF EXISTS score;
CREATE TABLE score (`stu_id` VARCHAR(20) NOT NULL COMMENT '学号(关联student.id)',`course_id` VARCHAR(20) NOT NULL COMMENT '课程号(关联course.id)',`grade` FLOAT(3) NOT NULL COMMENT '成绩(0-100)',PRIMARY KEY (`stu_id`, `course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生课程成绩表';-- 3. 课程表(course):存储课程信息及授课教师
DROP TABLE IF EXISTS course;
CREATE TABLE course (`id` VARCHAR(20) NOT NULL COMMENT '课程号(主键)',`name` VARCHAR(20) NOT NULL COMMENT '课程名称',`teacher_id` VARCHAR(20) NOT NULL COMMENT '教师号(关联teacher.id)',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程信息表';-- 4. 教师表(teacher):存储教师基本信息
DROP TABLE IF EXISTS teacher;
CREATE TABLE teacher (`id` VARCHAR(20) NOT NULL COMMENT '教师号(主键)',`name` VARCHAR(20) NOT NULL COMMENT '教师姓名',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='教师基本信息表';

四、数据插入(DML)

向数据表中插入测试数据,确保后续查询案例可正常运行:

-- 1. 插入学生数据(6名学生,包含不同姓名、出生日期和性别)
INSERT INTO student (`id`, `name`, `birth`, `sex`)
VALUES 
('0001', '曹喜嗯', '1996-01-01', '男'),
('0002', '曹喜嗯', '1994-02-01', '女'),
('0003', '张曼玉', '1964-09-20', '女'),
('0004', '周星驰', '1962-06-22', '男'),
('0005', '刘德华', '1961-09-27', '男'),
('0006', '林志玲', '1974-11-29', '女');-- 2. 插入成绩数据(每个学生选修2-3门课程,成绩分布在60-99分)
INSERT INTO score (`stu_id`, `course_id`, `grade`)
VALUES 
('0001', '0001', 80),  -- 曹喜嗯(0001)-语文(0001)-80分
('0001', '0002', 90),  -- 曹喜嗯(0001)-数学(0002)-90分
('0001', '0003', 99),  -- 曹喜嗯(0001)-英语(0003)-99分
('0002', '0001', 60),  -- 曹喜嗯(0002)-语文(0001)-60分
('0002', '0002', 80),  -- 曹喜嗯(0002)-数学(0002)-80分
('0002', '0003', 88),  -- 曹喜嗯(0002)-英语(0003)-88分
('0003', '0001', 85),  -- 张曼玉(0003)-语文(0001)-85分
('0003', '0002', 78),  -- 张曼玉(0003)-数学(0002)-78分
('0003', '0004', 92),  -- 张曼玉(0003)-物理(0004)-92分
('0004', '0002', 65),  -- 周星驰(0004)-数学(0002)-65分
('0004', '0003', 70),  -- 周星驰(0004)-英语(0003)-70分
('0004', '0004', 88),  -- 周星驰(0004)-物理(0004)-88分
('0005', '0001', 95),  -- 刘德华(0005)-语文(0001)-95分
('0005', '0003', 82),  -- 刘德华(0005)-英语(0003)-82分
('0006', '0002', 93),  -- 林志玲(0006)-数学(0002)-93分
('0006', '0004', 76);  -- 林志玲(0006)-物理(0004)-76分-- 3. 插入课程数据(4门课程,关联对应教师)
INSERT INTO course (`id`, `name`, `teacher_id`)
VALUES 
('0001', '语文', '0002'),  -- 语文-王后雄(0002)
('0002', '数学', '0001'),  -- 数学-陈独秀(0001)
('0003', '英语', '0003'),  -- 英语-李大钊(0003)
('0004', '物理', '0004');  -- 物理-鲁迅(0004)-- 4. 插入教师数据(4名教师,对应课程授课)
INSERT INTO teacher (`id`, `name`)
VALUES 
('0001', '陈独秀'),  -- 数学教师
('0002', '王后雄'),  -- 语文教师
('0003', '李大钊'),  -- 英语教师
('0004', '鲁迅');    -- 物理教师

五、SQL实战案例

以下案例按“基础→进阶”排序,覆盖日常开发中90%以上的SQL场景,每个案例包含需求描述可执行SQL,部分案例附加逻辑说明。

5.1 简单查询(单表模糊/精准查询)

案例1:查询姓“曹”的学生名单

需求:筛选所有姓名以“曹”开头的学生,显示学号、姓名、出生日期。

SELECT id AS 学号, name AS 姓名, birth AS 出生日期
FROM student
WHERE name LIKE '曹%';  -- %:通配符,匹配任意长度字符(包括0个)
案例2:查询姓名最后一个字是“玉”的学生名单

需求:筛选姓名末尾为“玉”的学生,显示完整信息。

SELECT id AS 学号, name AS 姓名, birth AS 出生日期, sex AS 性别
FROM student
WHERE name LIKE '%玉';  -- %匹配姓名前的任意字符
案例3:查询姓名中包含“星”的学生名单

需求:筛选姓名中包含“星”字的学生(无论位置)。

SELECT id AS 学号, name AS 姓名, sex AS 性别
FROM student
WHERE name LIKE '%星%';  -- 前后均加%,匹配中间包含指定字符的情况
案例4:查询1996年出生的学生名单

需求:筛选出生日期在1996年的学生,显示学号、姓名。

-- 方法1:使用YEAR()函数提取年份
SELECT id AS 学号, name AS 姓名
FROM student
WHERE YEAR(birth) = 1996;-- 方法2:使用日期范围(性能更优,支持索引)
SELECT id AS 学号, name AS 姓名
FROM student
WHERE birth BETWEEN '1996-01-01' AND '1996-12-31';
案例5:查询各学生的年龄(按当前日期计算)

需求:根据出生日期计算学生当前年龄,显示学号、姓名、年龄。

SELECT id AS 学号,name AS 姓名,TIMESTAMPDIFF(YEAR, birth, CURDATE()) AS 年龄  -- TIMESTAMPDIFF:计算两个日期的差值(年/月/日)
FROM student;

5.2 汇总查询(聚合函数)

常用聚合函数:SUM()(求和)、COUNT()(计数)、AVG()(平均值)、MAX()(最大值)、MIN()(最小值)。

案例1:查询课程编号为“0002”(数学)的总成绩

需求:计算所有学生数学课程的分数总和。

SELECT course_id AS 课程号,'数学' AS 课程名称,  -- 固定值显示,也可关联course表获取SUM(grade) AS 总成绩
FROM score
WHERE course_id = '0002';
案例2:查询选了课程的学生人数(去重)

需求:统计至少选修1门课程的学生数量(避免重复计数)。

SELECT COUNT(DISTINCT stu_id) AS 选课学生人数  -- DISTINCT:去重,确保1个学生只计数1次
FROM score;
案例3:查询所有课程成绩小于85分的学生的学号、姓名

需求:筛选所有课程成绩均低于85分的学生(无任何一门课≥85)。

SELECT s.id AS 学号,s.name AS 姓名
FROM student s
JOIN score sc ON s.id = sc.stu_id
GROUP BY s.id, s.name
HAVING MAX(sc.grade) < 85;  -- HAVING:筛选分组后的结果,MAX(grade) <85表示所有成绩均<85
案例4:查询没有学全所有课程的学生的学号、姓名

需求:所有课程共4门(0001-0004),筛选选修课程数<4的学生。

SELECT s.id AS 学号,s.name AS 姓名,COUNT(sc.course_id) AS 已选课程数
FROM student s
LEFT JOIN score sc ON s.id = sc.stu_id  -- LEFT JOIN:保留所有学生,包括没选课的(已选课程数为0)
GROUP BY s.id, s.name
HAVING COUNT(sc.course_id) < (SELECT COUNT(*) FROM course);  -- 子查询获取总课程数

5.3 分组查询(GROUP BY)

分组查询用于按指定字段(如课程号、性别)对数据分组,再对每组进行聚合计算。

案例1:查询各科成绩最高和最低得分

需求:按课程分组,显示每门课程的最高分、最低分。

SELECT course_id AS 课程号,c.name AS 课程名称,MAX(grade) AS 最高分,MIN(grade) AS 最低分
FROM score sc
JOIN course c ON sc.course_id = c.id  -- 关联course表获取课程名称
GROUP BY course_id, c.name  -- GROUP BY需包含非聚合字段(course_id、c.name)
ORDER BY course_id;  -- 按课程号升序排列结果
案例2:查询每门课程被选修的学生数

需求:统计每门课程的选课人数,按人数降序排列。

SELECT c.id AS 课程号,c.name AS 课程名称,COUNT(sc.stu_id) AS 选课人数
FROM course c
LEFT JOIN score sc ON c.id = sc.course_id  -- LEFT JOIN:保留无学生选修的课程(选课人数为0)
GROUP BY c.id, c.name
ORDER BY 选课人数 DESC;  -- 按人数降序,人数相同则按课程号升序(可加:, c.id ASC)
案例3:查询学生中男、女人数

需求:按性别分组,统计男生和女生的数量。

SELECT sex AS 性别,COUNT(id) AS 人数
FROM student
GROUP BY sex;
案例4:查询同名同姓学生名单并统计同名人数

需求:筛选存在重名的学生,显示姓名、同名人数。

-- 步骤1:先统计每个姓名的人数,筛选人数≥2的姓名
WITH name_count AS (SELECT name,COUNT(id) AS 同名人数FROM studentGROUP BY nameHAVING COUNT(id)2
)
-- 步骤2:关联学生表,显示重名学生的完整信息
SELECT s.id AS 学号,s.name AS 姓名,s.sex AS 性别,nc.同名人数
FROM student s
JOIN name_count nc ON s.name = nc.name
ORDER BY s.name, s.id;

5.4 带条件的分组查询(HAVING)

HAVING用于筛选分组后的结果(区别于WHERE:筛选分组前的行),需配合GROUP BY使用。

案例1:查询平均成绩大于60分学生的学号和平均成绩

需求:筛选所有课程平均分数超过60的学生,按平均成绩降序排列。

SELECT stu_id AS 学号,AVG(grade) AS 平均成绩  -- AVG(grade):计算该学生所有课程的平均分
FROM score
GROUP BY stu_id
HAVING AVG(grade) > 60  -- 筛选平均分>60的学生
ORDER BY 平均成绩 DESC;
案例2:查询至少选修两门课程的学生学号以及课程数目

需求:统计选修课程数≥2的学生,显示学号和课程数。

SELECT stu_id AS 学号,COUNT(course_id) AS 课程数目
FROM score
GROUP BY stu_id
HAVING COUNT(course_id)2  -- 筛选课程数≥2的学生
ORDER BY 课程数目 DESC;
案例3:查询不及格的课程并按照课程号从大到小排序

需求:筛选成绩<60的课程记录,按课程号降序排列。

SELECT stu_id AS 学号,course_id AS 课程号,grade AS 成绩
FROM score
WHERE grade < 60  -- WHERE:先筛选不及格的行,再排序
ORDER BY course_id DESC;
案例4:查询两门以上成绩不满85分的同学的学号及其平均成绩

需求:筛选有≥3门课程成绩<85的学生,显示学号和平均成绩。

SELECT stu_id AS 学号,AVG(grade) AS 平均成绩,SUM(CASE WHEN grade < 85 THEN 1 ELSE 0 END) AS 不满85分的课程数  -- 统计<85分的课程数量
FROM score
GROUP BY stu_id
HAVING SUM(CASE WHEN grade < 85 THEN 1 ELSE 0 END)3  -- 筛选课程数≥3的学生
ORDER BY 平均成绩 DESC;

5.5 多表查询(JOIN关联)

多表查询通过JOIN关联多个表,获取跨表的组合数据,常用INNER JOIN(内连接)、LEFT JOIN(左连接)。

案例1:查询所有学生的学号、姓名、选课数、总成绩

需求:显示所有学生(包括没选课的)的选课数量和总成绩,没选课则为0。

SELECT s.id AS 学号,s.name AS 姓名,COUNT(sc.course_id) AS 选课数,IFNULL(SUM(sc.grade), 0) AS 总成绩  -- IFNULL:将NULL替换为0(没选课的学生总成绩为0)
FROM student s
LEFT JOIN score sc ON s.id = sc.stu_id
GROUP BY s.id, s.name
ORDER BY 总成绩 DESC;
案例2:查询平均成绩大于85分的所有学生的学号、姓名、平均成绩

需求:关联学生表和成绩表,筛选平均分>85的学生。

SELECT s.id AS 学号,s.name AS 姓名,ROUND(AVG(sc.grade), 2) AS 平均成绩  -- ROUND:保留2位小数,避免小数过长
FROM student s
JOIN score sc ON s.id = sc.stu_id  -- INNER JOIN:只保留有成绩的学生
GROUP BY s.id, s.name
HAVING AVG(sc.grade) > 85
ORDER BY 平均成绩 DESC;
案例3:查询学生的选课情况:学号、姓名、课程号、课程名称

需求:显示学生的完整选课信息(跨4表:student、score、course)。

SELECT s.id AS 学号,s.name AS 姓名,c.id AS 课程号,c.name AS 课程名称,sc.grade AS 成绩
FROM student s
LEFT JOIN score sc ON s.id = sc.stu_id
LEFT JOIN course c ON sc.course_id = c.id
ORDER BY s.id, c.id;  -- 按学号、课程号排序,结果更清晰
案例4:查询出每门课程的大于80分人数和不大于80分的人数

需求:按课程分组,统计每门课程“≥80分”和“<80分”的人数。

SELECT c.id AS 课程号,c.name AS 课程名称,SUM(CASE WHEN sc.grade > 80 THEN 1 ELSE 0 END) AS 大于80分人数,SUM(CASE WHEN sc.grade ≤ 80 THEN 1 ELSE 0 END) AS 不大于80分人数
FROM course c
LEFT JOIN score sc ON c.id = sc.course_id
GROUP BY c.id, c.name
ORDER BY c.id;
案例5:按分数段统计各科成绩([90,100]、[80,90)、[70,80)、[60,70)、<60)

需求:按课程和分数段分组,统计各分段的人数。

SELECT c.id AS 课程号,c.name AS 课程名称,-- CASE WHEN:将分数映射到对应分段CASE WHEN sc.grade BETWEEN 90 AND 100 THEN '[90,100]'WHEN sc.grade BETWEEN 80 AND 89 THEN '[80,90)'WHEN sc.grade BETWEEN 70 AND 79 THEN '[70,80)'WHEN sc.grade BETWEEN 60 AND 69 THEN '[60,70)'ELSE '<60'END AS 分数段,COUNT(sc.stu_id) AS 人数
FROM course c
LEFT JOIN score sc ON c.id = sc.course_id
GROUP BY c.id, c.name, 分数段  -- 按课程和分数段分组
ORDER BY c.id, 分数段 DESC;  -- 分数段从高到低排序
案例6:数据的行列互换(将课程名转为列,显示学生各科成绩)

需求:将“学生-课程-成绩”的行数据,转为“学生-语文-数学-英语-物理”的列数据(适合报表展示)。

SELECT s.id AS 学号,s.name AS 姓名,-- 按课程名匹配成绩,MAX()消除NULL(每个学生1门课只有1个成绩)MAX(CASE WHEN c.name = '语文' THEN sc.grade END) AS 语文,MAX(CASE WHEN c.name = '数学' THEN sc.grade END) AS 数学,MAX(CASE WHEN c.name = '英语' THEN sc.grade END) AS 英语,MAX(CASE WHEN c.name = '物理' THEN sc.grade END) AS 物理
FROM student s
LEFT JOIN score sc ON s.id = sc.stu_id
LEFT JOIN course c ON sc.course_id = c.id
GROUP BY s.id, s.name
ORDER BY s.id;-- 补充:MySQL 8.0+支持PIVOT函数(更简洁)
SELECT *
FROM (SELECT s.id AS 学号, s.name AS 姓名, c.name AS 课程名, sc.grade AS 成绩FROM student sLEFT JOIN score sc ON s.id = sc.stu_idLEFT JOIN course c ON sc.course_id = c.id
) AS source_table
PIVOT (MAX(成绩) FOR 课程名 IN ('语文', '数学', '英语', '物理')  -- 课程名转为列
) AS pivot_table
ORDER BY 学号;

5.6 多表连接查询(复杂关联)

案例1:查询课程号为“0001”(语文)且分数小于90的学生信息,按分数降序排列

需求:显示语文成绩<90的学生完整信息(关联student和score表)。

SELECT s.id AS 学号,s.name AS 姓名,s.sex AS 性别,sc.grade AS 语文成绩
FROM student s
JOIN score sc ON s.id = sc.stu_id
WHERE sc.course_id = '0001' AND sc.grade < 90
ORDER BY sc.grade DESC;
案例2:查询不同老师所教的不同课程的平均分,从高到低显示

需求:按教师和课程分组,计算每门课程的平均分(关联4表)。

SELECT t.id AS 教师号,t.name AS 教师姓名,c.id AS 课程号,c.name AS 课程名称,ROUND(AVG(sc.grade), 2) AS 平均成绩
FROM teacher t
JOIN course c ON t.id = c.teacher_id
LEFT JOIN score sc ON c.id = sc.course_id
GROUP BY t.id, t.name, c.id, c.name
ORDER BY 平均成绩 DESC;
案例3:查询课程名称为“数学”且分数低于90的学生姓名和分数

需求:筛选数学成绩<90的学生(关联3表)。

SELECT s.name AS 学生姓名,sc.grade AS 数学成绩
FROM student s
JOIN score sc ON s.id = sc.stu_id
JOIN course c ON sc.course_id = c.id
WHERE c.name = '数学' AND sc.grade < 90
ORDER BY sc.grade DESC;
案例4:查询学过“陈独秀”老师所教的所有课的同学的学号、姓名

需求:陈独秀教数学(0002),筛选选修了数学的学生。

-- 方法1:子查询获取陈独秀的课程号
SELECT s.id AS 学号,s.name AS 姓名
FROM student s
JOIN score sc ON s.id = sc.stu_id
WHERE sc.course_id = (SELECT c.id FROM course cJOIN teacher t ON c.teacher_id = t.idWHERE t.name = '陈独秀'
)
GROUP BY s.id, s.name;-- 方法2:多表直接关联
SELECT DISTINCT s.id AS 学号,  -- DISTINCT:避免学生重复(若1门课多次选课)s.name AS 姓名
FROM student s
JOIN score sc ON s.id = sc.stu_id
JOIN course c ON sc.course_id = c.id
JOIN teacher t ON c.teacher_id = t.id
WHERE t.name = '陈独秀';
案例5:查询至少有一门课与学号为“0001”的学生所学课程相同的学生的学号和姓名

需求:筛选与“曹喜嗯(0001)”有共同课程的学生(排除0001本人)。

SELECT DISTINCT s.id AS 学号,s.name AS 姓名
FROM student s
JOIN score sc ON s.id = sc.stu_id
-- 子查询获取0001学生的所有课程号
WHERE sc.course_id IN (SELECT course_id FROM score WHERE stu_id = '0001')AND s.id != '0001'  -- 排除0001本人
ORDER BY s.id;

5.7 窗口函数(MySQL 8.0+)

窗口函数用于“分组内排序”或“分组内聚合”,无需压缩行(区别于GROUP BY),常用函数:RANK()(排名,跳号)、DENSE_RANK()(排名,不跳号)、ROW_NUMBER()(行号)、AVG() OVER()(分组内平均值)。

案例1:查询学生平均成绩及其名次

需求:计算每个学生的平均成绩,并按平均分降序排名(名次跳号)。

SELECT s.id AS 学号,s.name AS 姓名,ROUND(AVG(sc.grade), 2) AS 平均成绩,-- RANK():按平均成绩降序排名,相同分数名次相同,后续名次跳号RANK() OVER (ORDER BY AVG(sc.grade) DESC) AS 名次
FROM student s
JOIN score sc ON s.id = sc.stu_id
GROUP BY s.id, s.name
ORDER BY 名次;
案例2:按照各科成绩进行排序,并显示排名(不跳号)

需求:按课程分组,对每门课程的成绩降序排名(相同分数名次相同,后续名次不跳号)。

SELECT c.name AS 课程名称,s.name AS 学生姓名,sc.grade AS 成绩,-- DENSE_RANK():分组内排名,不跳号;PARTITION BY:按课程分组DENSE_RANK() OVER (PARTITION BY sc.course_id ORDER BY sc.grade DESC) AS 课程内排名
FROM score sc
JOIN student s ON sc.stu_id = s.id
JOIN course c ON sc.course_id = c.id
ORDER BY sc.course_id, 课程内排名;
案例3:查询每门成绩最好的前两名学生的姓名

需求:按课程分组,筛选每门课程成绩排名前2的学生。

-- 步骤1:先对每门课程的成绩排名
WITH course_rank AS (SELECT sc.course_id,s.id AS stu_id,s.name AS stu_name,sc.grade,ROW_NUMBER() OVER (PARTITION BY sc.course_id ORDER BY sc.grade DESC) AS rnFROM score scJOIN student s ON sc.stu_id = s.id
)
-- 步骤2:筛选排名≤2的记录
SELECT c.name AS 课程名称,cr.stu_name AS 学生姓名,cr.grade AS 成绩,cr.rn AS 排名
FROM course_rank cr
JOIN course c ON cr.course_id = c.id
WHERE cr.rn ≤ 2
ORDER BY c.id, cr.rn;
案例4:按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩

需求:显示每个学生的每门课程成绩,并附带该学生的平均成绩(无需分组压缩行)。

SELECT s.id AS 学号,s.name AS 姓名,c.name AS 课程名称,sc.grade AS 课程成绩,-- AVG() OVER():按学生分组,计算该学生的平均成绩,不压缩行ROUND(AVG(sc.grade) OVER (PARTITION BY s.id), 2) AS 学生平均成绩
FROM student s
JOIN score sc ON s.id = sc.stu_id
JOIN course c ON sc.course_id = c.id
-- 按学生平均成绩降序、课程成绩降序排序
ORDER BY 学生平均成绩 DESC, sc.grade DESC;

六、经典面试题:查找A表存在而B表不存在的ID

面试高频题:给定A表(如student)和B表(如score),查找“在A表中存在,但在B表中不存在”的ID(如“没选课的学生ID”)。以下提供3种常用方法,附优缺点分析。

方法1:LEFT JOIN + IS NULL(兼容性最好)

-- 需求:查找没选课的学生ID和姓名(student存在,score不存在)
SELECT s.id AS 学号,s.name AS 姓名
FROM student s
-- LEFT JOIN:保留student所有记录,score匹配不到的记录字段为NULL
LEFT JOIN score sc ON s.id = sc.stu_id
WHERE sc.stu_id IS NULL;  -- 筛选score中stu_id为NULL的记录(没选课)

优点:兼容性好(支持所有MySQL版本),性能稳定;
缺点:若B表数据量大,需确保关联字段(stu_id)有索引。

方法2:NOT IN(简洁,但需注意NULL)

SELECT id AS 学号,name AS 姓名
FROM student
-- NOT IN:筛选ID不在score.stu_id中的学生
WHERE id NOT IN (SELECT DISTINCT stu_id FROM score);

优点:语法简洁,易理解;
缺点:若子查询(SELECT stu_id FROM score)结果包含NULL,则整个查询返回空(NOT INNULL不兼容),需确保子查询无NULL

方法3:NOT EXISTS(性能最优,推荐)

SELECT s.id AS 学号,s.name AS 姓名
FROM student s
-- NOT EXISTS:判断“不存在匹配的score记录”,即没选课
WHERE NOT EXISTS (SELECT 1  -- 用1代替*,减少字段读取,性能更优FROM score scWHERE sc.stu_id = s.id
);

优点:性能最优(MySQL对EXISTS优化较好,无需去重),对NULL兼容;
缺点:语法相对复杂,初学者需理解子查询逻辑。

七、总结

本教程通过“数据准备→实战案例→面试题”的结构,覆盖了MySQL SQL的核心场景:

  1. 基础操作:建表(DDL)、插入数据(DML)、单表查询(DQL);
  2. 核心技能:聚合函数、分组(GROUP BY)、筛选(HAVING)、多表关联(JOIN);
  3. 进阶功能:窗口函数(排名、分组聚合)、行列互换、子查询;
  4. 面试高频:A表存在而B表不存在的ID查询(3种方法)。

建议读者:

  1. 复制代码到MySQL客户端实际执行,观察结果;
  2. 修改案例中的条件(如分数段、课程号),举一反三;
  3. 尝试添加外键约束、索引,优化查询性能(进阶学习)。

通过反复练习,可快速掌握SQL实战技能,应对日常开发和面试需求!

http://www.dtcms.com/a/465922.html

相关文章:

  • 系统设计-高频面试题(更新中...)
  • IntelliJ IDEA使用经验(十五):SQL脚本文件转为数据库控制台
  • 【实时Linux实战系列】内核跟踪点(Tracepoints)与用户态探针(UST)的协同调试
  • Linux 进程通信——消息队列与信号量
  • 备案ip 查询网站查询网站小说一键生成动漫
  • 做养生产品哪个网站好嘉兴网站建设网址
  • Vue3中实现全局双向绑定变量
  • C语言数据结构-排序
  • 【三维重建-对极几何】极线约束(Epipolar Constraint)
  • LeetCode算法日记 - Day 68: 猜数字大小II、矩阵中的最长递增路径
  • WSL 安装与卸载
  • app和微网站的对比河源网站建设
  • 新乡网站建设哪家好备案 网站名称
  • 版本控制器 git(5)--- git 标签管理
  • BShare HTTPS 集成与排查实战,从 SDK 接入到 iOS 真机调试(bshare https、签名、回调、抓包)
  • 基于同步压缩连续小波变换(SS-CWT)的微震图像去噪与起始检测
  • 太原网站建设工作室wordpress的内链插件
  • 简述网站开发流程 旅游做网站送邮箱
  • 湖北省住房建设厅网站网站备案登记查询系统
  • uri: mongodb://jack:123456@localhost://27017 数据库访问其他的写法
  • 在K8s中,seaweedFS 和 Longhorn 的比较
  • 146、【OS】【Nuttx】【周边】效果呈现方案解析:特殊变量$
  • 实现流水灯
  • 培 网站建设方案 doc台州seo网站推广
  • vue前端面试题——记录一次面试当中遇到的题(3)
  • Vuex的工作流程
  • 学习笔记:Vue Router 动态路由与参数匹配详解
  • seo怎样新建网站wordpress 底部模板
  • 高性能场景推荐使用PostgreSQL
  • 用一颗MCU跑通7B大模型:RISC-V+SRAM极致量化实战