大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
SELECT 基本查询
这一课你将能做到
- 从任意一张表按需取列、用别名改名、用 DISTINCT 去掉重复
- 在查询时用表达式和常用函数即时加工列(四则运算、拼接、条件取值)
- 说清楚「为什么上线代码不建议
SELECT *」,并给出替代写法
为什么要学
SELECT 占日常 SQL 的八成。后端接口里,分页列表、详情页、下拉框选项,全是指定列的查询。会不会「只取需要的列、顺手把展示字段拼好」,直接决定接口的返回包大小和代码整洁度——一个 SELECT * 把 content_md 这种几百 KB 的正文列也拖出来,接口慢的锅常常就在这一行。
前置:已跑完第一课的建库脚本,以下语句都在 sql_practice 库执行。
先建立图像
把表想成一张 Excel:SELECT 负责挑列,WHERE 负责挑行(下一课),查询结果本身又是一张「虚拟表」:
course 表(7 行)
┌────┬────────────────────┬──────────┬───────┐
│ id │ title │ category │ price │
├────┼────────────────────┼──────────┼───────┤
│ 1 │ Java 基础入门 │ java │ 199 │
│ 2 │ Java 面向对象进阶 │ java │ 299 │
│ … │ … │ … │ … │
└────┴────────────────────┴──────────┴───────┘
SELECT title, price FROM course;
→ 只剩 title、price 两列,7 行不变(列是竖着切,行不动)
跟着做
步骤 1:全列查询 vs 指定列查询
SELECT * FROM course; -- 7 行 × 全部 8 列
SELECT slug, title, price FROM course; -- 7 行 × 3 列,清爽
* 返回的列顺序 = 建表时的定义顺序。查询工具里用 * 随便看看没问题,但要养成第二种的肌肉记忆。
步骤 2:列别名——给结果改名
SELECT title AS 课程名, price AS 售价 FROM course;
SELECT title AS course_name,
ROUND(price * 0.8, 2) AS `折后价(限时)`
FROM course;
AS可以省略(title 课程名也行),但写上更清楚- 别名含空格、括号或中文时,用反引号
`包起来最稳 - 表也能起别名:
SELECT c.title FROM course c;——JOIN 课会大量用到
步骤 3:表达式与函数——查询时加工数据
-- 四舍五入、算术
SELECT title, ROUND(price * 0.8, 2) AS price_80
FROM course;
-- CONCAT 拼接展示字段(接口里常见的「展示名」就这么拼)
SELECT CONCAT(title, '(', level, ')') AS display_name
FROM course;
-- IF 按条件取值
SELECT name, IF(vip = 1, '会员', '普通') AS member_label
FROM student;
注意:CONCAT 里任何一个参数是 NULL,整个结果就是 NULL。可能为 NULL 的列要用 CONCAT_WS 或 COALESCE 兜底,这是经典坑。
步骤 4:DISTINCT 去重
SELECT DISTINCT category FROM course;
-- 结果 3 行:java / database / frontend
SELECT DISTINCT category, level FROM course;
-- 结果 6 行:去重的是 (category, level) 的组合
第二条很关键:DISTINCT a, b 去重的是两列的组合,不是只对 a 去重。7 门课按 (category, level) 组合恰好 6 种(frontend+beginner 出现两次,合成一行)。
步骤 5:LIMIT 控制行数
SELECT title, price FROM course ORDER BY price DESC LIMIT 3;
-- 399 / 299 / 199 的三门课
排序细节下一课讲,这里先记住:调试大表时先 LIMIT,别一上来拉全表。
步骤 6:认识结果里的 NULL
SELECT id, student_id, lesson_id, score, completed_at
FROM study_record
WHERE student_id = 4;
-- 大鹏那条 completed=0 的记录,score 和 completed_at 都是 NULL
NULL 在结果里就显示为 NULL,它不是 0 也不是空字符串。怎么判断它,下一课讲。
讲清楚要点
- 逻辑执行顺序(先建立直觉,后面几课反复用到):
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。所以ORDER BY可以用 SELECT 起的别名,WHERE不行——它跑在 SELECT 之前。 SELECT *三宗罪:把大列(TEXT/MEDIUMTEXT)拖垮带宽;用不上覆盖索引(第 8 课);表加了列,接口返回字段就悄悄漂移。写清楚列清单是后端的基本功,MyBatis 的resultMap也要求列名明确。- 别名规则:
AS可省;含空格/特殊字符/中文建议反引号;表别名一到两个字母是通行惯例(course c、study_record r)。 - DISTINCT 作用于整行组合:
DISTINCT a, b是组合去重。单纯去重用它语义清晰;如果还要顺便计数,那是下一阶段GROUP BY的活。 - 字符串字面量用单引号:
'published'。反引号`是给标识符(表名列名别名)的,别混用。MySQL 里双引号默认也当字符串,但开了ANSI_QUOTES模式就变成标识符引用——为了可移植,字符串统一单引号。 - 不写 ORDER BY 就不要假设行序:查询结果没有保证的顺序,哪怕看起来像按 id 排的。需要顺序就必须写
ORDER BY。
你可能会懵的地方
-
为什么 WHERE 里不能用 SELECT 起的别名? 执行顺序:WHERE 先于 SELECT 跑,别名那时还不存在。要么把表达式完整写一遍,要么套一层子查询。ORDER BY 在 SELECT 之后跑,所以可以用别名。
-
DISTINCT 和 GROUP BY 都能去重,用哪个? 单纯去重用 DISTINCT,语义就是「去重」;GROUP BY 是为聚合(COUNT/AVG…)服务的,为了去重硬上 GROUP BY 语义不清。MySQL 8 默认开启
ONLY_FULL_GROUP_BY,乱写会被拦。 -
SELECT *到底多慢? 表小无所谓。线上大表 + 宽表 + TEXT 列才致命:多传几百 KB、多读好几个数据页。原则很简单:按需取列。 -
中文别名合法吗? 合法。但建议加反引号,防止别名里混入空格、括号时解析出错。
-
查询出来的 NULL 能显示成别的东西吗? 能,用
IFNULL(score, 0)或COALESCE(score, 0),「第一个非 NULL 的参数」。注意这只影响展示,不改表里的数据。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
上线代码写 SELECT * | 慢、字段漂移、盖不住索引 | 按需写列清单 |
| 别名含空格不加引号 | 语法错误 | 反引号包裹:AS `折后价` |
| WHERE 里用 SELECT 别名 | Unknown column | 写完整表达式,或套子查询 |
| 字符串用双引号 | ANSI_QUOTES 模式下报错 | 字符串一律单引号 |
以为 DISTINCT a,b 只对 a 去重 | 结果行数对不上 | 明确是组合去重 |
| 依赖无 ORDER BY 的返回顺序 | 分页/导出顺序漂移 | 需要顺序必写 ORDER BY |
卡点急救
Unknown column 'xxx' in 'field list':列名拼错,或写了表别名却忘了给列加前缀。先DESC 表名对准列名。- 结果比预期多/少一行:检查 DISTINCT 是不是组合去重、WHERE 条件是否生效(下一课细讲)。
- 数字列显示很长的小数(如
249.000000):是 AVG/SUM 的显示精度问题,套ROUND(x, 2)。 - 中文乱码:回到第一课卡点 4,连接层字符集问题,跟 SELECT 本身无关。
面试常问
-
SELECT *有什么问题? 三点:传输和内存浪费(尤其 TEXT 大列);可能用不上覆盖索引;表结构变更后返回字段漂移。生产代码写明确列清单。 -
DISTINCT 的实现和代价? 通常靠排序或哈希临时表去重,大结果集有明显代价。能在表设计或上游保证唯一,就别靠 DISTINCT 兜底。
-
不写 ORDER BY,结果顺序由什么决定? 无保证——取决于执行计划、存储顺序、并发情况。任何「默认按 id 排」的假设都是错的。
-
MyBatis 里怎么处理查询结果映射? 列名和 Java 属性名对不上时用
resultMap显式映射,或查询时用列别名对齐驼峰属性。这也是「写明确列清单」的实际原因之一。
自测三问
- 写一条查询:把课程标题和难度拼成「标题(level)」展示。
SELECT DISTINCT category, level FROM course;返回几行?为什么不是 3 行?- WHERE 能用 SELECT 起的别名吗?ORDER BY 呢?为什么?
小结一句话
SELECT 是「挑列」:明确列清单、用别名让结果可读、用 DISTINCT 去组合重复;上线代码永远不写 SELECT *。
延伸
- MySQL SELECT 语法
- 下一课:WHERE 过滤与排序——从「挑列」升级到「挑行」。