CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
SQL 与关系型数据库/课时/SELECT 基本查询
RawBlame
课时目录10 节

SQL 与关系型数据库

  1. 01关系型数据库与 SQL 是什么
  2. 02SELECT 基本查询
  3. 03WHERE 过滤与排序
  4. 04JOIN 多表联结
  5. 05聚合与 GROUP BY
  6. 06INSERT、UPDATE、DELETE
  7. 07表设计与主键外键
  8. 08索引与查询性能
  9. 09事务基础
  10. 10综合练习与本站数据模型

SELECT 基本查询

查列、别名、去重 DISTINCT。

30 分钟

SELECT 基本查询

这一课你将能做到

  1. 从任意一张表按需取列、用别名改名、用 DISTINCT 去掉重复
  2. 在查询时用表达式和常用函数即时加工列(四则运算、拼接、条件取值)
  3. 说清楚「为什么上线代码不建议 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 也不是空字符串。怎么判断它,下一课讲。

讲清楚要点

  1. 逻辑执行顺序(先建立直觉,后面几课反复用到):FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。所以 ORDER BY 可以用 SELECT 起的别名,WHERE 不行——它跑在 SELECT 之前。
  2. SELECT * 三宗罪:把大列(TEXT/MEDIUMTEXT)拖垮带宽;用不上覆盖索引(第 8 课);表加了列,接口返回字段就悄悄漂移。写清楚列清单是后端的基本功,MyBatis 的 resultMap 也要求列名明确。
  3. 别名规则:AS 可省;含空格/特殊字符/中文建议反引号;表别名一到两个字母是通行惯例(course c、study_record r)。
  4. DISTINCT 作用于整行组合:DISTINCT a, b 是组合去重。单纯去重用它语义清晰;如果还要顺便计数,那是下一阶段 GROUP BY 的活。
  5. 字符串字面量用单引号:'published'。反引号 ` 是给标识符(表名列名别名)的,别混用。MySQL 里双引号默认也当字符串,但开了 ANSI_QUOTES 模式就变成标识符引用——为了可移植,字符串统一单引号。
  6. 不写 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

卡点急救

  1. Unknown column 'xxx' in 'field list':列名拼错,或写了表别名却忘了给列加前缀。先 DESC 表名 对准列名。
  2. 结果比预期多/少一行:检查 DISTINCT 是不是组合去重、WHERE 条件是否生效(下一课细讲)。
  3. 数字列显示很长的小数(如 249.000000):是 AVG/SUM 的显示精度问题,套 ROUND(x, 2)。
  4. 中文乱码:回到第一课卡点 4,连接层字符集问题,跟 SELECT 本身无关。

面试常问

  • SELECT * 有什么问题? 三点:传输和内存浪费(尤其 TEXT 大列);可能用不上覆盖索引;表结构变更后返回字段漂移。生产代码写明确列清单。

  • DISTINCT 的实现和代价? 通常靠排序或哈希临时表去重,大结果集有明显代价。能在表设计或上游保证唯一,就别靠 DISTINCT 兜底。

  • 不写 ORDER BY,结果顺序由什么决定? 无保证——取决于执行计划、存储顺序、并发情况。任何「默认按 id 排」的假设都是错的。

  • MyBatis 里怎么处理查询结果映射? 列名和 Java 属性名对不上时用 resultMap 显式映射,或查询时用列别名对齐驼峰属性。这也是「写明确列清单」的实际原因之一。

自测三问

  1. 写一条查询:把课程标题和难度拼成「标题(level)」展示。
  2. SELECT DISTINCT category, level FROM course; 返回几行?为什么不是 3 行?
  3. WHERE 能用 SELECT 起的别名吗?ORDER BY 呢?为什么?

小结一句话

SELECT 是「挑列」:明确列清单、用别名让结果可读、用 DISTINCT 去组合重复;上线代码永远不写 SELECT *。

延伸

  • MySQL SELECT 语法
  • 下一课:WHERE 过滤与排序——从「挑列」升级到「挑行」。
加载本节练习…
← 上一课关系型数据库与 SQL 是什么下一课 →WHERE 过滤与排序