MySQL数据连接查询和子查询操作过程
目录
- 一、准备数据
- 二、查询语法及解释
- 1. 基础查询语法(单表 / 多表逗号连接)
- 2. JOIN连接查询语法(多表关联)
- 三、数据连接查询
- 1. 内连接查询
- 2. 左外连接查询
- 3. 右外连接查询
- 4. 交叉连接查询
- 5. 自连接查询
- 四、子查询
- 1. 子查询语法
- 2. 子查询示例
- 五、集合运算查询
- 1. 合并结果集(UNION 与 UNION ALL)
- 2. 求交集(INTERSECT )
- 3. 求差集(EXCEPT)
一、准备数据
-- 创建数据库
CREATE DATABASE IF NOT EXISTS test001;
-- 切换数据库
USE test001;
-- 删除数据表
DROP TABLE IF EXISTS student_course;
DROP TABLE IF EXISTS student;
DROP TABLE IF EXISTS course;
-- 1. 学生表(主表)
CREATE TABLE IF NOT EXISTS `student` (
student_id INT NOT NULL AUTO_INCREMENT COMMENT '学生ID(主键)',
student_no VARCHAR(20) NOT NULL COMMENT '学号(唯一标识,如2024001)',
student_name VARCHAR(50) NOT NULL COMMENT '学生姓名',
gender CHAR(1) NOT NULL COMMENT '性别(男/女)',
birth_date DATE NULL COMMENT '出生日期',
major VARCHAR(50) NOT NULL COMMENT '所属专业',
enroll_date DATE NOT NULL COMMENT '入学时间',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
-- 列级约束
PRIMARY KEY (student_id),
UNIQUE KEY uk_student_no (student_no), -- 学号唯一
CHECK (gender IN ('男', '女')) -- 限制性别只能是男或女
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci
AUTO_INCREMENT = 1001 -- 学生ID从1001开始
COMMENT = '学生信息表';
-- 2. 课程表(主表)
CREATE TABLE IF NOT EXISTS `course` (
course_id INT NOT NULL AUTO_INCREMENT COMMENT '课程ID(主键)',
course_no VARCHAR(20) NOT NULL COMMENT '课程编号(如CS101)',
course_name VARCHAR(100) NOT NULL COMMENT '课程名称',
credit TINYINT NOT NULL COMMENT '学分(1-6分)',
teacher_name VARCHAR(50) NOT NULL COMMENT '授课教师',
course_hours INT NOT NULL COMMENT '课时数',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
-- 列级约束
PRIMARY KEY (course_id),
UNIQUE KEY uk_course_no (course_no), -- 课程编号唯一
CHECK (credit BETWEEN 1 AND 6), -- 学分范围限制
CHECK (course_hours > 0) -- 课时必须为正数
)
ENGINE = InnoDB
DEFAULT CHARS编程ET = utf8mb4
COLLATE = utf8mb4_general_ci
AUTO_INCREMENT = 101 -- 课程ID从101开始
COMMENT = '课程信息表';
-- 3. 选课表(关系表,关联学生表和课程表)
CREATE TABLE IF NOT EXISTS `student_course` (
id INT NOT NULL AUTO_INCREMENT COMMENT '选课记录ID(主键)',
student_id INT NOT NULL COMMENT '学生ID(外键关联学生表)',
course_id INT NOT NULL COMMENT '课程ID(外键关联课程表)',
select_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '选课时间',
score DECIMAL(5,2) NULL COMMENT '课程成绩(0-100分,NULL表示未考试)',
is_valid TINYINT NOT NULL DEFAULT 1 COMMENT '是否有效(1-有效,0-已退课)',
-- 表级约束
PRIMARY KEY (id),
-- 联合唯一约束:同一学生不能重复选同一门课
UNIQUE KEY uk_stu_course (student_id, course_id),
-- 外键约束:关联学生表
CONSTRAINT fk_sc_student FOREIGN KEY (student_id)
REFERENCES `student`(student_id)
ON DELETE CASCADE -- 学生记录删除时,关联的选课记录自动删除
ON UPDATE CASCADE, -- 学生ID更新时,选课记录同步编程客栈更新
-- 外键约束:关联课程表
CONSTRAINT fk_sc_course FOREIGN KEY (course_id)
REFERENCES `course`(course_id)
ON DELETE CASCADE -- 课程记录删除时,关联的选课记录自动删除
ON UPDATE CASCADE,
-- 检查约束:成绩范围限制(0-100分)
CONSTRAINT chk_score CHECK (score IS NULL OR (score BETWEEN 0 AND 100)),
-- 检查约束:is_valid只能是0或1
CONSTRAINT chk_is_valid CHECK (is_valid IN (0, 1))
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci
COMMENT = '学生选课关系表';
-- 插入 500 条学生数据
DELIMITER $$
CREATE PROCEDURE InsertStudents()
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE gender CHAR(1);
DECLARE year_start DATE;
DECLARE birth DATE;
DECLARE major_name VARCHAR(50);
-- 常见专业列表
SET @majors = '计算机科学与技术,软件工程,电子信息工程,数学与应用数学,物理学,化学,生物技术,机械工程,自动化,通信工程,'
'土木工程,环境工程,经济学,金融学,会计学,法学,汉语言文学,英语,新闻传播学,临床医学,护理学';
WHILE i <= 500 DO
SET gender = IF(RAND() > 0.5, '男', '女');
-- 随机入学年份:2020 - 2024
SET year_start = MAKEDATE(2020 + FLOOR(RAND() * 5), 1 + FLOOR(RAND() * 365));
-- 出生日期:入学时 17~23 岁
SET birth = DATE_SUB(year_start, INTERVAL FLOOR(17 + RAND() * 7) YEAR);
-- 随机选择专业
SET major_name = ELT(CEILING(RAND() * 21),
'计算机科学与技术','软件工程','电子信息工程','数学与应用数学','物理学',
'化学','生物技术','机械工程','自动化','通信工程',
'土木工程','环境工程','经济学','金融学','会计学',
'法学','汉语言文学','英语','新闻传播学','临床医学','护理学'
);
INSERT INTO `student` (student_no, student_name, gender, birth_date, major, enroll_date)
VALUES (
CONCAT('20', LPAD(FLOOR(RAND() * 90 + 24), 2, '0'), LPAD(i, 3, '0')), -- 如 2024001
CONCAT(
ELT(CEILING(RAND() * 10), '张','李','王','刘','陈','杨','黄','赵','周','吴'),
ELT(CEILING(RAND() * 10), '伟','芳','敏','静','勇','磊','洋','娟','强','军')
),
gender,
birth,
major_name,
year_start
);
SET i = i + 1;
END WHILE;
END$$
DELIMITER ;
-- 执行并清理
CALL InsertStudents();
DROP PROCEDURE IF EXISTS InsertStudents;
-- 插android入 500 条课程数据
DELIMITER $$
CREATE PROCEDURE InsertCourses()
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE course_name_prefix VARCHAR(50);
DECLARE teacher_name VARCHAR(50);
SET @prefixes = '高等数学,线性代数,概率统计,C语言程序设计,Java编程,python数据分析,数据结构,算法设计,操作系统,'
'计算机网络,数据库原理,软件工程,电路分析,模拟电子技术,数字逻辑,信号与系统,电磁场与波,自动控制原理,'
'大学物理,大学化学,马克思主义基本原理,中国近代史纲要,英语读写,体育健康,艺术鉴赏,心理学导论,经济学基础';
WHILE i <= 500 DO
SET course_name_prefix = ELT(CEILING(RAND() * 27),
'高等数学','线性代数','概率统计','C语言程序设计','Java编程','Python数据分析','数据结构','算法设计','操作系统','计算机网络',
'数据库原理','软件工程','电路分析','模拟电子技术','数字逻辑','信号与系统','电磁场与波编程客栈','自动控制原理',
'大学物理','大学化学','马克思主义基本原理','中国近代史纲要','英语读写','体育健康','艺术鉴赏','心理学导论','经济学基础'
);
SET teacher_name = CONCAT(
ELT(CEILING(RAND() * 10), '张','李','王','刘','陈','杨','黄','赵','周','吴'),
'老师'
);
INSERT INTO `course` (course_no, course_name, credit, teacher_name, course_hours)
VALUES (
CONCAT('CS', LPAD(i, 3, '0')), -- CS001, CS002...
CONCAT(course_name_prefix, '(', CEILING(RAND() * 10), '期)'),
CEILING(RAND() * 6), -- 1~6 学分
teacher_name,
CASE CEILING(RAND() * 5)
WHEN 1 THEN 32
WHEN 2 THEN 48
WHEN 3 THEN 64
WHEN 4 THEN 80
ELSE 96
END
);
SET i = i + 1;
END WHILE;
END$$
DELIMITER ;
-- 执行并清理
CALL InsertCourses();
DROP PROCEDURE IF EXISTS InsertCourses;
-- 插入 500 条不重复的选课记录
DELIMITER $$
CREATE PROCEDURE InsertStudentCourse()
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE sid INT;
DECLARE cid INT;
DECLARE retry_count INT DEFAULT 0;
DECLARE max_retries INT DEFAULT 2000;
WHILE i <= 500 DO
-- 随机学生ID:1001 ~ 1500
SET sid = FLOOR(1001 + RAND() * 500);
-- 随机课程ID:101 ~ 600
SET cid = FLOOR(101 + RAND() * 500);
-- 尝试插入,跳过已存在的组合
BEGIN
DECLARE CONTINUE HANDLER FOR 1062 BEGIN END; -- 忽略重复键错误
INSERT INTO `student_course` (student_id, course_id, select_time, score, is_valid)
VALUES (
sid,
cid,
NOW() - INTERVAL FLOOR(RAND() * 365) DAY, -- 近一年内选课
IF(RAND() > 0.2, ROUND(40 + RAND() * 60, 2), NULL), -- 80% 有成绩
IF(RAND() > 0.1, 1, 0) -- 90% 有效,10% 已退课
);
-- 成功插入才计数
SET i = i + 1;
END;
SET retry_count = retry_count + 1;
IF retry_count > max_retries THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '插入失败:可能可用的 (student_id, course_id) 组合已耗尽';
END IF;
END WHILE;
END$$
DELIMITER ;
-- 执行并清理
CALL InsertStudentCourse();
DROP PROCEDURE IF EXISTS InsertStudentCourse;
-- 检查每张表的数据量
SELECT 'student' AS table_name, COUNT(*) AS count FROM student
UNION ALL
SELECT 'course' AS table_name, COUNT(*) AS count FROM course
UNION ALL
SELECT 'student_course' AS table_name, COUNT(*) AS count FROM student_course;
二、查询语法及解释
1. 基础查询语法(单表 / 多表逗号连接)
SELECT [ALL DISTINCT] <字段名> [AS 别名1] [, <字段名2> [AS 别名2]] FROM <表名1> [AS 表1别名] [, <表名2> [AS 表2别名], ...] [WHERE <检索条件>] [GROUP BY <列名1> [HAVING <条件表达式>]] [ORDER BY <列名2> [ASC DESC]];
2. JOIN连接查询语法(多表关联)
SELECT [ALL DISTINCT] 字段名1 [AS 别名1], 字段名2 [AS 别名2], ... FROM 表名1 [AS 表1别名] [INNER LEFT RIGHT [OUTER] CROSS] JOIN 表名2 [AS 表2别名] ON 条件;
| 类别 | 细分项 | 作用/说明 | 关键特征 |
|---|---|---|---|
| FROM 与 JOIN 子句 | - 左表(表名1) | 连接的基础表,作为查询的"基准"数据源 | 在 LEFT JOIN 中会保留所有记录;在 INNER JOIN 中仅保留匹配记录 |
| - 右表(表名2) | 需与左表关联的表,提供补充数据 | 在 RIGHT JOIN 中会保留所有记录;在 INNER JOIN 中仅保留匹配记录 | |
| - 连接方式 | 定义两表记录的匹配规则,决定结果集中包含哪些记录 | 不同连接类型直接影响结果集的范围(如交集、左表全量、右表全量等) | |
| 连接类型 | INNER JOIN(内连接) | 返回两表中同时满足 ON 条件的记录 | 仅保留交集,无匹配的记录不显示 |
| LEFT [OUTER] JOIN(左外连接) | 返回左表所有记录 + 右表中满足 ON 条件的匹配记录 | 左表无匹配时,右表字段显示 NULL;右表不影响左表记录的完整性 | |
| RIGHT [OUOygHRUbCTER] JOIN(右外连接) | 返回右表所有记录 + 左表中满足 ON 条件的匹配记录 | 右表无匹配时,左表字段显示 NULL;左表不影响右表记录的完整性 | |
| CROSS JOIN(交叉连接) | 返回两表的笛卡尔积(所有可能的记录组合) | 无需 ON 条件,结果行数 = 左表行数 × 右表行数,通常需配合 WHERE 过滤冗余数据 | |
| ON 子句 | 关联条件 | 定义两表记录的匹配规则(如 表1.字段A = 表2.字段B) | 过滤无效组合,仅保留逻辑关联的记录;JOIN 必须配合 ON 条件(除 CROSS JOIN 外) |
三、数据连接查询
1. 内连接查询
内连接(INNER JOIN)只会返回两表中同时满足连接条件的记录,相当于两个表的交集。
示例1:查询所有学生的选课信息,包括学生姓名、课程名称和成绩
SELECT s.student_name, c.course_name, sc.score FROM student s INNER JOIN student_course sc ON s.student_id = sc.student_id INNER JOIN course c ON sc.course_id = c.course_id;
或
SELECT s.student_name, c.course_name, sc.score FROM student s, student_course sc, course c WHERE s.student_id = sc.student_id AND sc.course_id = c.course_id;

示例2:查询选修了“计算机科学与技术”专业课程的学生姓名和课程名称
SELECT s.student_name, c.course_name FROM student s INNER JOIN student_course sc ON s.student_id = sc.student_id INNER JOIN course c ON sc.course_id = c.course_id WHERE s.major = '计算机科学与技术';
或
SELECT s.major, s.student_name, c.course_name FROM student s, student_course sc, course c WHERE s.student_id = sc.student_id AND sc.course_id = c.course_id AND s.major = '计算机科学与技术';

2. 左外连接查询
左外连接(LEFT OUTER JOIN / LEFT JOIN)以左表为基准,返回左表的所有记录,同时匹配右表中满足条件的记录。若右表无匹配,右表字段用 NULL 填充。
示例1:查询所有学生的基本信息及他们的选课成绩(含没选课的学生)。
SELECT s.student_no, s.student_name, s.major, c.course_name, sc.score FROM student s LEFT JOIN student_course sc ON s.student_id = sc.student_id LEFT JOIN course c ON sc.course_id = c.course_id ORDER BY s.student_id;

示例2:统计每个学生的选课数量(没选课的学生计数为 0)。
SELECT s.student_name, COUNT(sc.course_id) AS select_course_count FROM student s LEFT JOIN student_course sc ON s.student_id = sc.student_id GROUP BY s.student_no, s.student_name ORDER BY select_course_count DESC;

3. 右外连接查询
右外连接(RIGHT OUTER JOIN / RIGHT JOIN)以右表为基准,返回右表的所有记录,同时匹配左表中满足条件的记录。若左表无匹配,左表字段用 NULL 填充。
示例1:查询所有课程的信息及选该课的学生成绩(含没被选的课程)。
SELECT c.course_no, c.course_name, c.teacher_name, s.student_name, sc.score FROM student s RIGHT JOIN student_course sc ON s.student_id = sc.student_id RIGHT JOIN course c ON sc.course_id = c.course_id ORDER BY c.course_id;

示例2:统计每门课程的选课人数(没被选的课程计数为 0)。
SELECT c.course_name, COUNT(sc.student_id) AS student_count FROM student s RIGHT JOIN student_course sc ON s.student_id = sc.student_id RIGHT JOIN course c ON sc.course_id = c.course_id GROUP BY c.course_id, c.course_name, c.credit ORDER BY student_count DESC;

4. 交叉连接查询
交叉连接(CROSS JOIN) 用于返回两个表的笛卡尔积,即左表的每一行与右表的每一行都形成一条记录,结果集的行数 = 左表行数 × 右表行数。 交叉连接本身不使用 ON 条件过滤,通常需要配合 WHERE 子句筛选有效数据,否则结果可能包含大量冗余记录。
示例1:生成“所有学生与所有课程的组合”(例如:用于初始化选课系统的可选列表)。
SELECT s.student_name, c.course_name FROM student s CROSS JOIN course c;

示例2:在特定条件下筛选交叉组合(例如:为“计算机科学与技术”专业的学生匹配所有“计算机类”课程(课程名含“计算机”))。
SELECT s.student_name, c.course_name FROM student s CROSS JOIN course c WHERE s.major = '计算机科学与技术' AND c.course_name LIKE '%计算机%';

5. 自连接查询
自连接是表与自身的连接,即把一张表当作两张不同的表(通过别名区分),用于查询表中“具有关联关系的记录”。
示例:查找同专业的学生。
SELECT s1.student_name AS 学生A, s1.student_no 学号A, s2.student_name AS 学生B, s2.student_no 学号B, s1.major AS 共同专业 FROM student s1 INNER JOIN student s2 ON s1.major = s2.major WHERE s1.student_id < s2.student_id;
或
SELECT s1.student_name AS 学生A, s1.student_no 学号A, s2.student_name AS 学生B, s2.student_no 学号B, s1.major AS 共同专业 FROM student s1, student s2 WHERE s1.major = s2.major AND s1.student_id < s2.student_id;

四、子查询
子查询指嵌套在主查询中的查询语句,通常用于为主查询提供“条件值”或“数据集”,可放在 SELECT、FROM、WHERE 等子句中。
1. 子查询语法
子查询需用 括号 () 包裹,按返回结果可分为三类,语法结构对应不同场景:
| 子查询类型 | 返回结果 | 适用场景 | 语法示例片段 |
|---|---|---|---|
| 标量子查询 | 单个值(1行1列) | 为主查询提供单个条件值(如对比、赋值) | WHERE 主表字段 = (SELECT 字段 FROM 子表 WHERE 条件) |
| 列子查询 | 单个列的多个值(N行1列) | 配合 IN/NOT IN/ANY/ALL 筛选 | WHERE 主表字段 IN (SELECT 字段 FROM 子表 WHERE 条件) |
| 表子查询 | 多个列的数据集(N行M列) | 作为主查询的“临时表”,需起别名 | FROM (SELECT 字段1,字段2 FROM 子表 WHERE 条件) AS 临时表名 |
2. 子查询示例
示例1:标量子查询,查询与“刘娟”同专业的所有学生姓名和学号。
SELECT student_name, student_no FROM student WHERE major = ( SELECT major FROM student WHERE student_name = '刘娟' AND student_no = 2078009 );

示例2:列子查询,查询“数据结构”课程的所有选课学生姓名。
SELECT s.student_name FROM student s INNER JOIN student_course sc ON s.student_id = sc.student_id WHERE sc.course_id IN ( SELECT course_id FROM course WHERE course_name LIKE '%数据结构%' );

示例3:表子查询,查询“计算机科学与技术”专业学生的平均成绩作为临时表,查询每门课程的平均成绩。
SELECT temp.course_name, AVG(temp.score) AS avg_score FROM ( SELECT s.student_id, s.student_name, c.course_name, sc.score FROM student s INNER JOIN student_course sc ON s.student_id = sc.student_id INNER JOIN course c ON sc.course_id = c.course_id WHERE s.major = '计算机科学与技术' ) AS temp GROUP BY temp.course_name ORDER BY avg_score DESC;

示例4:EXISTS 子查询(判断是否存在记录),查询“至少选了1门课且成绩≥90分”的学生姓名。
用 EXISTS 判断子查询是否返回结果(无需关注具体值,只看“有无”),效率比 IN 更高。
SELECT student_name FROM student s WHERE EXISTS ( SELECT 1 FROM student_course sc WHERE sc.student_id = s.student_id AND sc.score >= 90 );

五、集合运算查询
集合运算用于将两个或多个查询结果集进行组合,主要包括 UNION、UNION ALL、INTERSECT 和 EXCEPT 四种,它们的核心是对结果集进行"合并"或"筛选"操作。
| 运算类型 | 语法格式 | 作用说明 | 关键特性 |
|---|---|---|---|
| UNION | 查询1 UNION 查询2 | 合并两个查询结果,自动去除重复记录 | 结果集列数和数据类型必须一致;会进行去重操作,性能略低 |
| UNION ALL | 查询1 UNION ALL 查询2 | 合并两个查询结果,保留所有记录(包括重复) | 列要求同上;不去重,性能优于 UNION |
| INTERSECT | 查询1 INTERSECT 查询2 | 返回两个结果集的交集(同时存在于两个结果集的记录) | mysql 不直接支持,需用 JOIN 替代 |
| EXCEPT | 查询1 EXCEPT 查询2 | 返回两个结果集的差集(在查询1中但不在查询2中的记录) | MySQL 不直接支持,需用 LEFT JOIN + IS NULL 替代 |
1. 合并结果集(UNION 与 UNION ALL)
查询"计算机科学与技术"专业的学生和"数据库原理"课程的选课学生,合并结果并去重。
SELECT student_id, student_name, '计算机专业学生' AS type FROM student WHERE major = '计算机科学与技术' UNION SELECT s.student_id, s.student_name, '数据库选课学生' AS type FROM student s INNER JOIN student_course sc ON s.student_id = sc.student_id INNER JOIN course c ON sc.course_id = c.course_id WHERE c.course_name LIKE '%数据库原理%';

2. 求交集(INTERSECT )
查询既选了"数据库"又选了"计算机"的学生姓名。
SELECT s1.student_name FROM ( SELECT s.student_id, s.student_name FROM student s INNER JOIN student_course sc ON s.student_id = sc.student_id INNER JOIN course c ON sc.course_id = c.course_id WHERE c.course_name LIKE '%数据库%' ) s1 INNER JOIN ( SELECT s.student_id FROM student s INNER JOIN student_course sc ON s.student_id = sc.student_id INNER JOIN course c ON sc.course_id = c.course_id WHERE c.course_name LIKE '%计算机%' ) s2 ON s1.student_id = s2.student_id;

3. 求差集(EXCEPT)
查询选了课但成绩未录入(score为NULL)的学生,排除已经退课的(is_valid=0)。
SELECT s.student_name, sc.id AS 选课记录ID
FROM student_course sc
INNER JOIN student s ON sc.student_id = s.student_id
WHERE sc.score IS NULL
AND NOT EXISTS (
SELECT 1
FROM student_course sc2
WHERE sc2.id = sc.id
AND sc2.is_valid = 0
);

到此这篇关于MySQL数据连接查询和子查询操作的文章就介绍到这了,更多相关mysql连接查询与子查询内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
加载中,请稍侯......
精彩评论