讲解
到这里,查询、聚合、连接、写操作、建表索引都已经学了一遍,最后一章把分散在全书的经验收敛成一份清单。可读性方面:关键字大写(SELECT、FROM、WHERE),子句分行书写,长查询按子句缩进,表和列用有意义的名字并坚持使用别名——SQL 是写给下一个人看的,包括三个月后的自己。
正确性方面:凡是要顺序就写 ORDER BY 并加唯一决胜列;更新删除前先用同条件 SELECT 验证;NULL 判断只用 IS NULL;NOT IN 的候选集先排 NULL;聚合查询遵守「普通列必须分组或聚合」的铁律。安全方面只有一条铁律但性命攸关:永远不要把用户输入直接拼进 SQL 字符串——' OR '1'='1 这样的输入就是 SQL 注入。所有程序语言的数据库驱动都支持参数化查询(占位符),把值作为参数传入而不是文本拼接。
性能方面:只查需要的列,别在生产代码里 SELECT *;WHERE、JOIN ON、ORDER BY 的高频列考虑索引;WHERE 里别对索引列套函数;拿不准就用 EXPLAIN QUERY PLAN(SQLite)或 EXPLAIN(MySQL/PostgreSQL)看数据库到底怎么执行你的查询——全表扫描还是走索引,一目了然。学习路径方面:把这 21 章的示例在自己的 SQLite 里全部跑一遍,然后找一份公开数据集(或自己造几百行数据)给自己出题,能独立写出「每个分类下最贵的两门课」这类查询,就算真正入门了。
示例
一条贯彻可读性约定的完整查询:关键字大写、子句分行、表用别名、条件分行缩进:
SELECT
s.name AS student_name,
COUNT(o.id) AS order_count,
SUM(o.amount) AS total_spent
FROM students AS s
LEFT JOIN orders AS o
ON s.id = o.student_id
AND o.status = 'paid'
GROUP BY s.id, s.name
HAVING COUNT(o.id) >= 1
ORDER BY total_spent DESC, s.id;
用 EXPLAIN QUERY PLAN 检查查询计划:没有索引时按 name 过滤要全表扫描(SCAN),加索引后变成 SEARCH:
EXPLAIN QUERY PLAN SELECT * FROM students WHERE name = '李明';
CREATE INDEX idx_students_name ON students (name);
EXPLAIN QUERY PLAN SELECT * FROM students WHERE name = '李明';
本教程三张表的回顾查询:用一条三表连接看看你已经能读懂多复杂的 SQL:
SELECT
c.category AS category,
COUNT(o.id) AS sold,
SUM(o.amount) AS revenue
FROM courses AS c
LEFT JOIN orders AS o ON c.id = o.course_id AND o.status = 'paid'
GROUP BY c.category
ORDER BY revenue DESC;
常见坑
- 把用户输入拼进 SQL 字符串:这是 SQL 注入,Web 安全的头号经典漏洞。任何语言、任何框架,都用参数化查询传值,没有例外。
- 只在测试小表上验证就上线:100 行数据上毫秒级的查询,在一百万行上可能全表扫描几分钟。上线前用接近真实规模的数据测一遍,配合 EXPLAIN 确认计划。
- 重复造报表查询的轮子:同一统计逻辑散落在十几处代码里,口径一旦调整处处要改。把关键统计收敛成数据库视图(CREATE VIEW)或统一的查询函数。
- 学了不练:SQL 是肌肉记忆型技能,看懂和会写之间隔着一百次亲手执行。把本教程的示例全部跑通只是起点,给自己出题做分析才是进阶。
小结
可读性靠约定,正确性靠习惯,安全性靠参数化,性能靠索引与 EXPLAIN。22 章到此结束——SELECT 的世界已经向你打开,剩下的就是去真实数据里练习。