MariaDB CTE递归查询 #
一、CTE概述 #
1.1 什么是CTE #
text
CTE(Common Table Expression)
├── 公用表表达式
├── 临时结果集
├── 只在当前查询有效
├── 提高可读性
└── 支持递归
1.2 CTE优势 #
| 优势 | 说明 |
|---|---|
| 可读性 | 复杂查询更清晰 |
| 模块化 | 分步处理逻辑 |
| 递归 | 处理层次数据 |
| 性能 | 可能优化执行计划 |
二、基本CTE #
2.1 基本语法 #
sql
WITH cte_name AS (
SELECT ...
)
SELECT ... FROM cte_name;
2.2 简单示例 #
sql
-- 使用CTE
WITH active_users AS (
SELECT id, name, email FROM users WHERE status = 1
)
SELECT * FROM active_users WHERE name LIKE 'J%';
-- 等价于子查询
SELECT * FROM (
SELECT id, name, email FROM users WHERE status = 1
) AS active_users WHERE name LIKE 'J%';
2.3 多个CTE #
sql
-- 定义多个CTE
WITH
active_users AS (
SELECT id, name FROM users WHERE status = 1
),
high_value_orders AS (
SELECT user_id, SUM(amount) AS total
FROM orders
GROUP BY user_id
HAVING total > 1000
)
SELECT
au.name,
hvo.total
FROM active_users au
JOIN high_value_orders hvo ON au.id = hvo.user_id;
2.4 CTE复用 #
sql
-- CTE可以在同一查询中多次引用
WITH user_stats AS (
SELECT
user_id,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
GROUP BY user_id
)
SELECT
u.name,
us1.order_count,
us2.total_amount
FROM users u
JOIN user_stats us1 ON u.id = us1.user_id
JOIN user_stats us2 ON u.id = us2.user_id
WHERE us1.order_count > 5 AND us2.total_amount > 1000;
三、递归CTE #
3.1 递归语法 #
sql
WITH RECURSIVE cte_name AS (
-- 基础查询(锚点)
SELECT ...
UNION ALL
-- 递归查询
SELECT ... FROM cte_name WHERE ...
)
SELECT ... FROM cte_name;
3.2 生成序列 #
sql
-- 生成1到10的序列
WITH RECURSIVE numbers AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM numbers WHERE n < 10
)
SELECT * FROM numbers;
+----+
| n |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
+----+
3.3 计算阶乘 #
sql
-- 计算阶乘
WITH RECURSIVE factorial AS (
SELECT 1 AS n, 1 AS fact
UNION ALL
SELECT n + 1, fact * (n + 1) FROM factorial WHERE n < 5
)
SELECT * FROM factorial;
+---+------+
| n | fact |
+---+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 6 |
| 4 | 24 |
| 5 | 120 |
+---+------+
四、层次结构查询 #
4.1 组织架构 #
sql
-- 创建员工表
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
manager_id INT
);
INSERT INTO employees VALUES
(1, 'CEO', NULL),
(2, 'VP1', 1),
(3, 'VP2', 1),
(4, 'Manager1', 2),
(5, 'Manager2', 2),
(6, 'Manager3', 3),
(7, 'Employee1', 4),
(8, 'Employee2', 4);
-- 查询组织架构
WITH RECURSIVE org_chart AS (
-- 基础查询:顶级管理者
SELECT
id,
name,
manager_id,
1 AS level,
CAST(name AS CHAR(500)) AS path
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- 递归查询:下属员工
SELECT
e.id,
e.name,
e.manager_id,
oc.level + 1,
CONCAT(oc.path, ' > ', e.name)
FROM employees e
JOIN org_chart oc ON e.manager_id = oc.id
)
SELECT * FROM org_chart ORDER BY path;
+----+-----------+------------+-------+---------------------------+
| id | name | manager_id | level | path |
+----+-----------+------------+-------+---------------------------+
| 1 | CEO | NULL | 1 | CEO |
| 2 | VP1 | 1 | 2 | CEO > VP1 |
| 4 | Manager1 | 2 | 3 | CEO > VP1 > Manager1 |
| 7 | Employee1 | 4 | 4 | CEO > VP1 > Manager1 > Employee1 |
| 8 | Employee2 | 4 | 4 | CEO > VP1 > Manager1 > Employee2 |
| 5 | Manager2 | 2 | 3 | CEO > VP1 > Manager2 |
| 3 | VP2 | 1 | 2 | CEO > VP2 |
| 6 | Manager3 | 3 | 3 | CEO > VP2 > Manager3 |
+----+-----------+------------+-------+---------------------------+
4.2 分类树 #
sql
-- 创建分类表
CREATE TABLE categories (
id INT PRIMARY KEY,
name VARCHAR(50),
parent_id INT
);
INSERT INTO categories VALUES
(1, 'Electronics', NULL),
(2, 'Computers', 1),
(3, 'Phones', 1),
(4, 'Laptops', 2),
(5, 'Desktops', 2),
(6, 'Smartphones', 3),
(7, 'Feature Phones', 3);
-- 查询分类树
WITH RECURSIVE category_tree AS (
SELECT
id,
name,
parent_id,
1 AS level,
CAST(name AS CHAR(500)) AS path
FROM categories
WHERE parent_id IS NULL
UNION ALL
SELECT
c.id,
c.name,
c.parent_id,
ct.level + 1,
CONCAT(ct.path, ' > ', c.name)
FROM categories c
JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT
CONCAT(REPEAT(' ', level - 1), name) AS tree,
level,
path
FROM category_tree
ORDER BY path;
+------------------+-------+---------------------------+
| tree | level | path |
+------------------+-------+---------------------------+
| Electronics | 1 | Electronics |
| Computers | 2 | Electronics > Computers |
| Laptops | 3 | Electronics > Computers > Laptops |
| Desktops | 3 | Electronics > Computers > Desktops |
| Phones | 2 | Electronics > Phones |
| Smartphones | 3 | Electronics > Phones > Smartphones |
| Feature Phones| 3 | Electronics > Phones > Feature Phones |
+------------------+-------+---------------------------+
4.3 查找所有上级 #
sql
-- 查找某个员工的所有上级
WITH RECURSIVE managers AS (
SELECT id, name, manager_id
FROM employees
WHERE id = 7 -- Employee1
UNION ALL
SELECT e.id, e.name, e.manager_id
FROM employees e
JOIN managers m ON e.id = m.manager_id
)
SELECT * FROM managers;
+----+-----------+------------+
| id | name | manager_id |
+----+-----------+------------+
| 7 | Employee1 | 4 |
| 4 | Manager1 | 2 |
| 2 | VP1 | 1 |
| 1 | CEO | NULL |
+----+-----------+------------+
4.4 查找所有下属 #
sql
-- 查找某个经理的所有下属
WITH RECURSIVE subordinates AS (
SELECT id, name, manager_id
FROM employees
WHERE id = 2 -- VP1
UNION ALL
SELECT e.id, e.name, e.manager_id
FROM employees e
JOIN subordinates s ON e.manager_id = s.id
)
SELECT * FROM subordinates;
+----+-----------+------------+
| id | name | manager_id |
+----+-----------+------------+
| 2 | VP1 | 1 |
| 4 | Manager1 | 2 |
| 5 | Manager2 | 2 |
| 7 | Employee1 | 4 |
| 8 | Employee2 | 4 |
+----+-----------+------------+
五、图遍历 #
5.1 创建图结构 #
sql
-- 创建图(节点关系)
CREATE TABLE edges (
from_node INT,
to_node INT,
weight INT
);
INSERT INTO edges VALUES
(1, 2, 10),
(1, 3, 5),
(2, 4, 20),
(3, 4, 15),
(4, 5, 10);
5.2 查找所有路径 #
sql
-- 查找从节点1到节点5的所有路径
WITH RECURSIVE paths AS (
SELECT
from_node,
to_node,
weight,
CAST(from_node AS CHAR(100)) AS path,
weight AS total_weight
FROM edges
WHERE from_node = 1
UNION ALL
SELECT
e.from_node,
e.to_node,
e.weight,
CONCAT(p.path, ' -> ', e.to_node),
p.total_weight + e.weight
FROM edges e
JOIN paths p ON e.from_node = p.to_node
)
SELECT path, total_weight FROM paths WHERE to_node = 5;
+---------------------+--------------+
| path | total_weight |
+---------------------+--------------+
| 1 -> 2 -> 4 -> 5 | 40 |
| 1 -> 3 -> 4 -> 5 | 30 |
+---------------------+--------------+
六、日期序列 #
6.1 生成日期范围 #
sql
-- 生成日期序列
WITH RECURSIVE date_series AS (
SELECT '2024-01-01' AS date
UNION ALL
SELECT DATE_ADD(date, INTERVAL 1 DAY)
FROM date_series
WHERE date < '2024-01-10'
)
SELECT * FROM date_series;
+------------+
| date |
+------------+
| 2024-01-01 |
| 2024-01-02 |
| 2024-01-03 |
| 2024-01-04 |
| 2024-01-05 |
| 2024-01-06 |
| 2024-01-07 |
| 2024-01-08 |
| 2024-01-09 |
| 2024-01-10 |
+------------+
6.2 填充缺失日期 #
sql
-- 填充缺失日期的销售数据
WITH RECURSIVE date_range AS (
SELECT '2024-01-01' AS date
UNION ALL
SELECT DATE_ADD(date, INTERVAL 1 DAY)
FROM date_range
WHERE date < '2024-01-10'
)
SELECT
dr.date,
COALESCE(SUM(s.amount), 0) AS total
FROM date_range dr
LEFT JOIN sales s ON dr.date = s.sale_date
GROUP BY dr.date
ORDER BY dr.date;
七、递归深度控制 #
7.1 限制递归深度 #
sql
-- 限制递归深度
WITH RECURSIVE org_chart 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,
oc.level + 1
FROM employees e
JOIN org_chart oc ON e.manager_id = oc.id
WHERE oc.level < 3 -- 限制深度
)
SELECT * FROM org_chart;
7.2 防止无限循环 #
sql
-- 使用访问路径防止循环
WITH RECURSIVE org_chart AS (
SELECT
id,
name,
manager_id,
CAST(id AS CHAR(1000)) AS visited
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT
e.id,
e.name,
e.manager_id,
CONCAT(oc.visited, ',', e.id)
FROM employees e
JOIN org_chart oc ON e.manager_id = oc.id
WHERE FIND_IN_SET(e.id, oc.visited) = 0 -- 防止循环
)
SELECT * FROM org_chart;
八、CTE与视图对比 #
8.1 对比 #
| 特性 | CTE | 视图 |
|---|---|---|
| 作用范围 | 当前查询 | 全局 |
| 存储 | 不存储 | 存储定义 |
| 参数 | 不支持 | 不支持 |
| 递归 | 支持 | 不支持 |
| 性能 | 可能优化 | 预编译 |
8.2 选择建议 #
sql
-- 使用CTE:临时、复杂查询
WITH daily_stats AS (
SELECT DATE(created_at) AS date, COUNT(*) AS count
FROM orders
GROUP BY DATE(created_at)
)
SELECT * FROM daily_stats WHERE count > 10;
-- 使用视图:重复使用
CREATE VIEW daily_order_stats AS
SELECT DATE(created_at) AS date, COUNT(*) AS count
FROM orders
GROUP BY DATE(created_at);
SELECT * FROM daily_order_stats WHERE count > 10;
九、最佳实践 #
9.1 命名规范 #
sql
-- 使用有意义的CTE名称
WITH
active_users AS (...),
high_value_orders AS (...),
user_rankings AS (...)
SELECT ...;
-- 避免使用无意义名称
WITH
cte1 AS (...),
cte2 AS (...),
cte3 AS (...)
SELECT ...;
9.2 性能优化 #
sql
-- 1. 使用索引
-- 为递归查询中的连接列创建索引
-- 2. 限制递归深度
WHERE level < 10
-- 3. 使用物化提示(如果支持)
WITH cte_name AS MATERIALIZED (...)
-- 4. 避免在递归部分使用复杂计算
十、总结 #
CTE要点:
| 类型 | 说明 |
|---|---|
| 基本CTE | 临时结果集 |
| 多个CTE | 多个临时表 |
| 递归CTE | 处理层次数据 |
递归CTE结构:
text
WITH RECURSIVE cte AS (
基础查询(锚点)
UNION ALL
递归查询
)
SELECT ...
最佳实践:
- 使用有意义的命名
- 限制递归深度
- 防止无限循环
- 创建适当的索引
- 复杂查询使用CTE提高可读性
恭喜你完成了MariaDB完全指南的学习!
最后更新:2026-03-27