第3章:SQL 基础(Introduction to SQL)
一、导读
1.1 本章学习目标
SQL(Structured Query Language)是关系数据库的标准查询语言。本章讲解 SQL 的基本语法:数据查询、数据定义、数据操纵。
本章的核心学习目标包括:
- 掌握 SELECT-FROM-WHERE 基本查询结构
- 理解 JOIN 操作:内连接、外连接
- 掌握子查询:嵌套查询、相关子查询
- 理解聚合函数:COUNT、SUM、AVG、MAX、MIN
- 掌握 GROUP BY 和 HAVING 子句
- 理解视图的定义和用途
1.2 为什么 SQL 如此重要
SQL 是数据库领域最广泛使用的语言。无论是数据分析师、后端工程师、还是数据科学家,都需要掌握 SQL。理解 SQL 的工作原理,对于编写高效查询、优化数据库性能至关重要。
SQL 自 1986 年成为 ANSI 标准以来,经历了多次修订(SQL:92、SQL:99、SQL:2003 等),每次修订都增加了新的功能,如窗口函数、JSON 支持等。掌握 SQL 不仅是数据库开发的基础,也是数据分析、数据工程等领域的核心技能。
1.3 核心问题引导
在学习本章时,请思考以下问题:
SQL 查询的逻辑执行顺序是什么?为什么理解执行顺序很重要?
JOIN 操作的本质是什么?不同类型的 JOIN 有什么区别?
子查询和 JOIN 可以互相替代吗?性能上有何差异?
NULL 值在 SQL 中的语义是什么?如何正确处理 NULL?
二、核心概念详解
2.1 SELECT-FROM-WHERE 基本查询
SQL 查询的基本结构是 SELECT-FROM-WHERE:
SELECT 列名1, 列名2, ...
FROM 表名
WHERE 条件;2.1.1 简单查询
SELECT * FROM Student;
SELECT name, gpa
FROM Student
WHERE dept = '计算机';
SELECT name
FROM Student
WHERE gpa > 3.5;2.1.2 条件组合
SELECT * FROM Student
WHERE dept = '计算机' AND gpa > 3.5;
SELECT * FROM Student
WHERE dept = '计算机' OR dept = '数学';
SELECT * FROM Student
WHERE dept IN ('计算机', '数学', '物理');
SELECT * FROM Student
WHERE gpa BETWEEN 3.5 AND 4.0;
SELECT * FROM Student
WHERE name LIKE '张%';2.1.3 去重与排序
SELECT DISTINCT dept FROM Student;
SELECT name, gpa
FROM Student
ORDER BY gpa DESC;
SELECT name, gpa
FROM Student
ORDER BY dept ASC, gpa DESC;DISTINCT 关键字用于消除重复行,ORDER BY 子句用于对结果排序。可以指定升序(ASC)或降序(DESC),默认是升序。
2.2 JOIN 操作
JOIN 用于从多个表中查询数据。
2.2.1 内连接(INNER JOIN)
只返回两个表中匹配的行。
SELECT S.name, C.title
FROM Student S
INNER JOIN Takes T ON S.id = T.student_id
INNER JOIN Course C ON T.course_id = C.id;2.2.2 左外连接(LEFT OUTER JOIN)
返回左表的所有行,右表不匹配的行用 NULL 填充。
SELECT S.name, C.title
FROM Student S
LEFT OUTER JOIN Takes T ON S.id = T.student_id
LEFT OUTER JOIN Course C ON T.course_id = C.id;2.2.3 自然连接(NATURAL JOIN)
自动根据同名列进行连接。
SELECT *
FROM Student
NATURAL JOIN Takes;2.2.4 交叉连接(CROSS JOIN)
返回两个表的笛卡尔积。
SELECT S.name, C.title
FROM Student S
CROSS JOIN Course C;交叉连接的结果行数等于两个表行数的乘积,通常用于生成所有可能的组合。
2.3 子查询
子查询是嵌套在另一个查询中的查询。
2.3.1 嵌套子查询
SELECT name
FROM Student
WHERE id IN (
SELECT student_id
FROM Takes
WHERE course_id = 'CS101'
);2.3.2 相关子查询
子查询引用了外层查询的列。
SELECT S1.name, S1.dept, S1.gpa
FROM Student S1
WHERE S1.gpa = (
SELECT MAX(S2.gpa)
FROM Student S2
WHERE S2.dept = S1.dept
);2.3.3 EXISTS 操作符
SELECT name
FROM Student S
WHERE EXISTS (
SELECT *
FROM Takes T
WHERE T.student_id = S.id
);2.3.4 标量子查询
标量子查询返回单个值,可以用在 SELECT 列表中。
SELECT name,
(SELECT AVG(gpa) FROM Student) AS avg_gpa
FROM Student;2.4 聚合函数
聚合函数对一组值执行计算并返回单个值。
SELECT COUNT(*) FROM Student;
SELECT AVG(gpa) FROM Student;
SELECT SUM(gpa) FROM Student;
SELECT MAX(gpa), MIN(gpa) FROM Student;
SELECT dept, COUNT(*) AS student_count
FROM Student
GROUP BY dept;需要注意的是,聚合函数会忽略 NULL 值(COUNT(*) 除外)。如果某列全部为 NULL,AVG 的结果是 NULL 而不是 0。
2.5 GROUP BY 和 HAVING
GROUP BY 将结果分组,HAVING 过滤分组。
SELECT dept, COUNT(*) AS student_count
FROM Student
GROUP BY dept
HAVING COUNT(*) > 10;
SELECT dept, AVG(gpa) AS avg_gpa
FROM Student
GROUP BY dept
HAVING AVG(gpa) > 3.5;GROUP BY 子句中可以使用多个列进行多级分组。HAVING 子句与 WHERE 子句的区别在于:WHERE 在分组前过滤行,HAVING 在分组后过滤组。
2.6 视图
视图是虚拟表,不存储数据,只存储查询定义。
CREATE VIEW CS_Student AS
SELECT *
FROM Student
WHERE dept = '计算机';
SELECT * FROM CS_Student WHERE gpa > 3.5;
DROP VIEW CS_Student;视图的优点:
- 简化复杂查询
- 提供安全性(隐藏敏感数据)
- 提供逻辑数据独立性
三、深入分析
3.1 SQL 执行顺序
SQL 的逻辑执行顺序是:
FROM(包括 JOIN)
WHERE
GROUP BY
HAVING
SELECT
ORDER BY
LIMIT
理解执行顺序对于编写正确和高效的 SQL 至关重要。例如,WHERE 子句中不能使用 SELECT 中定义的别名,因为 WHERE 在 SELECT 之前执行。
3.2 NULL 值处理
NULL 表示未知或缺失的值。NULL 与任何值的比较结果都是 UNKNOWN。
SELECT * FROM Student WHERE gpa IS NULL;
SELECT * FROM Student WHERE gpa IS NOT NULL;
SELECT COALESCE(gpa, 0) FROM Student;
SELECT NULLIF(dept, '') FROM Student;三值逻辑(TRUE、FALSE、UNKNOWN)影响 WHERE 和 HAVING 的行为:WHERE 只保留结果为 TRUE 的行。
3.3 字符串与日期操作
SELECT CONCAT(name, ' - ', dept) FROM Student;
SELECT LENGTH(name) FROM Student;
SELECT UPPER(name), LOWER(name) FROM Student;
SELECT SUBSTRING(name, 1, 2) FROM Student;
SELECT CURRENT_DATE, CURRENT_TIME;
SELECT DATE_FORMAT(enroll_date, '%Y-%m') FROM Student;四、实践案例
4.1 案例一:电商订单分析查询
SELECT C.name,
COUNT(O.id) AS order_count,
SUM(O.total_amount) AS total_spent,
AVG(O.total_amount) AS avg_order
FROM Customer C
INNER JOIN Orders O ON C.id = O.customer_id
WHERE O.order_date >= '2024-01-01'
GROUP BY C.id, C.name
HAVING SUM(O.total_amount) > 10000
ORDER BY total_spent DESC
LIMIT 10;这个查询综合运用了 JOIN、WHERE、GROUP BY、HAVING、ORDER BY 和 LIMIT,是典型的业务分析场景。
4.2 案例二:使用子查询找出每个部门薪资最高的员工
SELECT E.name, E.salary, E.dept_id
FROM Employee E
WHERE E.salary = (
SELECT MAX(E2.salary)
FROM Employee E2
WHERE E2.dept_id = E.dept_id
);这个相关子查询对外层查询的每一行都执行一次内层查询,当数据量大时性能较差。可以改写为 JOIN 形式以提高效率。
4.3 案例三:使用 EXPLAIN 分析查询
EXPLAIN SELECT * FROM Student WHERE dept = '计算机';
EXPLAIN SELECT * FROM Student WHERE id = 1;通过 EXPLAIN 可以查看查询的执行计划,包括是否使用了索引、扫描了多少行等信息,是 SQL 优化的重要工具。
五、常见误区
5.1 误区一:"SELECT * 总是最好的"
SELECT * 会查询所有列,可能导致不必要的 I/O 和网络传输开销。应该只查询需要的列,这也能让数据库优化器选择更优的执行计划(如使用覆盖索引)。
5.2 误区二:"子查询总是比 JOIN 慢"
现代数据库优化器可以将子查询转换为 JOIN,性能差异不大。在某些场景下(如 EXISTS 子查询),子查询可能比等价的 JOIN 更高效,因为可以提前终止搜索。
5.3 误区三:"HAVING 和 WHERE 可以互换"
HAVING 用于过滤分组后的结果,可以使用聚合函数;WHERE 用于过滤分组前的行,不能使用聚合函数。两者在查询执行的不同阶段生效。
5.4 难点:理解相关子查询
相关子查询的执行效率较低,因为外层查询的每一行都要执行一次子查询。可以通过改写为 JOIN 或使用窗口函数来优化。
5.5 难点:理解 GROUP BY 的语义
GROUP BY 后的 SELECT 只能包含分组列和聚合函数。如果 SELECT 中包含非分组列且未被聚合,在严格模式下的数据库(如 MySQL 的 ONLY_FULL_GROUP_BY 模式)会报错。
六、本章小结
第3章讲解了 SQL 的基础知识:
SELECT-FROM-WHERE 是 SQL 查询的基本结构
JOIN 用于从多个表查询数据
子查询 可以嵌套在另一个查询中
聚合函数 对一组值执行计算
GROUP BY 和 HAVING 用于分组和过滤
视图 是虚拟表,简化复杂查询
后续章节将深入讲解 SQL 的高级特性。
七、思考题
解释 SQL 的逻辑执行顺序,并说明为什么 WHERE 子句中不能使用 SELECT 中定义的别名。
比较 INNER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN 和 FULL OUTER JOIN 的区别,各举一个适用场景。
什么情况下子查询比 JOIN 更合适?请举例说明。
NULL 值在 SQL 中的语义是什么?为什么 NULL = NULL 的结果不是 TRUE?
编写一个 SQL 查询,找出选修了所有课程的学生(使用除法操作或 NOT EXISTS 实现)。
视图有哪些优点和局限性?视图可以被更新吗?在什么条件下可以更新?
解释 GROUP BY 和 DISTINCT 的区别。在什么情况下它们的结果相同?