-- 找出访问了但没有下单的用户 SELECT v.customer_id, COUNT(*) AS count_no_trans FROM Visits v LEFTJOIN Transactions t ON v.visit_id = t.visit_id WHERE t.transaction_id ISNULL GROUPBY v.customer_id;
-- 统计每个学生每科的考试次数 SELECT s.student_id, s.student_name, sub.subject_name, COUNT(e.subject_name) AS attended_exams FROM Students s CROSSJOIN Subjects sub LEFTJOIN Examinations e ON s.student_id = e.student_id AND sub.subject_name = e.subject_name GROUPBY s.student_id, s.student_name, sub.subject_name ORDERBY s.student_id, sub.subject_name;
-- 每个产品的平均售价(按销量加权) SELECT p.product_id, IFNULL(ROUND(SUM(p.price * u.units) /SUM(u.units), 2), 0) AS average_price FROM Prices p LEFTJOIN UnitsSold u ON p.product_id = u.product_id AND u.purchase_date BETWEEN p.start_date AND p.end_date GROUPBY p.product_id;
-- 按月统计各类型交易数量和金额 SELECT DATE_FORMAT(trans_date, '%Y-%m') ASmonth, country, COUNT(*) AS trans_count, SUM(CASEWHEN state ='approved'THEN1ELSE0END) AS approved_count, SUM(amount) AS trans_total_amount, SUM(CASEWHEN state ='approved'THEN amount ELSE0END) AS approved_total_amount FROM Transactions GROUPBY DATE_FORMAT(trans_date, '%Y-%m'), country;
-- 即时配送率(首单是否即时配送) WITH first_order AS ( SELECT customer_id, MIN(order_date) AS first_date FROM Delivery GROUPBY customer_id ) SELECT ROUND( SUM(CASEWHEN d.order_date = d.customer_pref_delivery_date THEN1ELSE0END) /COUNT(*) *100, 2 ) AS immediate_percentage FROM Delivery d JOIN first_order f ON d.customer_id = f.customer_id AND d.order_date = f.first_date;
-- 每个部门薪资前三(DENSE_RANK 不跳号) SELECT d.name AS Department, e.name AS Employee, e.salary AS Salary FROM ( SELECT name, salary, departmentId, DENSE_RANK() OVER (PARTITIONBY departmentId ORDERBY salary DESC) AS dr FROM Employee ) e JOIN Department d ON e.departmentId = d.id WHERE e.dr <=3;
-- 找出连续出现至少3次的数字 WITH cte AS ( SELECT num, LEAD(num, 1) OVER (ORDERBY id) AS next1, LEAD(num, 2) OVER (ORDERBY id) AS next2 FROM Logs ) SELECTDISTINCT num AS ConsecutiveNums FROM cte WHERE num = next1 AND num = next2;
-- 查询 2019-08-16 时每个产品的价格(变更前的最新价格) SELECT product_id, new_price AS price FROM Products WHERE (product_id, change_date) IN ( SELECT product_id, MAX(change_date) FROM Products WHERE change_date <='2019-08-16' GROUPBY product_id ) UNION SELECT product_id, 10AS price FROM Products WHERE product_id NOTIN ( SELECT product_id FROM Products WHERE change_date <='2019-08-16' );
-- 最后一个能上车不超重的人(累计体重) WITH cum AS ( SELECT person_id, person_name, weight, SUM(weight) OVER (ORDERBY turn) AS cum_weight FROM Queue ) SELECT person_name FROM cum WHERE cum_weight <=1000 ORDERBY cum_weight DESC LIMIT 1;
-- 统计低收入、平均收入、高收入各有多少人 SELECT'Low Salary'AS category, COUNT(*) AS accounts_count FROM Accounts WHERE income <20000 UNIONALL SELECT'Average Salary', COUNT(*) FROM Accounts WHERE income BETWEEN20000AND50000 UNIONALL SELECT'High Salary', COUNT(*) FROM Accounts WHERE income >50000;
技巧:UNION ALL 比 UNION 快(不去重),固定分类用 UNION ALL 拼接更清晰。
MOD 奇偶行交换(CASE WHEN 精妙用法)
1 2 3 4 5 6 7 8 9 10
-- 奇数行与下一行的学生座位互换 SELECT CASE WHEN id %2=1AND id = (SELECTCOUNT(*) FROM Seat) THEN id WHEN id %2=1THEN id +1 ELSE id -1 ENDAS id, student FROM Seat ORDERBY id;
-- 拥有最多好友的用户(好友关系是双向的) SELECT id, COUNT(*) AS num FROM ( SELECT requester_id AS id FROM RequestAccepted UNIONALL SELECT accepter_id AS id FROM RequestAccepted ) t GROUPBY id ORDERBY num DESC LIMIT 1;
-- 找出 2021 年在不同城市有相同总投保金额的投保人 SELECT id, tiv_2021 FROM Insurance WHERE tiv_2020 IN ( SELECT tiv_2020 FROM Insurance GROUPBY tiv_2020 HAVINGCOUNT(*) >1 ) AND (lat, lon) IN ( SELECT lat, lon FROM Insurance GROUPBY lat, lon HAVINGCOUNT(*) =1 );
-- 第二高的薪资(不存在则返回 NULL) SELECT ( SELECTDISTINCT salary FROM Employee ORDERBY salary DESC LIMIT 1OFFSET1 ) AS SecondHighestSalary;
更通用:支持第 N 高
1 2 3 4 5 6 7 8 9 10 11 12
CREATEFUNCTION getNthHighestSalary(N INT) RETURNSINT BEGIN RETURN ( SELECT salary FROM ( SELECT salary, DENSE_RANK() OVER (ORDERBY salary DESC) AS dr FROM Employee ) t WHERE dr = N LIMIT 1 ); END
WITH step1 AS ( SELECT player_id, MIN(event_date) AS first_login FROM Activity GROUPBY player_id ), step2 AS ( SELECT s.player_id FROM step1 s JOIN Activity a ON s.player_id = a.player_id AND a.event_date = DATE_ADD(s.first_login, INTERVAL1DAY) ) SELECT ROUND(COUNT(*) / (SELECTCOUNT(DISTINCT player_id) FROM Activity), 2) AS fraction FROM step2;
EXISTS vs IN 的选择
1 2 3 4 5 6 7 8 9
-- NOT EXISTS(当子查询结果可能含 NULL 时,比 NOT IN 更安全) SELECT employee_id FROM Employees e WHERENOTEXISTS ( SELECT1FROM Managers m WHERE m.id = e.manager_id );
-- NOT IN 的陷阱:若子查询结果含 NULL,整个 NOT IN 结果为空! -- WHERE manager_id NOT IN (SELECT id FROM Managers) -- 危险!
窗口函数嵌套在子查询中使用
窗口函数不能直接出现在 WHERE 中,必须套一层子查询再过滤。
1 2 3 4 5 6 7 8 9
-- 在子查询中用窗口函数,外层再过滤 SELECT* FROM ( SELECT *, ROW_NUMBER() OVER (PARTITIONBY dept ORDERBY salary DESC) AS rn FROM Employee ) t WHERE rn =1;
本类速查
类型
写法
使用场景
标量子查询
SELECT (SELECT ...)
返回单个值
IN 子查询
WHERE col IN (SELECT ...)
匹配值列表
EXISTS 子查询
WHERE EXISTS (SELECT 1 ...)
检查是否存在
FROM 子查询
FROM (SELECT ...) t
派生表
CTE
WITH t AS (SELECT ...)
复杂多步查询(推荐)
Advanced String Functions 字符串与正则
涉及题目:1667、1527、196、1484、1327、1517
大小写规范化(首字母大写)
1 2 3 4 5 6
-- 修复姓名格式:首字母大写,其余小写 SELECT user_id, CONCAT(UPPER(LEFT(name, 1)), LOWER(SUBSTRING(name, 2))) AS name FROM Users ORDERBY user_id;
-- 正则 REGEXP '正则表达式' REGEXP_REPLACE(str, pattern, replacement) -- MySQL 8.0+
核心解题模板汇总
模板:分组 TopN
1 2 3 4 5 6 7 8
SELECT* FROM ( SELECT *, DENSE_RANK() OVER (PARTITIONBY 分组列 ORDERBY 排序列 DESC) AS dr FROM 表名 ) t WHERE dr <= N;
模板:LEFT JOIN + IS NULL(找”没有”的数据)
1 2 3 4
SELECT a.id FROM 主表 a LEFTJOIN 关联表 b ON a.id = b.aid AND [其他条件] WHERE b.id ISNULL;
模板:连续问题(日期 - 行号 = 常数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
WITH dedup AS ( SELECTDISTINCT user_id, event_date FROM 表名 ), grouped AS ( SELECT user_id, event_date, DATE_SUB(event_date, INTERVALROW_NUMBER() OVER (PARTITIONBY user_id ORDERBY event_date) DAY ) AS grp FROM dedup ) SELECT user_id, COUNT(*) AS streak FROM grouped GROUPBY user_id, grp HAVING streak >= N;
模板:留存率
1 2 3 4 5 6
SELECT ROUND(COUNT(DISTINCT b.user_id) /COUNT(DISTINCT a.user_id), 2) AS rate FROM 首次行为表 a LEFTJOIN 行为表 b ON a.user_id = b.user_id AND b.event_date = DATE_ADD(a.first_date, INTERVAL N DAY);
模板:条件聚合(行列转换核心)
1 2 3 4 5 6 7
SELECT group_col, SUM(CASEWHEN type ='A'THENvalueELSE0END) AS type_a, SUM(CASEWHEN type ='B'THENvalueELSE0END) AS type_b, COUNT(CASEWHEN type ='C'THEN1END) AS type_c_cnt FROM 表名 GROUPBY group_col;
模板:滑动窗口聚合
1 2 3 4 5
SELECT dt, SUM(value) OVER (ORDERBY dt ROWSBETWEEN6 PRECEDING ANDCURRENTROW) AS sum_7day, AVG(value) OVER (ORDERBY dt ROWSBETWEEN6 PRECEDING ANDCURRENTROW) AS avg_7day FROM 日汇总表;
高频易错点速查
易错点
错误写法
正确写法
NULL 比较
col = NULL
col IS NULL
NOT IN 含 NULL
NOT IN (子查询含NULL)
改用 NOT EXISTS
删除重复行
DELETE ... WHERE id NOT IN (SELECT MIN(id) FROM t ...)
多套一层子查询绕过 MySQL 限制
字符长度(中文)
LENGTH(name)
CHAR_LENGTH(name)
日期差(跨月)
date1 - date2
DATEDIFF(date1, date2)
精确保留小数
直接返回浮点
ROUND(val, 2)
UNION 去重慢
UNION
不需要去重时用 UNION ALL
窗口函数 WHERE
WHERE RANK() <= 3
外层套子查询再 WHERE dr <= 3
深分页
LIMIT 100000, 10
WHERE id > 100000 LIMIT 10
LeetCode SQL 50 题目索引
#
题号
题目
类别
核心考点
1
1757
Recyclable and Low Fat Products
Select
WHERE 多条件
2
584
Find Customer Referee
Select
IS NULL 判断
3
595
Big Countries
Select
OR 条件
4
1148
Article Views I
Select
DISTINCT
5
1683
Invalid Tweets
Select
CHAR_LENGTH
6
1378
Replace Employee ID With The Unique Identifier
Basic Joins
LEFT JOIN
7
1068
Product Sales Analysis I
Basic Joins
INNER JOIN
8
1581
Customer Who Visited but Did Not Make Any Transactions