MySQL SELECT 方法速查
先确定“每行结果代表什么”,再选择查询方法:查明细用 WHERE,做统计用 GROUP BY,需要保留明细并计算排名用窗口函数。这里以 MySQL 8.4 为准,重点是 SELECT 的写法和结果含义。
按问题找方法
| 想查询什么 | 主要写法 | 直接阅读 |
|---|---|---|
| 满足条件、含空值或重复值的数据 | WHERE、IS NULL、DISTINCT | 筛选与去重 |
| 每个客户的订单数、金额、已完成金额 | GROUP BY、HAVING、条件聚合 | 分组统计 |
| 带用户名称的订单、没有订单的用户 | JOIN、LEFT JOIN、NOT EXISTS | 关联与存在性 |
| 高于平均值的数据、分步骤统计 | 标量子查询、派生表、WITH | 子查询与 CTE |
| 每个客户最新一笔、每组前 N、累计金额 | ROW_NUMBER、RANK、SUM OVER | 窗口函数 |
| 金额最高的前几笔、继续读取下一页 | ORDER BY、LIMIT、游标条件 | 排序与分页 |
| 拼接文本、处理 NULL、按月显示 | COALESCE、CASE、日期与字符串函数 | 查询语法与函数 |
统一样例数据与运行前提
先在独立练习库执行现有查询样例初始化。以下所有查询共用该数据,不需要为每个例子重新建表。
users(id, name, age, email):
| id | name | age | |
|---|---|---|---|
| 1 | Lin | 25 | lin@example.com |
| 2 | Chen | 30 | NULL |
| 3 | Wu | 22 | wu@example.com |
orders(id, customer_id, amount, status, created_at):
| id | customer_id | amount | status | created_at |
|---|---|---|---|---|
| 101 | 1 | 800 | completed | 2024-01-01 |
| 102 | 1 | 500 | pending | 2024-01-02 |
| 103 | 2 | 200 | completed | 2024-02-01 |
下文是这份固定样例的预期结果。本次整理未连接 MySQL 执行 SQL;修改练习数据后结果会变化。示例无 ORDER BY 时,只说明结果内容,不承诺返回顺序。
筛选与去重
| 需求 | SQL 示例 | 预期结果 / 注意事项 |
|---|---|---|
| 选择需要的列 | SELECT id, name FROM users ORDER BY id; | 1/Lin、2/Chen、3/Wu;比 SELECT * 更明确 |
| 数值范围 | SELECT id FROM users WHERE age BETWEEN 23 AND 30 ORDER BY id; | 1、2;BETWEEN 包含两端 |
| 多条件组合 | SELECT id FROM orders WHERE status = 'completed' AND amount >= 500; | 101;AND/OR 混用时加括号 |
| 指定几个值 | SELECT id FROM orders WHERE status IN ('completed', 'pending') ORDER BY id; | 101、102、103 |
| 查缺失值 | SELECT id FROM users WHERE email IS NULL; | 2;不写 email = NULL |
| 前缀匹配 | SELECT id FROM users WHERE name LIKE 'L%'; | 1;大小写规则取决于排序规则 |
| 去重客户 | SELECT DISTINCT customer_id FROM orders ORDER BY customer_id; | 1、2;DISTINCT 对整个 SELECT 列组合去重 |
| 日期区间 | SELECT id FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2024-02-01' ORDER BY id; | 101、102;半开区间也适用于带时间的字段 |
分组与条件统计
| 需求 | SQL 示例 | 预期结果 / 注意事项 |
|---|---|---|
| 总行数与非空数 | SELECT COUNT(*) AS total, COUNT(email) AS filled FROM users; | total=3、filled=2 |
| 每个客户订单数 | SELECT customer_id, COUNT(*) AS n FROM orders GROUP BY customer_id ORDER BY customer_id; | 1/2、2/1 |
| 找到重复客户 | SELECT customer_id, COUNT(*) AS n FROM orders GROUP BY customer_id HAVING COUNT(*) > 1; | 1/2;可用于检查重复值,不代表订单重复 |
| 找累计消费超过 1000 的客户 | SELECT customer_id, SUM(amount) AS total FROM orders GROUP BY customer_id HAVING SUM(amount) > 1000; | 1/1300;WHERE 筛行,HAVING 筛分组 |
| 只统计已完成金额 | SELECT customer_id, SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END) AS paid FROM orders GROUP BY customer_id ORDER BY customer_id; | 1/800、2/200 |
| 按月统计订单 | SELECT DATE_FORMAT(created_at, '%Y-%m') AS month, COUNT(*) AS n FROM orders GROUP BY month ORDER BY month; | 2024-01/2、2024-02/1 |
只查询分组键或合法聚合表达式,不通过关闭 ONLY_FULL_GROUP_BY 来掩盖不明确的结果。SUM(DISTINCT amount) 去重的是金额,不是订单;同额的不同订单不应该因此少算。
多表关联与存在性
| 需求 | SQL 示例 | 预期结果 / 注意事项 |
|---|---|---|
| 订单附带用户姓名 | SELECT o.id, u.name FROM orders o JOIN users u ON u.id = o.customer_id ORDER BY o.id; | 101/Lin、102/Lin、103/Chen |
| 所有用户及订单数 | SELECT u.id, COUNT(o.id) AS n FROM users u LEFT JOIN orders o ON o.customer_id = u.id GROUP BY u.id ORDER BY u.id; | 1/2、2/1、3/0;COUNT(*) 会把空侧行也计入 |
| 至少有一笔完成订单 | SELECT u.id FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = u.id AND o.status = 'completed') ORDER BY u.id; | 1、2;每个用户最多返回一行 |
| 没有任何订单 | SELECT u.id FROM users u WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = u.id); | 3;不受 NOT IN 子查询含 NULL 的陷阱影响 |
外连接先决定“是否保留未匹配用户”。例如筛右表的已完成订单时,将条件放在 ON 可保留没有完成订单的用户;放在 WHERE 则会过滤外连接补出的 NULL 行。
多个一对多表直接连接后,行数可能被乘大。统计每个用户的两个明细集合时,通常先分别聚合到用户粒度,再连接汇总结果。不要随手加 DISTINCT 或 SUM(DISTINCT amount) 作为通用修复。
排序、Top N 与分页
| 需求 | SQL 示例 | 预期结果 / 注意事项 |
|---|---|---|
| 全局金额最高两笔 | SELECT id, amount FROM orders ORDER BY amount DESC, id ASC LIMIT 2; | 101/800、102/500;唯一 ID 用于同分时稳定排序 |
| 每页两条的第二页 | SELECT id FROM orders ORDER BY id LIMIT 2 OFFSET 2; | 103;大偏移通常需要扫描并丢弃许多行 |
| 从上一页继续读 | SELECT id FROM orders WHERE id > 102 ORDER BY id LIMIT 2; | 103;102 应来自上一页最后一行 |
| 按时间和 ID 继续读 | SELECT id FROM orders WHERE created_at > '2024-01-02' OR (created_at = '2024-01-02' AND id > 102) ORDER BY created_at, id LIMIT 2; | 103;条件方向必须与排序方向一致 |
LIMIT 2 只取全局两行;“每个客户最新两笔”需要分组窗口排名。同样,先 MAX(created_at) 再任意取其他列,不能保证取到同一条订单。
组合查询的三个检查点
- 行粒度:每行是订单、用户还是用户每月?连接后是否变多?
- 边界数据:没有订单、NULL、并列金额、同一天多笔记录是否符合预期?
- 访问路径:确认结果后再用 EXPLAIN 与索引 检查扫描和排序,不给所有 SELECT 套固定复杂度。
应用传入的条件值使用驱动参数绑定;表名、排序列不能当成普通值参数绑定,应通过允许列表选择。不要直接拼接用户输入。
继续阅读:查询练习、MySQL 8.4 SELECT、JOIN 语法、GROUP BY。
继续阅读
- 知识专题
- 应用实践
- 知识专题