MySQL高级查询 #
一、子查询 #
1.1 子查询概念 #
子查询是嵌套在另一个查询中的查询,可以出现在SELECT、FROM、WHERE、HAVING子句中。
sql
-- 子查询示例
SELECT * FROM users
WHERE age > (SELECT AVG(age) FROM users);
1.2 标量子查询 #
sql
-- 返回单个值的子查询
SELECT
name,
age,
(SELECT AVG(age) FROM users) AS avg_age,
age - (SELECT AVG(age) FROM users) AS age_diff
FROM users;
-- 在WHERE中使用
SELECT * FROM users
WHERE age > (SELECT AVG(age) FROM users);
-- 在HAVING中使用
SELECT status, AVG(age) AS avg_age
FROM users
GROUP BY status
HAVING AVG(age) > (SELECT AVG(age) FROM users);
1.3 列子查询 #
sql
-- 返回一列值的子查询
SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders WHERE status = 'completed');
-- 使用NOT IN
SELECT * FROM users
WHERE id NOT IN (SELECT user_id FROM orders);
1.4 行子查询 #
sql
-- 返回一行多列
SELECT * FROM users
WHERE (name, age) = (SELECT name, age FROM users WHERE id = 1);
1.5 表子查询 #
sql
-- 返回多行多列,用作临时表
SELECT u.name, o.total
FROM (
SELECT user_id, SUM(amount) AS total
FROM orders
GROUP BY user_id
) AS o
JOIN users u ON o.user_id = u.id;
1.6 EXISTS子查询 #
sql
-- EXISTS:存在则返回true
SELECT * FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
);
-- NOT EXISTS:不存在则返回true
SELECT * FROM users u
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
);
1.7 相关子查询 #
sql
-- 相关子查询:子查询引用外部查询的列
SELECT
name,
age,
(SELECT COUNT(*) FROM users u2 WHERE u2.age > u1.age) AS rank
FROM users u1
ORDER BY rank;
-- 查找每个部门薪资最高的员工
SELECT e.name, e.salary, e.department_id
FROM employees e
WHERE e.salary = (
SELECT MAX(salary)
FROM employees e2
WHERE e2.department_id = e.department_id
);
二、联合查询 #
2.1 UNION #
sql
-- UNION:合并结果集,自动去重
SELECT name FROM users WHERE status = 1
UNION
SELECT name FROM admins WHERE status = 1;
-- 合并不同表的数据
SELECT id, name, 'user' AS type FROM users
UNION
SELECT id, name, 'admin' AS type FROM admins;
2.2 UNION ALL #
sql
-- UNION ALL:合并结果集,保留重复
SELECT name FROM users WHERE status = 1
UNION ALL
SELECT name FROM admins WHERE status = 1;
2.3 UNION与UNION ALL区别 #
| 特性 | UNION | UNION ALL |
|---|---|---|
| 去重 | 是 | 否 |
| 性能 | 较慢 | 较快 |
| 使用场景 | 需要去重 | 不需要去重 |
2.4 INTERSECT和EXCEPT(MySQL 8.0+) #
sql
-- INTERSECT:交集(MySQL 8.0.31+)
SELECT name FROM users
INTERSECT
SELECT name FROM admins;
-- EXCEPT:差集(MySQL 8.0.31+)
SELECT name FROM users
EXCEPT
SELECT name FROM admins;
三、公共表表达式(CTE) #
3.1 基本CTE #
sql
-- WITH子句定义CTE
WITH user_orders AS (
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
)
SELECT u.name, uo.order_count
FROM users u
JOIN user_orders uo ON u.id = uo.user_id;
3.2 多个CTE #
sql
WITH
active_users AS (
SELECT * FROM users WHERE status = 1
),
user_orders AS (
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
)
SELECT au.name, uo.order_count
FROM active_users au
LEFT JOIN user_orders uo ON au.id = uo.user_id;
3.3 递归CTE #
sql
-- 递归查询组织架构
WITH RECURSIVE org_tree AS (
-- 基础查询:顶级员工
SELECT id, name, manager_id, 1 AS level
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- 递归查询:下属员工
SELECT e.id, e.name, e.manager_id, ot.level + 1
FROM employees e
JOIN org_tree ot ON e.manager_id = ot.id
)
SELECT * FROM org_tree ORDER BY level;
-- 递归查询分类树
WITH RECURSIVE category_tree AS (
SELECT id, name, parent_id, CAST(name AS CHAR(1000)) AS path
FROM categories
WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.name, c.parent_id, CONCAT(ct.path, ' > ', c.name)
FROM categories c
JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT * FROM category_tree;
四、窗口函数 #
4.1 窗口函数语法 #
sql
function_name([arguments]) OVER (
[PARTITION BY partition_expression]
[ORDER BY order_expression [ASC|DESC]]
[frame_clause]
)
4.2 ROW_NUMBER #
sql
-- 行号
SELECT
name,
age,
ROW_NUMBER() OVER (ORDER BY age DESC) AS row_num
FROM users;
-- 分组行号
SELECT
name,
department_id,
salary,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rank_in_dept
FROM employees;
4.3 RANK和DENSE_RANK #
sql
-- RANK:相同值排名相同,跳过后续排名
SELECT
name,
score,
RANK() OVER (ORDER BY score DESC) AS rank,
DENSE_RANK() OVER (ORDER BY score DESC) AS dense_rank
FROM students;
-- 示例结果:
-- name score rank dense_rank
-- ---- ----- ---- ----------
-- Alice 100 1 1
-- Bob 100 1 1
-- Carol 90 3 2
-- Dave 80 4 3
4.4 聚合窗口函数 #
sql
-- 累计求和
SELECT
order_date,
amount,
SUM(amount) OVER (ORDER BY order_date) AS running_total
FROM orders;
-- 移动平均
SELECT
order_date,
amount,
AVG(amount) OVER (
ORDER BY order_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg
FROM orders;
-- 分组聚合
SELECT
name,
department_id,
salary,
AVG(salary) OVER (PARTITION BY department_id) AS dept_avg_salary
FROM employees;
4.5 LEAD和LAG #
sql
-- LAG:获取前N行的值
-- LEAD:获取后N行的值
SELECT
order_date,
amount,
LAG(amount, 1) OVER (ORDER BY order_date) AS prev_amount,
LEAD(amount, 1) OVER (ORDER BY order_date) AS next_amount,
amount - LAG(amount, 1) OVER (ORDER BY order_date) AS diff
FROM orders;
4.6 FIRST_VALUE和LAST_VALUE #
sql
SELECT
name,
department_id,
salary,
FIRST_VALUE(salary) OVER (PARTITION BY department_id ORDER BY salary DESC) AS max_salary,
LAST_VALUE(salary) OVER (
PARTITION BY department_id
ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS min_salary
FROM employees;
4.7 NTILE #
sql
-- 将数据分成N组
SELECT
name,
score,
NTILE(4) OVER (ORDER BY score DESC) AS quartile
FROM students;
五、CASE表达式 #
5.1 简单CASE #
sql
SELECT
name,
status,
CASE status
WHEN 1 THEN 'Active'
WHEN 0 THEN 'Inactive'
ELSE 'Unknown'
END AS status_text
FROM users;
5.2 搜索CASE #
sql
SELECT
name,
score,
CASE
WHEN score >= 90 THEN 'A'
WHEN score >= 80 THEN 'B'
WHEN score >= 70 THEN 'C'
WHEN score >= 60 THEN 'D'
ELSE 'F'
END AS grade
FROM students;
5.3 CASE在聚合中使用 #
sql
-- 条件计数
SELECT
COUNT(*) AS total,
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS active_count,
SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) AS inactive_count
FROM users;
-- 条件求和
SELECT
SUM(CASE WHEN category = 'A' THEN amount ELSE 0 END) AS total_a,
SUM(CASE WHEN category = 'B' THEN amount ELSE 0 END) AS total_b
FROM orders;
六、复杂查询示例 #
6.1 分组排名 #
sql
-- 每个部门薪资前3名
WITH ranked AS (
SELECT
name,
department_id,
salary,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rank
FROM employees
)
SELECT * FROM ranked WHERE rank <= 3;
6.2 同比环比计算 #
sql
-- 月度销售额及环比
WITH monthly_sales AS (
SELECT
DATE_FORMAT(order_date, '%Y-%m') AS month,
SUM(amount) AS total
FROM orders
GROUP BY month
)
SELECT
month,
total,
LAG(total, 1) OVER (ORDER BY month) AS prev_month,
(total - LAG(total, 1) OVER (ORDER BY month)) / LAG(total, 1) OVER (ORDER BY month) * 100 AS growth_rate
FROM monthly_sales;
6.3 连续登录问题 #
sql
-- 查找连续登录3天以上的用户
WITH login_dates AS (
SELECT DISTINCT
user_id,
login_date,
login_date - INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY AS group_date
FROM user_logins
),
consecutive_groups AS (
SELECT
user_id,
group_date,
COUNT(*) AS consecutive_days
FROM login_dates
GROUP BY user_id, group_date
)
SELECT user_id, MAX(consecutive_days) AS max_consecutive_days
FROM consecutive_groups
GROUP BY user_id
HAVING max_consecutive_days >= 3;
七、总结 #
高级查询要点:
| 技术 | 说明 |
|---|---|
| 子查询 | 嵌套查询,用于复杂条件 |
| UNION | 合并结果集 |
| CTE | 临时命名结果集,提高可读性 |
| 窗口函数 | 不减少行的聚合计算 |
| CASE | 条件表达式 |
最佳实践:
- 复杂查询使用CTE提高可读性
- 合理使用窗口函数避免自连接
- 注意子查询性能,优先使用JOIN
- UNION ALL比UNION性能更好
下一步,让我们学习多表查询!
最后更新:2026-03-26