码上问学问代码之理,学编程之道

第 2 章 · 数据查询基础

数据存在数据库里,如果拿不出来、看不明白,它就只是一堆躺着的字节。这一章教你用 SELECT 把数据「叫醒」——取哪几列、怎么去重、按什么条件筛、怎么排序、怎么只取前几条。学完这几句,你就能自己从「古典文学网」的库里翻出想要的任何一篇作品。

为什么先学「查」

先讲为什么你去图书馆找一本书,不会把整座馆的书都搬回家一本本翻——你会先看索书号,直接走到那一排。数据库也一样:库里可能躺着几百万行数据,查询就是「精确地只取你要的那一部分」。而且在真实项目里,写代码的时间一大半都花在「怎么把数据查出来」上;增删改反而少数。
所以这门课的第一件事,不是建表,是学会把数据问出来。

SELECT:告诉数据库要哪几列

最基本的取数语句
一条 SELECT 的执行顺序:像一条流水线,从左到右逐站加工FROM从哪张表取articleWHERE先筛行dynasty = '唐'SELECT再取列title, authorORDER BY排序views DESCLIMIT截取几条LIMIT 6为什么顺序重要:WHERE 里不能用 SELECT 的别名——因为别名那一站还没到MySQL 逻辑执行顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMITLIMIT 的另一个用途:分页(列表页第 n 页)LIMIT 每页条数 OFFSET 跳过条数第 2 页 · 每页 6 条 → LIMIT 6 OFFSET 6OFFSET = (页码 − 1) × 每页条数
动手做「古典文学网」的作品表叫 article。先取出每篇作品的编号、标题、作者、朝代这四列看看。
查询.sqlsql可运行
SELECT id, title, author, dynasty FROM article;
查询结果SQL 执行结果
运行结果: id | title | author | dynasty 1 | 静夜思 | 李白 | 唐 2 | 春望 | 杜甫 | 唐 3 | 念奴娇·赤壁怀古 | 苏轼 | 宋 4 | 声声慢 | 李清照 | 宋 5 | 天净沙·秋思 | 马致远 | 元 6 | 陋室铭 | 刘禹锡 | 唐 -- 共 6 行
讲透
  • SELECT 后面接列名:想要哪几列就写哪几列,用逗号分隔;顺序由你定,不必和表里一样。
  • FROM 后面接表名:article 就是作品表。
  • * 是「所有列」的简写:SELECT * FROM article; 能一次取出全部 14 列。它很适合「先看看这张表里有什么」,但正式查询里不建议用——列多了既慢又难读,还容易把敏感字段(比如密码)也带出来。
  • 语句末尾的分号表示「这句话说完了」,可以换行写,风格更清晰。
避坑列名写错(比如把 dynasty 写成 dynastry),MySQL 会直接报 Unknown column 'xxx' in 'field list'。遇到这个报错,第一件事就是对照表结构,检查列名拼写。

别名 AS:给结果改个好名字

让输出给人看
动手做英文列名对写代码友好,给用户看却不够直观。用 AS 给列取一个中文别名,结果的表头就变成了中文。
查询.sqlsql可运行
SELECT title AS 作品, author AS 作者, views AS 浏览量
FROM article;
查询结果SQL 执行结果
运行结果: 作品 | 作者 | 浏览量 静夜思 | 李白 | 328 春望 | 杜甫 | 256 念奴娇·赤壁怀古 | 苏轼 | 412 声声慢 | 李清照 | 298 天净沙·秋思 | 马致远 | 187 陋室铭 | 刘禹锡 | 203 -- 共 6 行
讲透
  • AS 只影响「显示出来的列名」,不改表里的真实字段名。所以别名取什么都行,原字段照旧。
  • 别名里含空格或特殊字符时要用引号包起来(如 AS `阅读量 (次)`),一般中文别名不需要。
  • 凡是有计算或聚合的地方,别名几乎是必需的——下一章做统计时你会大量用到它。

DISTINCT:去掉重复

先看看库里都有什么
先讲为什么「我们库里都收录了哪些朝代的作品?」——如果你直接查 dynasty 这一列,每条作品都会带出一个朝代,于是「唐」会重复出现。你要的是「有哪些」,不是「每条是什么」,这时就该去重。
生活里也一样:点名时要的是「有哪些人到了」,而不是把每个名字重复念一遍。
动手做先看看不去重是什么样,再加上 DISTINCT 对比。
查询.sqlsql可运行
-- 不去重:每篇作品都带出一个朝代,「唐」出现了三次
SELECT dynasty FROM article;
查询结果SQL 执行结果
运行结果: dynasty 唐 唐 宋 宋 元 唐 -- 共 6 行
查询.sqlsql可运行
-- 去重:同一朝代只留一条
SELECT DISTINCT dynasty FROM article;
查询结果SQL 执行结果
运行结果: dynasty 唐 宋 元 -- 共 3 行
讲透
  • DISTINCT 作用于「整行列组合」:SELECT DISTINCT dynasty, category_id 去重的是「朝代 + 分类」这个组合,不是单独某一列。想只按一列去重,就只写那一列。
  • 结果的顺序是不保证的:去重后的 3 行谁先谁后,MySQL 不承诺。要固定顺序,就显式加 ORDER BY(下一节就学)。
  • 这类「有哪些分类 / 有哪些作者」的需求,在前端就是「筛选下拉框」的数据来源。

WHERE:按条件筛选

只要唐朝的作品
先讲为什么全站作品有上万篇,用户只想看「唐诗」。你总不能把全部数据取到前端再筛——那样又慢又浪费带宽。筛选这件事,应该交给离数据最近的数据库来做,这就是 WHERE:它决定「哪些行才有资格出现在结果里」。
动手做在 FROM 后面加一句 WHERE 条件,只留下朝代是「唐」的作品。
查询.sqlsql可运行
SELECT title, author, dynasty
FROM article
WHERE dynasty = '唐';
查询结果SQL 执行结果
运行结果: title | author | dynasty 静夜思 | 李白 | 唐 春望 | 杜甫 | 唐 陋室铭 | 刘禹锡 | 唐 -- 共 3 行
讲透
  • 字符串要用单引号:'唐'。数字和布尔值不用引号,写成 views > 300、is_recommend = 1。
  • 这里的 = 是「判断相等」,不是赋值。习惯写 Python 的同学要特别留心:SQL 里判断相等就是一个等号。
  • WHERE 里的条件可以组合:AND(都满足)、OR(满足其一)、NOT(取反)——这是第 3 章的重点。
  • 执行顺序:数据库是先执行 WHERE 筛行,再 SELECT 取列。所以 WHERE 里不能使用 SELECT 中定义的别名(新手最常踩的坑之一)。
避坑两个高频错误:① 把 = 写成 ==(那是 Python 的写法,SQL 里不认);② 给字符串忘了加引号,写成 WHERE dynasty = 唐——MySQL 会把「唐」当成一个列名去找,报 Unknown column '唐'。

ORDER BY:让结果有顺序

按浏览量从高到低
先讲为什么数据库返回结果的顺序,默认是「不保证」的——它怎么快就怎么来。但页面上「最热作品」「最新发布」都得有确定顺序。想让结果有顺序,就必须显式说出来,这就是 ORDER BY。
动手做按浏览量降序排出来,做一张「热门作品榜」。
查询.sqlsql可运行
SELECT title, author, views
FROM article
ORDER BY views DESC;
查询结果SQL 执行结果
运行结果: title | author | views 念奴娇·赤壁怀古 | 苏轼 | 412 静夜思 | 李白 | 328 声声慢 | 李清照 | 298 春望 | 杜甫 | 256 陋室铭 | 刘禹锡 | 203 天净沙·秋思 | 马致远 | 187 -- 共 6 行
讲透
  • ASC 升序(从小到大,默认)、DESC 降序(从大到小)。不写就是 ASC。数字按大小,字符串按字符编码顺序(中文默认按 Unicode 码点,和拼音顺序不同,要按拼音排序得用 CONVERT(... USING gbk) 之类的技巧)。
  • 可以按多列排序:ORDER BY dynasty ASC, views DESC 表示「先按朝代升序,同一个朝代内再按浏览量降序」。
  • 语句书写顺序是固定的:SELECT → FROM → WHERE → ORDER BY → LIMIT。顺序写反会直接报语法错。

LIMIT:只要前几条

首页只要 6 篇,不用全取
动手做首页只需要「浏览量最高的 3 篇」。用 LIMIT 限量,数据库就只取前几条,不必把整表算完。
查询.sqlsql可运行
SELECT title, author, views
FROM article
ORDER BY views DESC
LIMIT 3;
查询结果SQL 执行结果
运行结果: title | author | views 念奴娇·赤壁怀古 | 苏轼 | 412 静夜思 | 李白 | 328 声声慢 | 李清照 | 298 -- 共 3 行

再加一个 OFFSET,就能「跳过前几条、从第几条开始取」——这正是分页的底层原理:

查询.sqlsql可运行
-- 跳过前 2 条,再取 2 条(相当于「第 2 页,每页 2 条」)
SELECT id, title
FROM article
ORDER BY id
LIMIT 2 OFFSET 2;
查询结果SQL 执行结果
运行结果: id | title 3 | 念奴娇·赤壁怀古 4 | 声声慢 -- 共 2 行
讲透
  • LIMIT n:只取前 n 条。加上 OFFSET m 就是「跳过 m 条再取 n 条」。
  • 分页公式:第 p 页、每页 s 条 → LIMIT s OFFSET (p-1)*s。
  • 用 LIMIT 时一定要配 ORDER BY。否则「前 3 条」是哪 3 条不确定,翻页时可能出现重复或漏掉某条——这是分页 bug 的头号来源。

综合案例:作品列表页要用的三条查询

把本章串起来

「古典文学网」首页与列表页背后,其实就是下面这三类查询。用本章的知识,你已经能独立写出来了。

首页推荐位.sqlsql可运行
-- ① 首页推荐位:推荐作品,按浏览量降序
SELECT id, title, author
FROM article
WHERE is_recommend = 1
ORDER BY views DESC;
查询结果SQL 执行结果
运行结果: id | title | author 3 | 念奴娇·赤壁怀古 | 苏轼 1 | 静夜思 | 李白 2 | 春望 | 杜甫 -- 共 3 行
分类筛选.sqlsql可运行
-- ② 点「唐诗」分类:只看唐朝作品,热门在前
SELECT title, author, views
FROM article
WHERE dynasty = '唐'
ORDER BY views DESC;
查询结果SQL 执行结果
运行结果: title | author | views 静夜思 | 李白 | 328 春望 | 杜甫 | 256 陋室铭 | 刘禹锡 | 203 -- 共 3 行
列表分页.sqlsql可运行
-- ③ 列表分页:按发布顺序倒序,每页 3 条,取第 1 页
SELECT id, title, author
FROM article
ORDER BY id DESC
LIMIT 3;
查询结果SQL 执行结果
运行结果: id | title | author 6 | 陋室铭 | 刘禹锡 5 | 天净沙·秋思 | 马致远 4 | 声声慢 | 李清照 -- 共 3 行
讲透:一条语句是怎么「拼」出来的先想清楚四件事,再把它们按固定顺序摆好——① 要哪几列(SELECT)② 从哪张表(FROM)③ 筛什么条件(WHERE)④ 怎么排、取几条(ORDER BY / LIMIT)。这就是你以后写任何查询的通用套路。
动手练习
1查出所有「宋」代作品,只显示标题和作者两列。
2找出浏览量最高的 2 篇作品,显示标题与浏览量(提示:先排序、再限量)。
3迁移练习:「红影赏析」有个 movie 表(字段 id、title、genre、rating)。请查出所有「科幻」分类的电影,按评分从高到低排列。想一想:这和查「唐诗按浏览量排序」是不是同一件事?
参考答案
练习答案.sqlsql可运行
-- 第 1 题
SELECT title, author FROM article WHERE dynasty = '宋';

-- 第 2 题
SELECT title, views FROM article ORDER BY views DESC LIMIT 2;

-- 第 3 题(红影赏析,表结构不同,思路完全一致)
SELECT title, rating FROM movie WHERE genre = '科幻' ORDER BY rating DESC;
查询结果SQL 执行结果
运行结果: -- 第 1 题 title | author 念奴娇·赤壁怀古 | 苏轼 声声慢 | 李清照 -- 共 2 行 -- 第 2 题 title | views 念奴娇·赤壁怀古 | 412 静夜思 | 328 -- 共 2 行 -- 第 3 题(在红影的库里执行) title | rating 流浪地球 | 8.4 星际穿越 | 9.3 -- 按 rating 降序,实际顺序以库里数据为准

第 3 题的关键:换的是「表名和字段名」,不变的是「WHERE 筛条件 + ORDER BY 排序」这套骨架。能把它迁移过去,说明你真懂了。

本章小结