【mysql 练习题】查询和“01”号同学所学课程完全相同的其他同学的学号

数据分析师求职与MySQL实战

先说一件有点难过的事情,我还是放弃了想做数据分析师~投了好多好多好多的简历,只有几个面试,可能真的和我长时间的空白期有关吧,哪怕很认真准备了很久也没办法得到青睐。把mysql的50题刷完~看一看剩下的投递里会不会有面试,这段很努力很努力的时光就暂时封存起来吧,可能未来我会在知识产权领域大有作为呢!

如果有需要数据分析相关资源的小伙伴可以找我~python,mysql,power bi,商业数据分析我这里都有学习的资料。

插入表: 

#学生表
CREATE TABLE `Student`(
`s_id` VARCHAR(20),
`s_name` VARCHAR(20) NOT NULL DEFAULT '',
`s_birth` VARCHAR(20) NOT NULL DEFAULT '',
`s_sex` VARCHAR(10) NOT NULL DEFAULT '',
PRIMARY KEY(`s_id`)
);
#课程表
CREATE TABLE `Course`(
`c_id` VARCHAR(20),
`c_name` VARCHAR(20) NOT NULL DEFAULT '',
`t_id` VARCHAR(20) NOT NULL,
PRIMARY KEY(`c_id`)
);
#教师表
CREATE TABLE `Teacher`(
`t_id` VARCHAR(20),
`t_name` VARCHAR(20) NOT NULL DEFAULT '',
PRIMARY KEY(`t_id`)
);
#成绩表
CREATE TABLE `Score`(
`s_id` VARCHAR(20),
`c_id` VARCHAR(20),
`s_score` INT(3),
PRIMARY KEY(`s_id`,`c_id`)
);
#插入学生表测试数据
insert into Student values('01' , '赵雷' , '1990-01-01' , '男');
insert into Student values('02' , '钱电' , '1990-12-21' , '男');
insert into Student values('03' , '孙风' , '1990-05-20' , '男');
insert into Student values('04' , '李云' , '1990-08-06' , '男');
insert into Student values('05' , '周梅' , '1991-12-01' , '女');
insert into Student values('06' , '吴兰' , '1992-03-01' , '女');
insert into Student values('07' , '郑竹' , '1989-07-01' , '女');
insert into Student values('08' , '王菊' , '1990-01-20' , '女');
#课程表测试数据
insert into Course values('01' , '语文' , '02');
insert into Course values('02' , '数学' , '01');
insert into Course values('03' , '英语' , '03');

#教师表测试数据
insert into Teacher values('01' , '张三');
insert into Teacher values('02' , '李四');
insert into Teacher values('03' , '王五');

#成绩表测试数据
insert into Score values('01' , '01' , 80);
insert into Score values('01' , '02' , 90);
insert into Score values('01' , '03' , 99);
insert into Score values('02' , '01' , 70);
insert into Score values('02' , '02' , 60);
insert into Score values('02' , '03' , 80);
insert into Score values('03' , '01' , 80);
insert into Score values('03' , '02' , 80);
insert into Score values('03' , '03' , 80);
insert into Score values('04' , '01' , 50);
insert into Score values('04' , '02' , 30);
insert into Score values('04' , '03' , 20);
insert into Score values('
<think> 我们面临的问题:查询学号'01'的同学学习的课程完全相同其他同学的信息。 注意:完全相同的含义是指选修的课程集合完全相同课程数量相同课程ID集合相同)。 参考引用中提供了几种不同的方法: 引用[1]:使用group_concat函数将每个学生选的课程课程ID排序后拼接成一个字符串,然后比较字符串是否相等。这种方法直观,但需要注意如果课程ID不是单字符或数字,可能需要分隔符(但题目中通常是数字,所以可以不用分隔符,但要注意课程拼接后可能产生歧义,比如课程ID 1课程ID12拼接成112,但112拼接也是112,但实际上题目中课程ID通常是固定位数或者我们可以用分隔符,但题目没有明确说明,所以实际中最好用分隔符,但本题中课程ID是数字且位数相同?题目没有明确,但为了通用性,我们可以在group_concat中用分隔符,比如逗)。 引用[2]:使用左连接NULL判断。思路是:先得到01同学课程,然后每个学生(排除01)的每门课程去匹配01课程,如果某个学生一门课程不在01课程中,那么左连接后会出现NULL(因为左连接条件是t.sid=sc.sid and t.cid=sc.cid,且t表中包含01的所有课程所有学生的组合?实际上引用[2]的方法比较复杂,它构建了一个大表,包含所有学生01同学课程,然后左连接每个学生自己的选,如果某个课程没有被选上,则sc.sid为NULL,说明这个学生没有选这门,或者选了但这里匹配不上(实际上匹配不上可能是因为该学生没有选这门,但01有这门,所以这个学生就缺了这门)。然后通过not in那些在pp表中出现sc_sid为NULL的学生(即存在至少一门01没选),剩下的就是01课程完全相同学生(因为01他们都选了,并且他们自己没有额外的?注意,这里并没有排除学生选了01没有的,所以需要进一步考虑)。 引用[3]:提供了一种思路:先选出选了01同学没有选的学生,然后排除这些学生。但是,这种方法只能排除那些选了01没有的学生,却没有检查学生是否缺了01。所以还需要补充:除了不能选01没有的,还必须选全01。 引用[4]:使用双重否定:即不存在01选的课程这个学生没选,并且也不存在这个学生选的课程01没选。但是引用[4]的代码片段只展示了部分条件。 综合思路: 方法1(推荐):使用group_concat(需要MySQL支持,且注意排序后拼接) 步骤: 1. 将每个学生选的课程课程ID排序后拼接成字符串(用逗分隔,避免歧义)。 2.学号'01'的同学课程也按同样的方式拼接。 3. 比较拼接后的字符串,相等则说明课程完全相同。 方法2:使用集合运算(通过双重否定) 条件: a.学生没有选修01同学未选修的课程(即该学生选的课程都在01课程集合中) b. 01同学选修的课程,该学生都选修了(即该学生没有漏掉01的任何一门) 等价于: a.学生选修的课程集合是01选修课程集合的子集(通过b可以保证是超集,所以就是相等) 但实际上,我们分别用两个条件: - 不存在一门课程01选了而该学生没选(即该学生选了01的每一门) - 不存在一门课程是该学生选了而01没选(即该学生没有选01之外的) 因此,我们可以这样写: SELECT * FROM student WHERE s_id IN ( SELECT s_id FROM sc GROUP BY s_id HAVING COUNT(DISTINCT c_id) = (SELECT COUNT(DISTINCT c_id) FROM sc WHERE s_id='01') -- 课程数量相等 AND -- 条件1:该学生没有01没选的(即他的都是01) SUM(CASE WHEN c_id NOT IN (SELECT c_id FROM sc WHERE s_id='01') THEN 1 ELSE 0 END) = 0 AND -- 条件2:01他都有(即不存在01他没有) SUM(CASE WHEN c_id IN (SELECT c_id FROM sc WHERE s_id='01') THEN 0 ELSE 1 END) = 0 -- 但实际上,条件1已经保证了选的都在01中,条件2是多余的?因为条件1保证了没有01之外的,条件2可以改为:他选的课程数量等于01课程数量,那么由于01课程数量他一样,并且他的都是01,那么他必然选了01的所有。所以可以简化为: -- 即:课程数量相等 且 选的都是01(没有01之外的) ) AND s_id <> '01'; 或者另一种写法(使用双重NOT EXISTS): SELECT * FROM student WHERE s_id <> '01' AND NOT EXISTS ( -- 选择01而该学生没选的 SELECT 1 FROM sc sc1 WHERE sc1.s_id = '01' AND NOT EXISTS ( SELECT 1 FROM sc sc2 WHERE sc2.s_id = student.s_id AND sc2.c_id = sc1.c_id ) ) AND NOT EXISTS ( -- 选择该学生01没选的 SELECT 1 FROM sc sc3 WHERE sc3.s_id = student.s_id AND NOT EXISTS ( SELECT 1 FROM sc sc4 WHERE sc4.s_id = '01' AND sc4.c_id = sc3.c_id ) ); 方法3:使用左连接的方法(如引用[2])但相对复杂。 我们这里提供两种写法的SQL代码: 写法1:使用group_concat(简洁,但依赖函数,且需要排序保证字符串一致) 写法2:使用双重NOT EXISTS(标准SQL,不依赖特定函数,但嵌套较多) 考虑到本题要求,我们分别给出两种写法。 注意:在写法1中,我们使用group_concat,为了确保拼接字符串一致,必须按课程ID排序。 我们假设表结构: student(s_id, s_name, ...) 学生表 sc(s_id, c_id, ...) 选表 写法1(group_concat): 步骤: 1. 生成每个学生课程ID排序后拼接的字符串(按c_id升序排列,用逗分隔) 2. 生成01学生课程ID字符串 3. 通过字符串相等来匹配 注意:由于group_concat默认有长度限制(1024),如果课程非常多,需要调整group_concat_max_len参数。但本题中课程数量一般不会太多。 SQL语句: SELECT stu.* FROM student stu JOIN ( SELECT s_id, GROUP_CONCAT(c_id ORDER BY c_id) AS courses FROM sc GROUP BY s_id ) t1 ON t1.s_id = stu.s_id JOIN ( SELECT GROUP_CONCAT(c_id ORDER BY c_id) AS courses FROM sc WHERE s_id = '01' ) t2 ON t1.courses = t2.courses WHERE stu.s_id <> '01'; -- 排除01自己 写法2(双重NOT EXISTS): SELECT stu.* FROM student stu WHERE stu.s_id <> '01' AND NOT EXISTS ( -- 01选了而当前学生没选的课程 SELECT 1 FROM sc sc1 WHERE sc1.s_id = '01' AND NOT EXISTS ( SELECT 1 FROM sc WHERE sc.s_id = stu.s_id AND sc.c_id = sc1.c_id ) ) AND NOT EXISTS ( -- 当前学生选了而01没选的课程 SELECT 1 FROM sc sc2 WHERE sc2.s_id = stu.s_id AND NOT EXISTS ( SELECT 1 FROM sc WHERE sc.s_id = '01' AND sc.c_id = sc2.c_id ) ); 写法2解释: 第一个NOT EXISTS:不存在01选了而该学生没选的。 第二个NOT EXISTS:不存在该学生选了而01没选的。 同时满足这两个条件,则课程集合相同。 两种方法都可以,但写法2更通用,且不依赖特定函数。 另外,引用[3]提供的方法(通过NOT IN)需要补充条件。引用[3]只排除了选了01没有的学生,但没有检查是否缺了01。所以我们可以这样: 先找出选了01没有的学生(这些学生肯定不符合),然后剩下的学生中,再检查他们的课程数量是否01相同,如果相同且没有01之外的,那么必然相同。 但这样需要两步,我们可以结合GROUP BY: SELECT stu.* FROM student stu WHERE stu.s_id NOT IN ( -- 选了01没有的学生 SELECT DISTINCT s_id FROM sc WHERE c_id NOT IN (SELECT c_id FROM sc WHERE s_id='01') ) AND stu.s_id <> '01' AND (SELECT COUNT(DISTINCT c_id) FROM sc WHERE s_id=stu.s_id) = (SELECT COUNT(DISTINCT c_id) FROM sc WHERE s_id='01'); 解释:先排除那些选了01没有的学生,然后剩下的学生中,他们的课程数量等于01课程数量,那么他们的课程集合就01相同(因为剩下的学生选的都是01有的,并且数量等于01课程数量,所以就是完全一样)。 这种方法也可以,而且逻辑清晰。 总结一下,我们给出三种方法: 方法一:group_concat(字符串比较) 方法二:双重NOT EXISTS(集合运算) 方法三:先排除选了01没有的学生,再检查课程数量(排除法+数量比较) 下面我们分别写出这三种方法的SQL代码: 方法一(group_concat): SELECT stu.* FROM student stu INNER JOIN ( SELECT s_id, GROUP_CONCAT(c_id ORDER BY c_id SEPARATOR ',') AS course_list FROM sc GROUP BY s_id ) t1 ON stu.s_id = t1.s_id INNER JOIN ( SELECT GROUP_CONCAT(c_id ORDER BY c_id SEPARATOR ',') AS course_list FROM sc WHERE s_id = '01' ) t2 ON t1.course_list = t2.course_list WHERE stu.s_id <> '01'; 方法二(双重NOT EXISTS): SELECT stu.* FROM student stu WHERE stu.s_id <> '01' AND NOT EXISTS ( -- 01课程而当前学生没有 SELECT 1 FROM sc sc1 WHERE sc1.s_id = '01' AND NOT EXISTS ( SELECT 1 FROM sc WHERE s_id = stu.s_id AND c_id = sc1.c_id ) ) AND NOT EXISTS ( -- 当前学生课程01没有 SELECT 1 FROM sc sc2 WHERE sc2.s_id = stu.s_id AND NOT EXISTS ( SELECT 1 FROM sc WHERE s_id = '01' AND c_id = sc2.c_id ) ); 方法三(排除法+数量比较): SELECT stu.* FROM student stu WHERE stu.s_id <> '01' AND stu.s_id NOT IN ( SELECT DISTINCT s_id FROM sc WHERE c_id NOT IN (SELECT c_id FROM sc WHERE s_id='01') ) AND (SELECT COUNT(DISTINCT c_id) FROM sc WHERE s_id=stu.s_id) = (SELECT COUNT(DISTINCT c_id) FROM sc WHERE s_id='01'); 注意:在方法三中,我们使用了子查询,注意效率问题(尤其是数据量大时)。另外,如果存在重复选(同一学生同一课程多次),应该用DISTINCT c_id(因为课程相同是指课程集合,重复选不算不同课程)。但题目中sc表应该不会重复(同一个学生一门只选一次),所以也可以不用DISTINCT,用COUNT(*)也可以,因为同一个学生一门只会出现一次(如果设计允许重复,则必须用DISTINCT)。这里假设没有重复选,那么COUNT(*)COUNT(DISTINCT c_id)结果一样,但为了严谨,我们使用COUNT(DISTINCT c_id)。 根据题目要求,选择一种写法即可。这里我们推荐方法二(双重NOT EXISTS)最为标准且通用。 我们选择方法二作为主要解答,同时提供方法一方法三供参考。 最终答案: </think> ### 查询学号'01'同学课程完全相同其他同学信息 #### 📌 核心逻辑分析 要判断课程"完全相同"需满足两个条件: 1. **课程集合相等**:其他同学课程集合必须'01'同学完全一致(既不能多也不能少) 2. **排除自身**:结果中需排除'01'同学本人 以下是三种高效实现方法(基于MySQL): --- ### 方法1:使用GROUP_CONCAT(推荐,直观高效) ```sql SELECT stu.* FROM student stu JOIN ( -- 所有学生课程集合(排序后拼接) SELECT s_id, GROUP_CONCAT(c_id ORDER BY c_id) AS course_list FROM sc GROUP BY s_id ) t1 ON stu.s_id = t1.s_id JOIN ( -- '01'同学课程集合 SELECT GROUP_CONCAT(c_id ORDER BY c_id) AS course_list FROM sc WHERE s_id = '01' ) t2 ON t1.course_list = t2.course_list WHERE stu.s_id <> '01'; ``` **原理说明**: 1. `GROUP_CONCAT(c_id ORDER BY c_id)` 将课程ID排序后拼接成字符串(如"1,2,3") 2. 通过比较拼接字符串的相等性判断课程集合完全相同 3. 支持多课程ID混合排序(如123不会混淆为"123")[^1] --- ### 方法2:双重NOT EXISTS(标准SQL,通用性强) ```sql SELECT stu.* FROM student stu WHERE s_id <> '01' AND NOT EXISTS ( -- 检查'01'有而当前学生没有的课程 SELECT 1 FROM sc sc1 WHERE sc1.s_id = '01' AND NOT EXISTS ( SELECT 1 FROM sc WHERE s_id = stu.s_id AND c_id = sc1.c_id ) ) AND NOT EXISTS ( -- 检查当前学生有而'01'没有的课程 SELECT 1 FROM sc sc2 WHERE sc2.s_id = stu.s_id AND NOT EXISTS ( SELECT 1 FROM sc WHERE s_id = '01' AND c_id = sc2.c_id ) ); ``` **原理说明**: 1. 第一个`NOT EXISTS`确保当前学生包含'01'所有课程 2. 第二个`NOT EXISTS`确保当前学生没有'01'之外的课程 3. 二者结合保证课程集合完全相等[^3][^4] --- ### 方法3:排除法 + 课程数验证 ```sql SELECT stu.* FROM student stu WHERE s_id <> '01' AND s_id NOT IN ( -- 选择过'01'未选课程学生 SELECT DISTINCT s_id FROM sc WHERE c_id NOT IN ( SELECT c_id FROM sc WHERE s_id = '01' ) ) AND ( -- 验证课程数量相同 SELECT COUNT(DISTINCT c_id) FROM sc WHERE s_id = stu.s_id ) = ( SELECT COUNT(DISTINCT c_id) FROM sc WHERE s_id = '01' ); ``` **原理说明**: 1. `NOT IN`排除选择'01'未选课程学生 2. 通过子查询验证课程数量相等 3. 二者结合保证课程集合完全相同[^3] --- ### 🌟 方法对比推荐 | 方法 | 优点 | 缺点 | 适用场景 | |-------------|-------------------------------|---------------------------|-----------------------| | GROUP_CONCAT | 简洁高效,代码可读性强 | 依赖MySQL特定函数 | 中小数据集,MySQL环境 | | NOT EXISTS | 标准SQL,所有数据库兼容 | 嵌套较深,执行计划需优化 | 跨数据库环境 | | 排除法 | 逻辑清晰,易于理解 | 需执行两次子查询 | 教学演示场景 | **推荐优先使用GROUP_CONCAT方法**,其在MySQL中性能最佳且代码最简洁。若需跨数据库兼容可选择NOT EXISTS方案。 --- ### 相关问题 1. GROUP_CONCAT函数在拼接大量数据时有哪些注意事项? 2. 如何优化双重NOT EXISTS查询的执行性能? 3. 如果课程表存在重复选记录(同一学生同一课程多次),查询逻辑需要如何调整? 4. 在哪些场景下课程集合比较更适合使用位图(bitmap)方式实现? [^1]: 引用[1] [^3]: 引用[3] [^4]: 引用[4]
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值