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 条件表达式

最佳实践:

  1. 复杂查询使用CTE提高可读性
  2. 合理使用窗口函数避免自连接
  3. 注意子查询性能,优先使用JOIN
  4. UNION ALL比UNION性能更好

下一步,让我们学习多表查询!

最后更新:2026-03-26