3.4 《数据库系统概论》之数据查询---SELECT(从基础到进阶:单表操作、多表关联与子查询实战)

3.4 《数据库系统概论》之数据查询---SELECT(从基础到进阶:单表操作、多表关联与子查询实战)

1. SELECT查询基础入门

SQL查询就像在图书馆找书一样,SELECT语句就是我们的"图书检索系统"。想象一下,当你走进图书馆,首先要确定想找什么书(选择列),在哪个区域找(从哪张表),以及有什么具体要求(过滤条件)。这就是SELECT查询的基本逻辑。

SELECT语句的标准格式如下:

SELECT [ALL|DISTINCT] <目标列表达式> [别名] FROM <表名或视图名> [别名] [WHERE <条件表达式>] [GROUP BY <列名1> [HAVING <条件表达式>]] [ORDER BY <列名2> [ASC|DESC]]

举个最简单的例子,假设我们有一个学生表Student,包含学号(Sno)、姓名(Sname)、年龄(Sage)等字段:

-- 查询所有学生的姓名和年龄 SELECT Sname, Sage FROM Student; -- 查询计算机系的所有学生信息 SELECT * FROM Student WHERE Sdept = 'CS';

在实际项目中,我经常看到新手容易犯的几个错误:

  1. 忘记指定WHERE条件导致全表扫描
  2. SELECT * 查询不必要的数据列
  3. 忽略DISTINCT导致结果重复

2. 单表查询的进阶技巧

2.1 列选择与计算字段

SELECT不仅可以查询原始列,还能进行各种计算和转换:

-- 计算学生出生年份(假设当前是2023年) SELECT Sname, 2023-Sage AS BirthYear FROM Student; -- 使用函数处理数据 SELECT Sname, UPPER(Sdept) AS Department FROM Student; -- 使用CASE WHEN条件表达式 SELECT Sname, CASE WHEN Sage < 20 THEN '新生' ELSE '老生' END AS StudentType FROM Student;

2.2 条件过滤的艺术

WHERE子句是SQL的过滤系统,常用的条件运算符包括:

  • 比较运算符:=, >, <, >=, <=, <>
  • 范围检查:BETWEEN AND
  • 集合检查:IN
  • 模糊匹配:LIKE
  • 空值判断:IS NULL
-- 查询年龄在20-23岁之间的学生 SELECT * FROM Student WHERE Sage BETWEEN 20 AND 23; -- 查询姓"张"且名字为两个字的学生 SELECT * FROM Student WHERE Sname LIKE '张_'; -- 查询没有成绩记录的选课信息 SELECT * FROM SC WHERE Grade IS NULL;

2.3 排序与聚合函数

ORDER BY和聚合函数是数据分析的利器:

-- 按成绩降序排列 SELECT Sno, Grade FROM SC WHERE Cno='3' ORDER BY Grade DESC; -- 常用聚合函数 SELECT COUNT(*) AS TotalStudents, AVG(Sage) AS AvgAge, MAX(Sage) AS MaxAge FROM Student;

GROUP BY的使用需要注意:

  1. SELECT中的非聚合列必须出现在GROUP BY中
  2. WHERE在分组前过滤,HAVING在分组后过滤
-- 统计各系学生人数 SELECT Sdept, COUNT(*) AS StudentCount FROM Student GROUP BY Sdept; -- 查询选课超过3门的学生 SELECT Sno, COUNT(*) AS CourseCount FROM SC GROUP BY Sno HAVING COUNT(*) > 3;

3. 多表连接查询实战

3.1 连接原理与类型

连接查询就像拼图游戏,我们需要找到表之间的关联字段。常见的连接类型包括:

  1. 内连接(INNER JOIN):只返回匹配的行
  2. 左外连接(LEFT JOIN):返回左表所有行,右表不匹配则为NULL
  3. 右外连接(RIGHT JOIN):返回右表所有行,左表不匹配则为NULL
  4. 全外连接(FULL JOIN):返回左右表所有行
-- 等值连接:查询学生及其选课信息 SELECT Student.Sno, Sname, Cno, Grade FROM Student, SC WHERE Student.Sno = SC.Sno; -- 使用JOIN语法更清晰 SELECT Student.Sno, Sname, Cno, Grade FROM Student INNER JOIN SC ON Student.Sno = SC.Sno;

3.2 连接性能优化

多表连接时,数据库通常采用以下算法:

  1. 嵌套循环连接(Nested Loop):适合小表驱动大表
  2. 哈希连接(Hash Join):适合中等规模数据
  3. 排序合并连接(Sort-Merge):适合已排序数据
-- 复合条件连接:查询选修2号课程且成绩>88的学生 SELECT Student.Sno, Sname FROM Student JOIN SC ON Student.Sno = SC.Sno WHERE SC.Cno='2' AND SC.Grade > 88;

在实际项目中,连接查询最容易出现的性能问题就是笛卡尔积(忘记写连接条件),我曾见过一个查询因为漏写连接条件,导致两个百万级表的笛卡尔积,直接把数据库卡死。

4. 子查询与嵌套查询

4.1 子查询类型

子查询就像SQL中的"俄罗斯套娃",主要分为:

  1. 不相关子查询:子查询可独立执行
  2. 相关子查询:子查询依赖外部查询
-- 不相关子查询:查询与"刘晨"同系的学生 SELECT Sno, Sname, Sdept FROM Student WHERE Sdept IN (SELECT Sdept FROM Student WHERE Sname='刘晨'); -- 相关子查询:查询超过自己平均成绩的课程 SELECT Sno, Cno FROM SC x WHERE Grade >= (SELECT AVG(Grade) FROM SC y WHERE y.Sno=x.Sno);

4.2 EXISTS与NOT EXISTS

EXISTS用于检查子查询是否返回结果,不关心具体数据:

-- 查询选修了1号课程的学生 SELECT Sname FROM Student WHERE EXISTS (SELECT * FROM SC WHERE Sno=Student.Sno AND Cno='1');

4.3 集合查询

集合操作包括UNION(并)、INTERSECT(交)、EXCEPT(差):

-- 查询计算机系或年龄<19的学生 SELECT * FROM Student WHERE Sdept='CS' UNION SELECT * FROM Student WHERE Sage<19;

5. 实战案例与性能优化

5.1 典型查询模式

-- 查询每个学生的总学分 SELECT Student.Sno, Sname, SUM(Ccredit) AS TotalCredit FROM Student JOIN SC ON Student.Sno = SC.Sno JOIN Course ON SC.Cno = Course.Cno WHERE Grade >= 60 -- 及格才算学分 GROUP BY Student.Sno, Sname; -- 查询各科成绩前三名 SELECT Cno, Sno, Grade FROM SC x WHERE (SELECT COUNT(*) FROM SC y WHERE y.Cno=x.Cno AND y.Grade>x.Grade) < 3 ORDER BY Cno, Grade DESC;

5.2 性能优化建议

  1. 为常用连接字段创建索引
  2. 避免在WHERE条件中对字段进行函数操作
  3. 只查询需要的列,避免SELECT *
  4. 大数据量表连接时,先过滤再连接
  5. 复杂查询考虑使用临时表或视图
-- 优化示例:先过滤再连接 SELECT S.Sno, Sname, Cname, Grade FROM (SELECT * FROM Student WHERE Sdept='CS') S JOIN SC ON S.Sno = SC.Sno JOIN Course ON SC.Cno = Course.Cno;

在实际项目中,我曾优化过一个需要20秒执行的查询,通过添加适当的索引和重写查询逻辑,最终将执行时间降到了0.5秒。关键是要理解数据库的执行计划,知道它如何访问和处理数据。