7、查询学过编号为“01”的课程并且也学过编号为“02”的课程的学生的学号、姓名(重点)

本篇博客主要讲解如何使用SQL查询语句来找出学习过课程编号为01和02的所有学生,包括他们的学号和姓名。通过实例解析SQL的交集操作在解决此类问题中的应用。
-- 7、查询学过编号为“01”的课程并且也学过编号为“02”的课程的学生的学号、姓名(重点)
-- 法一错误
-- 相同字段c_id不可能即是01又是02
SELECT a.c_id,b.s_id,b.s_name 
FROM 
score as a
INNER JOIN
student as b ON a.s_id=b.s_id
WHERE a.c_id=
<think> 我们有一个需求:查询学过课程编号为"01"且学过课程编号为"02"的学生学号姓名。 参考引用[1]引用[2]中的方法,我们可以使用两种主要方式:使用连接(JOIN)或使用子查询(IN)。 但是要注意,引用[2]中使用了两个子查询(一个在WHERE子句中使用IN,另一个在FROM子句中)来找出同时满足条件的学生。 方法一:使用自连接 我们可以两次连接成绩表(score表),分别连接课程01课程02,然后判断同一个学生在这两门课上都有记录。 参考引用[1]中连接的方式,但这里需要连接两次成绩表。 方法二:使用子查询 我们可以先找出所有学过课程01学生,再找出所有学过课程02学生,然后取交集(使用IN操作符)[^2]。 或者使用交集(INTERSECT)操作,但Hive可能不支持INTERSECT(取决于版本),所以通常使用IN或EXISTS。 我们选择两种方法来实现: 方法一(自连接): 步骤: 1. 从成绩表(假设表名为score)中分别取出课程01课程02的记录,作为两个临时表。 2. 将这两个临时表通过学生ID连接,得到同时有这两门课的学生ID。 3. 再连接学生表(student)获得学生姓名。 方法二(子查询): 步骤: 1. 从成绩表中查询所有选了课程01学生ID。 2. 从成绩表中查询所有选了课程02学生ID。 3. 在成绩表中查询学生ID同时存在于前两个结果集的学生ID(或者直接使用IN,但注意需要同时满足两个条件,所以用AND连接两个IN条件是不对的,因为同一个学生不会在同一行同时满足两个条件,所以应该用两个子查询的交集)。 4. 然后连接学生表获得姓名。 但是注意,引用[2]中使用了以下方式: 先通过一个子查询找出同时选了0102课程学生ID(通过在01课程学生ID中筛选那些也在02课程学生ID列表中的学生),然后连接学生表。 我们参考引用[2]的写法,但也可以优化。 考虑到性能,在Hive中,使用JOIN通常比使用IN子查询性能更好,因为IN子查询可能会导致性能问题(特别是当子查询结果很大时)。 因此,我们给出两种写法: 写法1:使用自连接(推荐) ```sql SELECT s.student_id, s.student_name FROM student s JOIN score sc1 ON s.student_id = sc1.student_id AND sc1.course_id = &#39;01&#39; JOIN score sc2 ON s.student_id = sc2.student_id AND sc2.course_id = &#39;02&#39;; ``` 写法2:使用子查询(IN) ```sql SELECT s.student_id, s.student_name FROM student s WHERE s.student_id IN (SELECT student_id FROM score WHERE course_id = &#39;01&#39;) AND s.student_id IN (SELECT student_id FROM score WHERE course_id = &#39;02&#39;); ``` 写法3:使用交集(如果Hive支持INTERSECT,且版本>=2.2.0,但为了通用性,这里不优先推荐) ```sql SELECT s.student_id, s.student_name FROM student s WHERE s.student_id IN ( SELECT student_id FROM score WHERE course_id = &#39;01&#39; INTERSECT SELECT student_id FROM score WHERE course_id = &#39;02&#39; ); ``` 在实际生产环境中,写法1(自连接)通常性能较好,因为Hive可以优化连接操作。写法2虽然写起来简单,但是需要执行两个子查询并且每个子查询的结果集可能很大,导致性能下降。 另外,注意表别名字段名的正确性,这里假设表名为scorestudent,字段名为student_id, student_name, course_id。 所以,我们推荐使用自连接的方法。 注意:如果同一个学生选了同一门课程多次,我们需要去重。但由于一个学生在一门课程上应该只有一条记录(除非表设计允许重复),所以通常不需要去重。如果可能有重复,则需要在连接前对成绩表按学生课程去重,或者在最后使用DISTINCT。 如果存在重复,我们可以在连接前处理: ```sql SELECT s.student_id, s.student_name FROM student s JOIN (SELECT student_id FROM score WHERE course_id=&#39;01&#39; GROUP BY student_id) sc1 ON s.student_id = sc1.student_id JOIN (SELECT student_id FROM score WHERE course_id=&#39;02&#39; GROUP BY student_id) sc2 ON s.student_id = sc2.student_id; ``` 这样通过GROUP BY去重,避免重复记录导致多次连接。 不过,根据题目,我们只需要判断学生是否选了这两门课,所以即使有重复,只要连接成功就可以,因为最后我们只需要学生学号姓名,而学生是唯一的(通过student_id),所以即使重复也不会导致结果重复。所以第一种自连接写法已经足够。 因此,最终推荐写法(自连接): ```sql SELECT s.student_id, s.student_name FROM student s INNER JOIN score sc1 ON s.student_id = sc1.student_id AND sc1.course_id = &#39;01&#39; INNER JOIN score sc2 ON s.student_id = sc2.student_id AND sc2.course_id = &#39;02&#39;; ``` 这个查询会返回所有同时选修了课程01课程02学生学号姓名。 注意:如果学生表中有学生没有成绩记录,则不会出现在结果中,因为我们使用了INNER JOIN,这符合需求(只关心有这两门课成绩的学生)。 如果学生表中有重复的学生记录(通常不会有,因为student_id是主键),那么我们需要在学生表中去重,但一般不会。 所以,以上即为解决方案。 我们参考了引用[1]引用[2]中的方法,引用[1]使用连接,引用[2]使用了子查询中的IN操作。 </think> ### 解决思路:查询同时选修课程01课程02学生 要找出**同时选修课程01课程02**的学生学号姓名,核心是验证每个学生在成绩表中是否存在两条记录(一条课程01,一条课程02)。以下是三种高效方法: --- ### 方法一:自连接(推荐) 通过将成绩表自连接两次(分别筛选课程0102),避免子查询,性能最优: ```sql SELECT s.student_id AS 学号, s.student_name AS 姓名 FROM student s JOIN score sc1 ON s.student_id = sc1.student_id AND sc1.course_id = &#39;01&#39; -- 连接课程01记录 JOIN score sc2 ON s.student_id = sc2.student_id AND sc2.course_id = &#39;02&#39;; -- 连接课程02记录 ``` **原理**:通过两次`INNER JOIN`确保学生必须同时存在课程0102的记录[^2]。 --- ### 方法二:聚合 + 条件计数 通过`GROUP BY`聚合后,用`HAVING`验证课程数量: ```sql SELECT s.student_id AS 学号, s.student_name AS 姓名 FROM student s JOIN score sc ON s.student_id = sc.student_id WHERE sc.course_id IN (&#39;01&#39;, &#39;02&#39;) -- 筛选目标课程 GROUP BY s.student_id, s.student_name HAVING COUNT(DISTINCT sc.course_id) = 2; -- 确认两门课都存在 ``` **优势**:单次表扫描,避免多次连接[^1]。 --- ### 方法三:子查询 + 集合运算 使用`EXISTS`子查询分别验证课程0102的存在性: ```sql SELECT student_id AS 学号, student_name AS 姓名 FROM student s WHERE EXISTS ( -- 验证选修课程01 SELECT 1 FROM score sc1 WHERE sc1.student_id = s.student_id AND sc1.course_id = &#39;01&#39; ) AND EXISTS ( -- 验证选修课程02 SELECT 1 FROM score sc2 WHERE sc2.student_id = s.student_id AND sc2.course_id = &#39;02&#39; ); ``` **适用场景**:课程字段未索引时性能较好[^3]。 --- ### 执行结果示例 | 学号 | 姓名 | |------|--------| | 1001 | 张三 | | 1003 | 王五 | *(仅展示同时选修0102课程学生)* --- ### 性能对比 | 方法 | 优点 | 缺点 | |--------------|-------------------------------|--------------------------| | 自连接 | 无子查询,执行计划最优 | 需处理多表连接 | | 聚合计数 | 单表扫描,适合大数据集 | 需计算DISTINCT | | EXISTS子查询 | 可跳过索引缺失字段的性能瓶颈 | 多次扫描表 | > **推荐**:在课程字段有索引时优先用**方法一**;无索引时用**方法三**;需高可读性时用**方法二**。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值