RSS
菜单
全部文章快讯开发科技深度热点

第 82 章 速查卡:SQL高频写法

内容摘要

本篇把面试和手写题中最常出现的 SQL 写法按"分页 / 去重 / 聚合 / JOIN / 子查询 / 更新"分类整理成速查表,每条给出场景、推荐写法与说明。配合大量可运行示例代码,让你在面试白板上能快速写出正确、高效的 SQL。本文所有示例基于 MySQL 8.0,语法以 8.0 为准。

📖 本篇简介:本篇把面试和手写题中最常出现的 SQL 写法按"分页 / 去重 / 聚合 / JOIN / 子查询 / 更新"分类整理成速查表,每条给出场景、推荐写法与说明。配合大量可运行示例代码,让你在面试白板上能快速写出正确、高效的 SQL。本文所有示例基于 MySQL 8.0,语法以 8.0 为准。

目录

  • 1 本篇使用说明
  • 2 分页查询高频写法
  • 3 去重与排序高频写法
  • 4 聚合与分组高频写法
  • 5 JOIN写法速查
  • 6 子查询与CTE写法
  • 7 更新删除高频写法
  • 8 常见低效写法反例表
  • 9 日期字符串函数高频用法
  • 10 手写题高频题型模板
  • 11 SQL书写规范速记
  • 12 注意事项与常见坑
  • 13 自测练习

1 本篇使用说明

SQL 手写题在面试中占比很高,考点不是"背语法",而是"在正确的场景选对写法"。同样的需求,写法不同,性能可能差几个数量级。下面每张表都按"场景 → 推荐写法 → 说明"组织。

阅读建议:先看"场景"列对应你遇到过的业务,再看"推荐写法"列模仿结构,最后对照"反例表"避开常见错误。所有示例基于 MySQL 8.0,CTE、窗口函数等 8.0 特性会在对应小节标注。

| 写法维度 | 你要掌握的 | 面试中的考点 |

|---|---|---|

| 分页 | 深分页怎么优化 | LIMIT 大偏移为什么慢 |

| 去重 | DISTINCT vs GROUP BY | 哪个走索引 |

| 聚合 | 分组后过滤用 HAVING | WHERE 与 HAVING 区别 |

| JOIN | INNER/LEFT 选型 | N+1 问题怎么避免 |

| 子查询 | EXISTS vs IN | 哪个性能好 |

| 更新 | UPDATE 多表 | 小心无 WHERE |

| 日期 | 怎么取当月/上周 | 别在列上套函数 |

| 字符串 | 模糊搜索 | 前导通配符失效 |

2 分页查询高频写法

分页是业务里最常见的查询,也是面试最爱考的写法。核心痛点是深分页:LIMIT 100000, 10 要扫描 10 万行再丢弃。

| 场景 | 推荐写法 | 说明 |

|---|---|---|

| 普通浅分页 | LIMIT 0, 10 | 前几页没问题,走索引 |

| 深分页第一版 | LIMIT 100000, 10 | 慢,扫描 10 万行 |

| 深分页优化(主键定位) | WHERE id > 上次最大id LIMIT 10 | 延迟关联,走索引 |

| 深分页优化(子查询) | WHERE id IN (SELECT id FROM t LIMIT 100000,10) | 先查主键再回表 |

| 总数+分页 | SELECT COUNT(*) 单独查 | 不要和分页混一条 SQL |

| 按时间倒序分页 | ORDER BY created_at DESC, id DESC | 加 id 做 tiebreaker 防漏 |

| 跳页跳到底 | 不允许跳页 | 业务上限制只能上一页下一页 |

| 大数据量总数 | EXPLAIN 估算行数 | 大表 count 慢,用估算 |

| 游标分页 | WHERE id < 上次id LIMIT 10 | 无限滚动加载首选 |

| 带筛选条件分页 | WHERE status=1 ORDER BY id LIMIT 10 | 筛选列要有联合索引 |

| 分页+排序稳定 | 末尾加唯一列 | 防止排序值相同时行跳动 |

| 导出全量 | 不用 LIMIT,用游标分批 | 避免一次性取百万行爆内存 |

下面是深分页优化的完整对比,本示例基于 MySQL 8.0:

-- 慢写法:扫描前 100010 行,丢弃前 100000 行
SELECT * FROM orders ORDER BY id LIMIT 100000, 10;

-- 快写法:先用覆盖索引定位主键,再回表
SELECT o.* FROM orders o
JOIN (SELECT id FROM orders ORDER BY id LIMIT 100000, 10) t
ON o.id = t.id;

-- 更快:记住上一页最大 id,下一页直接定位
-- 假设上一页最后一条 id 是 100000
SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 10;

面试话术:深分页优化的本质是"把扫描大量行的动作压在覆盖索引上,回表只回 10 行"。WHERE id > 上次最大id 是性能最好的,但要求前端能传递上次 id。

3 去重与排序高频写法

去重和排序是 SELECT 里最常见的修饰,写法选择直接影响是否走索引。

| 场景 | 推荐写法 | 说明 |

|---|---|---|

| 结果去重 | SELECT DISTINCT col | 单列去重,走索引 |

| 分组去重 | GROUP BY col | 配合聚合函数,比 DISTINCT 更灵活 |

| 取每组最新一条 | ROW_NUMBER() OVER(PARTITION BY ..) | 8.0 窗口函数首选 |

| 排序+限制 | ORDER BY col LIMIT n | 走索引避免 filesort |

| 多列排序 | ORDER BY a DESC, b ASC | 注意方向一致才走联合索引 |

| 随机取 N 条 | ORDER BY RAND() LIMIT 10 | 慢,大数据量别用 |

| 排除空值 | WHERE col IS NOT NULL | 先过滤再排序 |

| 去除重复行保留一条 | GROUP BY 业务唯一键 | 按业务键分组 |

| 排序时 NULL 位置 | ORDER BY col IS NULL | MySQL 默认 NULL 最小 |

| 按自定义顺序排 | ORDER BY FIELD(col,'a','b','c') | 有限枚举排序 |

| 汉字排序 | CONVERT(col USING gbk) | 按拼音/笔画 |

| 排序是否走索引 | EXPLAIN 看 Extra | Using filesort 说明没走 |

-- 取每个用户最新的一笔订单(本示例基于 MySQL 8.0,窗口函数)
SELECT user_id, order_id, amount, created_at
FROM (
  SELECT user_id, order_id, amount, created_at,
         ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
  FROM orders
) t
WHERE rn = 1;   -- 每个用户只取第一行

ORDER BY RAND() 在大表上会生成全表随机序列,极慢。优化思路是先 WHERE id BETWEEN 随机范围取一段,再 ORDER BY RAND() LIMIT n。

4 聚合与分组高频写法

聚合分组题在手写题里出现频率极高,考点集中在"WHERE 与 HAVING 的区别"和"分组后取 TopN"。

| 场景 | 推荐写法 | 说明 |

|---|---|---|

| 统计行数 | COUNT(*) | 不忽略 NULL,最常用 |

| 统计非空列 | COUNT(col) | 忽略该列为 NULL 的行 |

| 分组统计 | GROUP BY col | 配合聚合函数 |

| 分组后过滤 | HAVING COUNT(*) > 10 | WHERE 过滤行,HAVING 过滤组 |

| 分组排序取 TopN | ROW_NUMBER() 或子查询 | 见下方示例 |

| 同比环比 | LAG() OVER(ORDER BY ..) | 8.0 窗口函数 |

| 累计求和 | SUM() OVER(ORDER BY ..) | 窗口函数 |

| 空值聚合处理 | COALESCE(SUM(col), 0) | 避免返回 NULL |

| 分组百分比 | COUNT(*) / SUM(COUNT(*)) OVER() | 窗口函数算占比 |

| 去重计数 | COUNT(DISTINCT col) | 比 GROUP BY 再 COUNT 快 |

| 多维分组 | GROUP BY a, b WITH ROLLUP | 8.0 支持汇总行 |

| 中位数/分位数 | PERCENT_RANK() | 8.0 窗口函数 |

-- 每个部门工资 Top3 的员工(本示例基于 MySQL 8.0)
SELECT dept, emp, salary
FROM (
  SELECT dept, emp, salary,
         DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rk
  FROM emp
) t
WHERE rk <= 3;   -- 每部门取前 3 名,并列算同名次

WHERE 在分组前过滤行,HAVING 在分组后过滤组。面试常问"为什么 WHERE 里不能写聚合函数",答案就是聚合发生在分组之后,WHERE 阶段还没分组。

5 JOIN写法速查

JOIN 是手写题重灾区,考点集中在"用哪种 JOIN"和"避免 N+1"。

| 场景 | 推荐写法 | 说明 |

|---|---|---|

| 两表都匹配 | INNER JOIN | 只保留两边都有的行 |

| 保留左表全部 | LEFT JOIN | 右表没匹配补 NULL |

| 保留右表全部 | RIGHT JOIN | 一般改写成 LEFT 可读性更好 |

| 多表关联 | 拆成多个小 JOIN | 别一次 JOIN 五张表 |

| 防 N+1 | 一次性 JOIN 出来 | 不要循环里查数据库 |

| 去重关联 | 先子查询去重再 JOIN | 避免主表行数被放大 |

| 自连接 | JOIN t t2 ON .. | 查上下级、配对 |

| 半连接 | EXISTS | 不需要右表列时用 |

| 笛卡尔积排查 | 检查 ON 条件 | 忘写 ON 会全表相乘 |

| JOIN 字段类型 | 两边一致 | 隐式转换索引失效 |

| JOIN 顺序 | 小表驱动大表 | 优化器一般自动选 |

| 关联为空补默认 | COALESCE(b.col,0) | 避免 NULL 参与计算 |

-- 查询每个用户的最近一笔订单和用户名(本示例基于 MySQL 8.0)
SELECT u.user_id, u.name, o.order_id, o.amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.user_id
  AND o.id = (SELECT MAX(id) FROM orders WHERE user_id = u.user_id);
-- LEFT JOIN 保证没下单的用户也出现在结果里

JOIN 性能三要素:关联字段类型一致、关联字段有索引、小表驱动大表。类型不一致(比如一边 int 一边 varchar)会导致索引失效,这是高频坑。

6 子查询与CTE写法

8.0 引入 CTE(WITH 子句)后,复杂嵌套查询可读性大幅提升。面试会问"EXISTS 和 IN 哪个快"。

| 场景 | 推荐写法 | 说明 |

|---|---|---|

| 判断存在性 | EXISTS (SELECT 1 ..) | 半匹配即停,通常比 IN 快 |

| 取值列表 | IN (SELECT col ..) | 子查询结果小才用 |

| 派生表 | FROM (SELECT ..) t | 复杂查询拆步骤 |

| 递归查询 | WITH RECURSIVE | 树形结构、菜单层级 |

| 复用中间结果 | WITH cte AS (..) SELECT | 8.0 推荐,可读性好 |

| 相关子查询 | WHERE col = (SELECT ..) | 注意每行都执行 |

| 标量子查询 | SELECT (SELECT ..) FROM t | 每行执行一次,慎用 |

| 多行子查询 | IN / ANY / ALL | 注意语义区别 |

| 子查询改写 JOIN | 能用 JOIN 就别嵌套 | 优化器更好处理 |

| 物化 CTE | 8.0 自动物化物化表 | 多次引用时省计算 |

| 树形递归 | WITH RECURSIVE cte AS (..) | 部门层级、评论楼层 |

| NOT IN 陷阱 | 改 NOT EXISTS | NOT IN 遇 NULL 全空 |

-- 查询有下单记录的用户(本示例基于 MySQL 8.0)
-- EXISTS 写法:匹配到即停止,效率高
SELECT u.user_id, u.name
FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o WHERE o.user_id = u.user_id
);

-- CTE 写法:先聚合订单再关联(本示例基于 MySQL 8.0)
WITH order_stat AS (
  SELECT user_id, COUNT(*) AS cnt, SUM(amount) AS total
  FROM orders
  GROUP BY user_id
)
SELECT u.name, s.cnt, s.total
FROM users u
JOIN order_stat s ON s.user_id = u.user_id;

EXISTS vs IN 的经验法则:外表大、子查询小用 IN;外表小、子查询大用 EXISTS。MySQL 8.0 优化器很多场景下两者计划相同,但写 EXISTS 语义更清晰。

7 更新删除高频写法

UPDATE 和 DELETE 是高危操作,面试考的是"怎么写才安全"。

| 场景 | 推荐写法 | 说明 |

|---|---|---|

| 单表更新 | UPDATE t SET .. WHERE id=.. | 必须带 WHERE |

| 多表更新 | UPDATE a JOIN b ON .. SET a.x=b.y | 注意关联条件 |

| 按子查询更新 | UPDATE t SET col=(SELECT ..) | 避免更新自己 |

| 批量删除 | DELETE FROM t WHERE .. LIMIT 1000 | 分批,别一次删百万行 |

| 清空表 | TRUNCATE TABLE t | 比 DELETE 快,自增归零 |

| 插入或更新 | INSERT ... ON DUPLICATE KEY UPDATE | 8.0 常用 upsert |

| 批量插入 | INSERT INTO t VALUES (..),(..) | 一次多行比循环快 |

| 替换插入 | REPLACE INTO | 先 DELETE 再 INSERT,慎用 |

| 更新计数器 | SET cnt = cnt + 1 | 原子,别先 SELECT 再算 |

| 删除带 LIMIT | 分批删 | 避免大事务锁表 |

| 更新排序 | ORDER BY id LIMIT n | 配合 LIMIT 安全批量 |

| 禁用安全更新 | sql_safe_updates | 无 WHERE 更新直接报错 |

-- 安全的批量删除(本示例基于 MySQL 8.0)
-- 每次删 1000 行,循环执行,避免大事务锁表
DELETE FROM orders WHERE status = 'expired' AND created_at < '2024-01-01'
LIMIT 1000;

-- upsert:存在则更新,不存在则插入(本示例基于 MySQL 8.0)
INSERT INTO user_stat (user_id, cnt) VALUES (1, 1)
ON DUPLICATE KEY UPDATE cnt = cnt + 1;

面试必问:UPDATE/DELETE 忘写 WHERE 会怎样?答案是全表更新/删除,锁全表。生产环境要用事务包起来先 SELECT 确认,或者开启 sql_safe_updates。

8 常见低效写法反例表

这张表把"看着对、其实慢"的写法集中列出,面试识别反例是送分题。

| 反例写法 | 问题 | 正确写法 |

|---|---|---|

| WHERE name LIKE '%张%' | 前导通配符不走索引 | 全文索引或缩短前缀 |

| WHERE DATE(create_time)='2024-01-01' | 函数包裹列,索引失效 | WHERE create_time >= '2024-01-01' AND < '2024-01-02' |

| WHERE id + 1 = 100 | 表达式在列上,索引失效 | WHERE id = 99 |

| SELECT * FROM t | 取无用列,回表多 | 只查需要的列 |

| WHERE status IN (...) 太多 | 几百个值优化器放弃索引 | 改临时表 JOIN |

| ORDER BY RAND() | 全表排序 | 主键范围随机 |

| SELECT COUNT(*) FROM big_table | 大表慢 | 估算值或缓存 |

| OR 连接两个非索引列 | 可能索引合并失效 | 改 UNION ALL |

| 隐式类型转换 | varchar 列用数字查,索引失效 | 两边类型一致 |

| 深分页 LIMIT 100000,10 | 扫描大量行 | 延迟关联 |

| WHERE LOWER(name)='abc' | 函数包裹列 | 存小写字段或生成列 |

| NOT IN 子查询含 NULL | 结果全空 | 改 NOT EXISTS |

| JOIN 忘写 ON | 笛卡尔积 | 检查关联条件 |

-- 反例 vs 正例:时间范围查询(本示例基于 MySQL 8.0)
-- 反例:函数包裹列,索引失效
SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01';
-- 正例:范围条件,走索引
SELECT * FROM orders
WHERE create_time >= '2024-01-01'
  AND create_time <  '2024-01-02';

9 日期字符串函数高频用法

日期和字符串函数在手写题里经常出现,考点集中在"怎么取昨天/本月/上周"以及"别在列上套函数"。

| 场景 | 推荐写法 | 说明 |

|---|---|---|

| 取当天数据 | WHERE create_time >= CURDATE() | 走索引 |

| 取昨天 | WHERE create_time >= DATE_SUB(CURDATE(),INTERVAL 1 DAY) | 范围条件 |

| 取最近 7 天 | WHERE create_time >= DATE_SUB(NOW(),INTERVAL 7 DAY) | 范围条件 |

| 取本月 | WHERE create_time >= DATE_FORMAT(CURDATE(),'%Y-%m-01') | 月初 |

| 格式化日期 | DATE_FORMAT(dt,'%Y-%m-%d') | 展示用 |

| 取年份 | YEAR(dt) | 计算用 |

| 日期加减 | DATE_ADD(dt, INTERVAL 1 MONTH) | 注意月份天数 |

| 字符串拼接 | CONCAT(a,'-',b) | 别用 || |

| 字符串截取 | SUBSTRING(col,1,10) | 注意从 1 开始 |

| 去空格 | TRIM(col) | 数据清洗 |

| 大小写 | UPPER/LOWER(col) | 索引失效,慎用 |

| 模糊前缀 | LIKE '张%' | 走索引 |

-- 统计最近 30 天每天的下单量(本示例基于 MySQL 8.0)
SELECT DATE(create_time) AS dt, COUNT(*) AS cnt
FROM orders
WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
GROUP BY DATE(create_time)
ORDER BY dt;

10 手写题高频题型模板

这张表把面试白板上最常出现的 20 种题型浓缩成模板,每种题型给出套路,背下来遇到类似题直接套。

| 题型 | 套路模板 | 示例要点 |

|---|---|---|

| 求第 N 高 | 窗口函数 DENSE_RANK | 分组排序取 rn=N |

| 每个分类 Top3 | ROW_NUMBER + 子查询 | 分组取前 3 |

| 去重保留一条 | GROUP BY 业务键 | 或窗口函数 rn=1 |

| 连续登录 N 天 | 日期-行号分组 | 8.0 经典题 |

| 同比环比 | LAG() 窗口函数 | 对比上一期 |

| 累计求和 | SUM() OVER(ORDER BY) | 滚动汇总 |

| 占比 | COUNT / SUM(COUNT) OVER() | 算百分比 |

| 行列转换 | CASE WHEN 聚合 | 行转列 |

| 找重复 | GROUP BY HAVING COUNT>1 | 找重复数据 |

| 删除重复保留一条 | 窗口函数 + 主键过滤 | 删 rn>1 |

| 空值处理 | COALESCE(col, 默认值) | 防 NULL |

| 多条件排序 | ORDER BY FIELD | 自定义顺序 |

| 取上一条记录 | LAG() | 对比历史 |

| 取未下单用户 | LEFT JOIN 后 IS NULL | 找不存在的 |

| 分组拼接 | GROUP_CONCAT | 多行拼一行 |

| 分组最大值 | MAX() 或窗口函数 | 看是否要同组其他列 |

| 日期区间统计 | DATE_FORMAT + GROUP BY | 按天/周/月 |

| 行列转换求总数 | SUM(CASE WHEN..) | 交叉报表 |

| 分页总数 | COUNT + LIMIT 分开 | 别混一条 |

| 树形递归 | WITH RECURSIVE | 部门层级 |

-- 连续登录 3 天的用户(本示例基于 MySQL 8.0 经典题)
WITH login_distinct AS (
  SELECT DISTINCT user_id, login_date FROM user_login
)
SELECT user_id, MIN(login_date) AS start_day, MAX(login_date) AS end_day
FROM (
  SELECT user_id, login_date,
         DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER(
           PARTITION BY user_id ORDER BY login_date) DAY) AS grp
  FROM login_distinct
) t
GROUP BY user_id, grp
HAVING COUNT(*) >= 3;   -- 连续 3 天登录

连续登录题是 8.0 窗口函数的经典题,套路是"日期减去行号得到分组键,同组内日期差固定就是连续"。

11 SQL书写规范速记

除了写对,面试手写题还要写得"像工程化代码"。下面这张表是书写规范,写在白板上让面试官眼前一亮。

| 规范点 | 做法 | 为什么 |

|---|---|---|

| 关键字大写 | SELECT、FROM、WHERE 大写 | 可读性好,行业惯例 |

| 表名别名短 | u 代表 users,o 代表 orders | 短别名减少噪音 |

| 列写清楚 | 不用 SELECT * | 明确依赖,避免改表出问题 |

| 缩进对齐 | JOIN 换行写 ON 条件 | 一眼看清关联 |

| 注释复杂逻辑 | -- 连续登录题思路 | 体现工程思维 |

| 子查询别名 | 派生表必须有别名 | MySQL 强制要求 |

| 日期用范围 | >= AND < 别用 BETWEEN | BETWEEN 两边都含,时间点易漏 |

| LIMIT 必带 | 大查询别忘 LIMIT | 防全表拉爆内存 |

| 字符串用单引号 | 'abc' 双引号在某些模式报错 | 单引号是标准 |

| 表名字段名反引号 | 8.0 可省,关键字列要加 | 避免和保留字冲突 |

面试白板上写 SQL 时,哪怕需求再简单,也养成"先 SELECT 需要的列、WHERE 写清楚、最后 LIMIT"的习惯,面试官会觉得你是写过生产 SQL 的人。

12 注意事项与常见坑

| 常见坑 | 错误做法 | 正确认知 |

|---|---|---|

| SELECT * | 图省事写全列 | 多查无用列,回表放大,网络传输浪费 |

| 忘写 WHERE | 直接 UPDATE/DELETE | 全表操作,生产事故 |

| 隐式类型转换 | varchar 列用数字查 | 索引失效,全表扫 |

| 函数包裹索引列 | WHERE DATE(col)=.. | 索引失效,改范围 |

| 深分页不优化 | LIMIT 大偏移 | 延迟关联或记住上次 id |

| COUNT(*) 滥用 | 大表实时 count | 缓存估算值 |

| OR 乱用 | 以为 OR 走索引 | 可能退化为全表 |

| 子查询 SELECT * | 不写清楚列 | 性能差且易出错 |

| NOT IN 含 NULL | 不处理 NULL | 结果全空,改 NOT EXISTS |

| GROUP BY 列不全 | SELECT 非分组列 | 8.0 默认报错,要 ONLY_FULL_GROUP_BY |

13 自测练习

自测 1:LIMIT 100000, 10 为什么慢?怎么优化?

<details>

<summary>参考答案</summary>

MySQL 要扫描前 100010 行,再丢弃前 100000 行,只返回 10 行。扫描量大。优化:用延迟关联,先在覆盖索引上 SELECT id FROM t LIMIT 100000,10 拿到 10 个主键,再回表;或者前端记住上一页最大 id,用 WHERE id > ? LIMIT 10。

</details>

自测 2:WHERE 和 HAVING 的区别?

<details>

<summary>参考答案</summary>

WHERE 在分组前过滤行,不能用聚合函数;HAVING 在分组后过滤组,可以用聚合函数。性能上 WHERE 先过滤减少分组数据量更好。

</details>

自测 3:EXISTS 和 IN 哪个快?

<details>

<summary>参考答案</summary>

没有绝对答案,取决于数据量。外表大、子查询结果小用 IN;外表小、子查询大用 EXISTS,因为 EXISTS 匹配到即停。MySQL 8.0 优化器很多场景两者计划等价,写 EXISTS 语义更清晰。

</details>

自测 4:WHERE create_time >= '2024-01-01' 和 WHERE DATE(create_time)='2024-01-01' 哪个好?

<details>

<summary>参考答案</summary>

前者好。后者对列套了函数,索引失效,变成全表扫。前者是范围条件,能走 create_time 上的索引。

</details>

自测 5:怎么写"插入或更新"?

<details>

<summary>参考答案</summary>

用 INSERT ... ON DUPLICATE KEY UPDATE,依赖唯一索引冲突触发更新。比先 SELECT 再判断插入或更新省一次往返,且原子。

</details>

自测 6:NOT IN 为什么有坑?

<details>

<summary>参考答案</summary>

如果子查询结果里有任何一个 NULL,NOT IN 的整体结果就是空集,因为 NULL = 值 结果是 NULL 不是 TRUE,MySQL 会认为"没有任何行满足"。正确做法是改写成 NOT EXISTS,或者在子查询里过滤掉 NULL。

</details>

自测 7:怎么行列转换?

<details>

<summary>参考答案</summary>

用 SUM(CASE WHEN 条件 THEN 列 END) 把行转成列。比如按月统计各产品销量:SUM(CASE WHEN month='1月' THEN sales END) 作为"1月"列,GROUP BY 产品即可。

</details>

自测 8:GROUP_CONCAT 有什么坑?

<details>

<summary>参考答案</summary>

它有长度限制(group_concat_max_len,默认 1024 字节),超长会被截断。需要拼很长字符串时要先调大这个参数。另外它会按逗号拼接,要注意转义分隔符。

</details>

自测 9:WHERE 里为什么不能写聚合函数?

<details>

<summary>参考答案</summary>

MySQL 子句执行顺序是 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。WHERE 执行时还没 GROUP BY,没法用聚合函数。需要在分组后过滤就用 HAVING。

</details>

练习 1:手写一条 SQL,查询每个部门工资第二高的员工姓名(并列算同名次)。

练习 2:手写一条 SQL,统计每个用户最近 30 天的下单笔数和总金额,只展示下单超过 5 笔的用户。

练习 3:说明为什么生产环境执行 UPDATE 前要先 BEGIN 开事务并 SELECT 确认。

本篇覆盖了面试手写题 90% 的题型。建议遮住"推荐写法"列,看着场景自己写一遍,写不出来的题型就是你要重点练的。把连续登录、TopN、行列转换这三道经典题练到肌肉记忆,手写题基本稳了。

13.1 SQL 写法速查补充

| 场景 | 写法 |

|---|---|

| 去重计数 | COUNT(DISTINCT col) |

| 空值排最后 | ORDER BY col IS NULL |

| 多行合并 | GROUP_CONCAT |

| 行列转换 | SUM(CASE WHEN) |

| 连续登录 | 日期减行号 |

| TopN | ROW_NUMBER |

| 占比 | COUNT / SUM OVER |

| 同比 | LAG() |

13.2 SQL 写法补充

| 场景 | 写法 |

|---|---|

| 去重 | DISTINCT |

| 排序 | ORDER BY |

| 分组 | GROUP BY |

| 过滤组 | HAVING |

| 连接 | JOIN |

| 子查询 | EXISTS |

| 更新 | UPDATE |

| 删除 | DELETE |

13.3 收尾

本篇覆盖了 SQL 手写题的高频题型,建议多练。

下一篇预告:第 83 章 面试名词解释100条

— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 82 章 速查卡:SQL高频写法》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。