UPDATE orders SET status =1WHERE id =5; -- 一定要带 WHERE! DELETEFROM orders WHERE status =2; -- 一定要带 WHERE!
重要:
UPDATE/DELETE 不带 WHERE = 全表操作,生产事故源头。建议执行前先 SELECT 确认范围。
批量更新用 INSERT ... ON DUPLICATE KEY UPDATE(MySQL)或 UPSERT(PG ON CONFLICT)。
软删除:加 deleted_at 字段做逻辑删除,而非 DELETE,便于审计和恢复。
四、SELECT:查询基础
4.1 基本结构与执行顺序
1 2 3 4 5 6 7
SELECT 字段 FROM 表 WHERE 行过滤条件 GROUPBY 分组列 HAVING 分组后过滤 ORDERBY 排序列 LIMIT 限制行数;
SQL 书写顺序 vs 引擎执行顺序(面试必考):
1 2
书写: SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT 执行: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
因为先有表、再过滤、再分组、再过滤组、再选列、最后排序取前 N。理解这点就能解释"为什么 WHERE 里不能用 SELECT 的别名,而 ORDER BY 可以"。
4.2 WHERE 条件与运算符
1 2 3 4 5 6
SELECT*FROM orders WHERE status =1 AND amount BETWEEN100AND1000 AND user_id IN (1001,1002,1003) AND created_at >='2026-07-01' AND remark LIKE'%加急%'; -- % 任意多字符, _ 单字符
索引陷阱:对索引列用函数/运算会让索引失效:
1 2 3 4
WHEREDATE(created_at) ='2026-07-17'-- 索引失效!改用范围: WHERE created_at >='2026-07-17'AND created_at <'2026-07-18' WHERELEFT(name,3)='abc'-- 失效,改用 name LIKE 'abc%' WHERE amount +1=100-- 失效,改用 amount = 99
4.3 排序、分页与去重
1 2 3 4
SELECTDISTINCT user_id FROM orders WHERE status =1;
-- 子查询(相关子查询,慢) SELECT*FROM orders o WHEREEXISTS (SELECT1FROM users u WHERE u.id=o.user_id AND u.vip=1);
-- 等价 JOIN(通常更快) SELECT o.*FROM orders o JOIN users u ON o.user_id=u.id WHERE u.vip=1;
IN 子查询大结果集时 MySQL 可能性能差,可用 JOIN 或 EXISTS 替代。
MySQL 8 起 IN 子查询会自动优化为 semi-join。
六、聚合与分组
1 2 3 4 5 6 7 8 9 10 11 12
-- 每个用户的订单数和总金额,只看下单≥3次的 SELECT user_id, COUNT(*) AS order_cnt, SUM(amount) AS total, AVG(amount) AS avg_amt, MAX(amount) AS max_amt, MIN(amount) AS min_amt FROM orders WHERE status =1 GROUPBY user_id HAVINGCOUNT(*) >=3 ORDERBY total DESC;
要点:
WHERE 在分组前过滤行,HAVING 在分组后过滤组。
SELECT 出现的非聚合列必须在 GROUP BY 里(MySQL 默认 ONLY_FULL_GROUP_BY 严格模式),否则报错。
COUNT(*) 含 NULL 行,COUNT(列) 不计 NULL 行——区别常考。
SUM/AVG 忽略 NULL;AVG = SUM/COUNT(非NULL)。
常用聚合 + 条件聚合(pivot 式)
1 2 3 4 5 6
-- 按状态统计订单数,一行展示 SELECT COUNT(*) AS total, SUM(status=1) AS paid_cnt, -- MySQL 布尔转0/1 SUM(status=0) AS unpaid_cnt FROM orders;
七、窗口函数(面试加分项,MySQL 8.0+)
窗口函数 = 聚合 + 保留明细行,不像 GROUP BY 会折叠行。语法:
1 2 3 4 5
函数() OVER ( PARTITIONBY 分组列 ORDERBY 排序列 ROWS/RANGEBETWEEN 帧起 AND 帧止 )
-- 1. 每个用户最近3笔订单(分组 Top N) SELECT*FROM ( SELECT o.*, ROW_NUMBER() OVER (PARTITIONBY user_id ORDERBY created_at DESC) AS rn FROM orders o ) t WHERE rn <=3;
-- 2. 每个用户按时间累加金额(滚动求和) SELECT user_id, created_at, amount, SUM(amount) OVER (PARTITIONBY user_id ORDERBY created_at) AS cum_amount FROM orders;
-- 3. 用户内按金额排名 SELECT user_id, amount, DENSE_RANK() OVER (PARTITIONBY user_id ORDERBY amount DESC) AS rk FROM orders;
-- 4. 同环比:用 LAG 取上一行 SELECT user_id, dt, daily_amt, daily_amt -LAG(daily_amt) OVER (PARTITIONBY user_id ORDERBY dt) AS diff FROM daily_stats;
窗口函数是面试 SQL 的分水岭。GROUP BY 求分组聚合谁都会,窗口函数求"分组内 TopN / 滚动 / 排名"才是区分度。
八、集合操作:UNION / INTERSECT / EXCEPT
1 2 3 4 5 6 7 8
-- UNION 去重,UNION ALL 不去重(更快,常用) SELECT name FROM vip_users UNIONALL SELECT name FROM normal_users;
-- MySQL 无 INTERSECT/EXCEPT,用 JOIN / NOT IN 替代 SELECT id FROM A WHERE id IN (SELECT id FROM B); -- 交集 SELECT id FROM A WHERE id NOTIN (SELECT id FROM B); -- 差集
UNION 列数与类型必须一致。
去重要排序去重,开销大,确认无重复时优先 UNION ALL。
九、CASE 表达式
1 2 3 4 5 6 7 8
SELECT user_id, CASE WHENSUM(amount) >=10000THEN'钻石' WHENSUM(amount) >=1000THEN'黄金' ELSE'普通' ENDAS level FROM orders GROUPBY user_id;
CASE 可在 SELECT / WHERE / ORDER BY / GROUP BY 任意位置,灵活做分类/透传。
十、进阶:CTE 与递归
10.1 CTE(公用表表达式,with 子句)
1 2 3 4
WITH vip_orders AS ( SELECT o.*FROM orders o JOIN users u ON o.user_id=u.id WHERE u.vip=1 ) SELECT user_id, COUNT(*) FROM vip_orders GROUPBY user_id;
可读性好,可多次引用,MySQL 8.0+ 支持。
物化 CTE(PG MATERIALIZED)可做性能优化。
10.2 递归 CTE(树/层级查询)
1 2 3 4 5 6 7 8
-- 查所有下属(组织树) WITHRECURSIVE sub AS ( SELECT id, name, parent_id FROM org WHERE id =1-- 锚点: 起点 UNIONALL SELECT o.id, o.name, o.parent_id FROM org o JOIN sub s ON o.parent_id = s.id -- 递归: 找下级 ) SELECT*FROM sub;
SELECTDISTINCT salary FROM employee ORDERBY salary DESC LIMIT 1OFFSET N-1; -- 窗口函数版,处理 null 友好 SELECT salary FROM ( SELECT salary, DENSE_RANK() OVER (ORDERBY salary DESC) rk FROM employee ) t WHERE rk = N;
Q2:连续登录 N 天的用户
1 2 3 4 5 6 7 8
SELECT user_id FROM ( SELECT user_id, login_date, DATE_SUB(login_date, INTERVALROW_NUMBER() OVER (PARTITIONBY user_id ORDERBY login_date) DAY) AS grp FROM (SELECTDISTINCT user_id, DATE(login_time) login_date FROM logins) a ) b GROUPBY user_id, grp HAVINGCOUNT(*) >= N;
思路:用 日期 - 行号 把连续日期归到同一组(差值相同 = 连续)。
Q3:各部门薪水前三名(分组 TopN)
1 2 3 4 5
SELECT*FROM ( SELECT dept_id, name, salary, DENSE_RANK() OVER (PARTITIONBY dept_id ORDERBY salary DESC) rk FROM employee ) t WHERE rk <=3;
Q4:找出工资比经理高的员工
1 2 3 4 5
-- 自连接 SELECT e.name FROM employee e JOIN employee m ON e.manager_id = m.id WHERE e.salary > m.salary;
Q5:行转列(pivot)
1 2 3 4 5
SELECT user_id, SUM(CASEWHENmonth=1THEN amount ELSE0END) AS m1, SUM(CASEWHENmonth=2THEN amount ELSE0END) AS m2 FROM monthly_amount GROUPBY user_id;
Q6:删重复行,保留 id 最小
1 2 3 4 5 6
DELETEFROM orders WHERE id NOTIN ( SELECT min_id FROM ( SELECTMIN(id) min_id FROM orders GROUPBY user_id, created_at ) t );
(MySQL 不能直接在 DELETE 里嵌套查同表,要再包一层子查询。)
Q7:列转行(unpivot)
1 2 3
SELECT user_id, 'm1'ASmonth, m1 AS amount FROM t UNIONALL SELECT user_id, 'm2', m2 FROM t;
十四、学习路径建议
第 1 周:DDL/DML/SELECT 基础 + WHERE/ORDER/LIMIT,在本地 MySQL 建几张大表练手。
执行顺序: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT 索引最左前缀: 联合索引(a,b,c) 必须从 a 开始,范围后失效 覆盖索引: 查询列都在索引里 → Using index 不回表 窗口函数: 函数() OVER(PARTITION BY .. ORDER BY ..) 深分页: 用游标 WHERE id>last_id 或延迟关联 聚合含NULL: COUNT(*) 计 NULL, COUNT(列) 不计; SUM/AVG 忽略 NULL 索引失效: 函数/运算列、隐式类型转换、%左模糊、OR两边缺索引、!= UNION ALL 优于 UNION(不去重更快) TRUNCATE 不可回滚(DDL), DELETE 可回滚(DML) MySQL 默认隔离级别 RR,靠 MVCC+Next-Key Lock 防幻读