📑 本页目录(点开跳转)
00 · 怎么用这份教程
⏱ 30 分钟 | ⭐ 先划清一件事:这里不教你运维数据库,只教你把问题写成查询
🎯 一句话
这套教程只做一件事:把「我想知道 X」翻译成一条跑得对、跑得完、你敢把结果发出去的 SQL。 建表、建索引、备份、扩容、连接池 —— 站内别的章节已经在讲,这里一句不重复。
⏱ 先说清楚这套的章比站内其他板块长(普遍 50–120 分钟,站内其他板块的中位是 30)—— 因为每个知识点都带实跑代码和真实报错,压不下去。设计上就没打算让你一次读完: 超过 55 分钟的章在中间有 🛑 休息点,最长的几章有两个,停在那里下次接着读就行。 徽章是按 170 字/分钟实测校准的,宁可吓到你,也不骗你。
🚦 一、先划清一件事
「学 SQL」这四个字底下其实塞了两件很不一样的事:
| 关心什么 | 谁在干 | |
|---|---|---|
| 数据库怎么运起来 | 表怎么设计、索引怎么建、事务隔离级别、迁移、备份、连接池、扩容 | 后端 / DBA / 平台 |
| ⭐ 怎么把一个问题写成查询 | 这个数怎么算出来、这个连接会不会把账放大、这个 WHERE 到底挡掉了谁 |
所有需要看数的人 |
这套教程只做右边那件事。 判据很简单:如果一个问题的答案是「改表结构」或者「加台机器」,它不在这里; 如果答案是「把这条查询换个写法」,它在这里。
⭐ 为什么值得单独学:左边那件事你一年做不了几次,右边那件事你每天做十次。 而右边最贵的错误有一个共同形状 —— 不报错。查询跑完了,返回了一张漂亮的表,数字是错的, 没有任何一行日志告诉你。这套教程有一多半篇幅是在拆这类「不报错的错」。
🧯 站内已经有人 own 的四块,本教程一个字不重讲
⚠️ 这不是客气话,是分工。你要那几件事的时候,请直接去那边,别在这里找:
| 那件事 | 归谁 | 那边给了什么 |
|---|---|---|
| 索引怎么建 | AI 全栈 · 06 关系数据库 | 建索引的判据、复合索引的列序、覆盖索引,以及一组 3.802 秒 → 0.0019 秒的实测 |
COUNT(*) 在大表上多贵 · 游标分页怎么用 · SQL 注入 |
AI 全栈 · 06b 列表接口与分页 | 分页接口的完整形状;⭐ 注入那一节整段让给它,本教程不重复 |
| 训练侧和线上侧的 SQL 为什么算出两个数 | 模型上线之后 · 04 训练推理一致性 | 训推不一致的七种形态 |
| 数据本身脏在哪 | 数据这一关 · 04 脏数据的十种形态 | 数据进模型之前的那一半 |
⭐ 本教程和它们的接缝是双向的:09 章会用到 06 章建好的索引(但只讲写法怎么把它废掉),
05 章会解释 06b 那个 (updated_at, id) < (?, ?) 到底是什么(那边 own 怎么用),
03 章会把《AI 全栈》15 章那句「同一张表的 GROUP BY 看板」真写出来(按 feature、按天×模型;每用户 Top-N 那类要等 07 章的窗口函数)。
📋 二、章节地图
12 页,正文合计约 17 小时(章内有 🛑 断点,设计上就是分次读)。⭐ 不必按顺序读完,下一节有四条路线。
| # | 章 | ⏱ | 一句话 |
|---|---|---|---|
| 00 | 怎么用这份教程 | 30 | 你在这里 |
| 01 | 一条查询是怎么跑的 | 54 | SELECT 写在最前面,却几乎是最后才执行 —— 搞懂这个顺序,一半的报错自己就解释了 |
| 02 | JOIN 的几种语义 | 78 | ⭐ 实测:JOIN 之后 SUM 从 150 变成 250,没报错,账就这么错了 |
| 03 | 聚合与分组 | 146 | COUNT(*) 是 3,COUNT(city) 是 2 —— 差的那一行去哪了 |
| 04 | NULL 与三值逻辑 | 108 | ⭐ 实测:NOT IN ('BJ', NULL) 返回空集,一行都没有,且不报错 |
| 05 | 子查询的三种形状 | 100 | 标量 / 行 / 表;IN、EXISTS、JOIN 到底该选哪个 |
| 06 | CTE 与递归查询 | 80 | ⭐ 血缘图就是一张边表,「全部上游」是一条递归查询 |
| 07 | 窗口函数 | 70 | ⭐ GROUP BY 把十行压成一行,窗口函数让十行还是十行、但每行都知道自己排第几 |
| 08 | 窗口帧 | 106 | ⭐ 「最近 3 行」和「最近 3 天」,数据有缺口时不是一回事 |
| 09 | 计划里的连接与索引失效 | 104 | ⚠️ 索引在、计划里也写着索引名,它照样可能是全表扫 |
| 10 | 拿 SQL 审问一份数据 | 96 | ⭐「每一个 WHERE 都要有人负责解释」,这句话怎么真的执行下去 |
| 附录A | 速查与三库差异对照 | 54 | 一页纸:语法速查 + SQLite / Postgres / MySQL 差在哪 |
⚠️ 上表的分钟数是个参考;以各章页首那个 ⏱ 徽章为准,那个是按正文长度算出来的。
⭐ 01–05 是承重墙,后面每一章都踩在它们上面。06 之后的每一章都是独立能力,可以按需要挑。
🧭 三、四条读法路线
⭐ 别从头读到尾,除非你就是想系统学一遍。按你手上要做的事挑:
| 你要做的事 | 读哪几章 | 大概多久 |
|---|---|---|
| 看懂别人写的 SQL(review、接手祖传报表) | 01 → 02 → 03 → 04 → 05 → 06 | 约 9.4 小时(有 🛑 断点,分次读) |
| 自己查数、自己审一份数据 | 01 → 03 → 04 → 07 → 10 | 约 7.9 小时 |
| 后端写接口 / 查询慢了要救火 | 01 → 02 → 04 → 05 → 09 | 约 7.4 小时 |
| 做数据、做特征、做看板 | 03 → 06 → 07 → 08 → 10 | 约 8.3 小时 |
⭐ 不管走哪条,01 和 04 都建议不要跳。 01 是全部语法的骨架,04 是全部「不报错的错」的源头 —— 后面每一章至少会回扣它们一次。
⚠️ 如果你从来没写过 SQL:先花 20 分钟在任何一个在线练习站上把 SELECT / WHERE / ORDER BY 敲熟,
再回来从 01 开始。这套教程默认你见过 SQL 长什么样,不默认你会写。
🧰 四、怎么把每段代码跑起来
⭐ 本教程每一段代码都可以复制进一个空文件直接跑,不需要装数据库。
因为 Python 自带 sqlite3。整个教程用的都是内存库:进程结束就没了,跑一百遍也不留垃圾。
import sqlite3
db = sqlite3.connect(":memory:") # ⭐ 不装任何东西,库就在内存里
db.execute("CREATE TABLE calls(user_id TEXT, feature TEXT, cents REAL)")
db.executemany("INSERT INTO calls VALUES(?,?,?)",
[("u1", "chat", 0.056), ("u1", "summary", 2.64), ("u2", "summary", 10.2)])
for row in db.execute("SELECT feature, SUM(cents) FROM calls GROUP BY feature"):
print(row)
print("sqlite 版本:", sqlite3.sqlite_version)
实跑输出:
('chat', 0.056)
('summary', 12.84)
sqlite 版本: 3.50.4
本教程所有实测都跑在 Windows 11 · Python 3.13.14 · SQLite 3.50.4(Python 自带那个)上。
⭐ 为什么选 SQLite 而不是 Postgres: 装一个 Postgres 是运维,而运维不是这套教程的题目。 SQL 的语义 —— 执行顺序、连接、三值逻辑、聚合、窗口 —— 三个库高度一致, 分歧集中在函数名和少数语法糖上。真有分歧的地方,本教程会当场标出来。
⚠️ 两个标记,看到就知道该怎么办:
| 标记 | 含义 |
|---|---|
| 实跑输出 | 下面那段文本是从终端抄回来的,不是我写的。你在同版本上跑应该一模一样 |
| 🗓️ 未实跑 —— sqlite 无此语法 | 这是 Postgres / MySQL 特有的写法,本机跑不了,只给形状不给输出 |
⚠️ 耗时类的数字(毫秒、秒)会随机器变。 凡是出现耗时,正文一定会写明数据规模, 并且要看的是量级差异不是绝对值 —— 你的绝对数值会不同,但量级差异是结构性的。
三个库的完整差异对照在 附录A,遇到分歧再翻。
📐 五、这套教程的三条写作约定
① 每段代码自带建表。
不会出现「接着上一段」。你从任何一节的任何一个代码块开始复制,都能跑。
代价是同一张 calls 表可能建了七八遍 —— 这是故意的。
② 输出是抄回来的,不是想出来的。
包括那些「看起来很怪」的输出(NOT IN 返回空集、AVG 跳过 NULL、COUNT(DISTINCT) 不数 NULL)。
⭐ 它们怪,正是这套教程存在的理由。
③ 每一章的坑都有一个「不报错」标签。 本教程标了 ⚠️ 的地方,绝大多数都不会给你一个报错。报错是幸运的,报错说明你当场就知道了。 真正贵的是那些跑得好好的、结果错了的。
⭐ 一句话记住整套教程的目标: 让你在按下回车之前,就知道这条查询会返回几行、每行代表什么、以及哪些行被悄悄扔掉了。
🔗 这一章连到哪里
| 相关的地方 | 为什么 |
|---|---|
| AI 全栈 · 06 关系数据库 | ⭐ 索引怎么建、表怎么设计、迁移怎么做全在那边。本教程 09 章会用它建好的索引,但只讲写法怎么把索引废掉 |
| AI 全栈 · 06b 列表接口与分页 | 那边 own 游标分页怎么用、COUNT(*) 的成本、SQL 注入;本教程 05 章只回答那个复合游标是什么 |
| AI 全栈 · 12 限流配额与成本护栏 | 03 章的看板用的也是那一章第六节那张 calls 计费表的思路 —— ⚠️ 字段做了教学裁剪(保留 user_id/feature/model/cents/ms,加了 ok/dt,省去 trace_id/token 数),口径以那边为准 |
| 数据这一关 · 04 脏数据的十种形态 | 数据进模型之前的那一半。本教程假定数据已经在库里,坏在哪是它的题目 |
| 模型上线之后 · 04 训练推理一致性 | 训练侧和线上侧算出两个数的七种形态;本教程 08 章会碰到其中一条的 SQL 侧 |
✅ 检查点
- 这套教程不讲哪四类事?各归站内哪一章?
- 判断「一个问题在不在本教程范围内」的那条判据是什么?
- 为什么说本教程要拆的错误有一个共同形状?那个形状是什么?
- 12 章里哪几章被称作「承重墙」?为什么建议不跳 01 和 04?
- 为什么用 SQLite 而不是 Postgres?这个选择的代价是什么、怎么兜底?
- 看到 🗓️ 这个标记意味着什么?看到「实跑输出」四个字意味着什么?
- 为什么每段代码都要自带建表,哪怕同一张表要建七八遍?
- 遇到耗时类的数字(毫秒、秒)该怎么看?
👀 答案
- 索引怎么建 → 《AI 全栈》06;
COUNT(*)成本 / 游标分页怎么用 / SQL 注入 → 《AI 全栈》06b; 训推不一致的七种形态 → 《模型上线之后》04;数据本身脏在哪 → 《数据这一关》04。 - 看答案的形状:答案是「改表结构」或「加台机器」→ 不在这里;答案是「把这条查询换个写法」→ 在这里。
- 不报错。查询跑完了、返回了一张漂亮的表、数字是错的,没有任何一行日志告诉你。 报错是幸运的(当场就知道了),贵的是跑得好好的那种。
- 01–05 是承重墙。01 是全部语法的骨架(执行顺序),04 是全部「不报错的错」的源头(三值逻辑), 后面每章至少回扣它们一次。
- 因为装一个 Postgres 是运维,而运维不是本教程的题目;Python 自带
sqlite3,:memory:零安装。 代价是函数名和少数语法糖有分歧(语义 —— 执行顺序 / 连接 / 三值逻辑 / 聚合 / 窗口 —— 三库高度一致), 兜底办法是分歧处当场标 🗓️,完整对照在附录A。 - 🗓️ = 这是 Postgres / MySQL 特有语法,本机跑不了,只给形状不给输出; 「实跑输出」= 下面那段文本是从终端抄回来的,同版本(Python 3.13.14 / SQLite 3.50.4)应该一模一样。
- 为了从任何一节的任何一个代码块开始复制都能跑,不会出现「接着上一段」。重复建表是故意的。
- 看量级差异,不看绝对值 —— 耗时随机器变。正文出现耗时时一定会写明数据规模。
🛑 可以停在这里
⚡ 走神救援
「学 SQL」底下塞了两件事:数据库怎么运起来(表设计、索引、事务、备份、扩容——一年做不了几次)和怎么把一个问题写成查询(每天做十次)。⭐ 这套只做后者,判据是看答案的形状:答案是「改表结构」或「加机器」就不在这里,答案是「把这条查询换个写法」就在这里。
⭐⭐ 它值得单独学,是因为这一侧最贵的错误有个共同形状——不报错:查询跑完了、表也漂亮、数字是错的,没有一行日志提醒你。
站内已经有人 own 的四块一个字不重讲:索引怎么建、
COUNT(*)的成本与游标分页与注入、训推不一致的七种形态、数据本身脏在哪。⭐ 而接缝是双向的:这边讲「写法怎么把已经建好的索引废掉」,那边讲「索引怎么建」;这边回答那个复合游标条件是什么,那边 own 怎么用。⭐ 01–05 是承重墙,06 之后每章是独立能力可以挑着读。四条路线按目的分(看懂别人的 SQL / 自己查数审数据 / 后端写接口救火 / 做数据看板),⭐ 不管走哪条,01 和 04 都别跳。
跑代码不用装数据库:Python 自带的
sqlite3三行起步。⭐ 选它是因为装 Postgres 本身就是运维,而 SQL 的语义(执行顺序、连接、三值逻辑、聚合、窗口)三个库高度一致,分歧集中在函数名和语法糖上——分歧处当场标 🗓️「未实跑,本机无此语法」。三条写作约定:每段代码自带建表(能从任何一块开始复制,⭐ 重复建表是故意的)、输出是从终端抄回来的(包括那些看起来很怪的),⚠️ 耗时类数字随机器变,看量级不看绝对值。
下一节 👉 01-一条查询是怎么跑的.md