SQL关键字三分钟入门:WITH —— 公用表表达式让复杂查询更清晰
转自:https://blog.csdn.net/Landcc/article/details/148765844
在实际的数据库开发和分析中,我们常常会遇到复杂的多层嵌套查询,这样的 SQL 语句不仅难以阅读,也容易出错。
这时候就需要使用一个非常实用又优雅的关键字 —— WITH!
它可以帮助我们将复杂的子查询提取出来并命名,从而提升代码可读性、复用性和维护性。这个功能也被称为 CTE(Common Table Expressions,公用表表达式)。
1.什么是 WITH?
WITH 是 SQL 中用于定义临时结果集的关键字。这些临时结果集可以在后续查询中像普通表一样被引用,并且只在当前查询执行期间存在。
你可以把它理解为:“先写好一个中间结果,后面可以直接拿来用”。
2.基本语法
- WITH cte_name AS (
- -- 子查询内容
- SELECT ...
- )
- -- 后续主查询中使用 cte_name
- SELECT * FROM cte_name;
sql
cte_name是你给中间结果集起的名字。- 可以定义多个 CTE,用逗号分隔。
3.示例讲解
假设我们有一个 orders 表,记录了订单信息:
| order_id | customer_id | amount |
|---|---|---|
| 1 | 1 | 2999 |
| 2 | 2 | 499 |
| 3 | 1 | 199 |
| 4 | 3 | 899 |
示例1:计算每个客户的订单总金额(简单 CTE 使用)
- WITH customer_totals AS (
- SELECT customer_id, SUM(amount) AS total_amount
- FROM orders
- GROUP BY customer_id
- )
- SELECT *
- FROM customer_totals
- WHERE total_amount > 500;
sql
结果:
| customer_id | total_amount |
|---|---|
| 1 | 3198 |
| 3 | 899 |
👉 这里我们先定义了一个 CTE customer_totals 来计算每位客户的总消费金额,然后主查询筛选出金额大于500的客户。
示例2:多个 CTE 的使用(分步处理复杂逻辑)
- WITH
- -- 第一步:统计每位客户的总消费
- customer_totals AS (
- SELECT customer_id, SUM(amount) AS total_amount
- FROM orders
- GROUP BY customer_id
- ),
- -- 第二步:根据总消费划分客户等级
- customer_levels AS (
- SELECT customer_id, total_amount,
- CASE
- WHEN total_amount > 1000 THEN '高级客户'
- WHEN total_amount BETWEEN 500 AND 1000 THEN '中级客户'
- ELSE '普通客户'
- END AS level
- FROM customer_totals
- )
- -- 最终查询:展示客户等级信息
- SELECT * FROM customer_levels;
sql
结果:
| customer_id | total_amount | level |
|---|---|---|
| 1 | 3198 | 高级客户 |
| 2 | 499 | 普通客户 |
| 3 | 899 | 中级客户 |
通过多个 CTE 分步骤处理,整个查询逻辑更加清晰易懂。
示例3:递归 CTE(以员工层级为例)
递归 CTE 是 WITH 的一种高级用法,常用于处理树形结构或层级数据(如组织架构、分类目录等)。
假设我们有一个 employees 表:
| employee_id | name | manager_id |
|---|---|---|
| 1 | 张三 | NULL |
| 2 | 李四 | 1 |
| 3 | 王五 | 2 |
- WITH RECURSIVE employee_hierarchy AS (
- -- 初始查询:没有上级的员工(即 CEO)
- SELECT employee_id, name, manager_id, CAST(name AS TEXT) AS hierarchy_path
- FROM employees
- WHERE manager_id IS NULL
-
- UNION ALL
-
- -- 递归部分:查找下属员工
- SELECT e.employee_id, e.name, e.manager_id,
- CAST(eh.hierarchy_path || ' → ' || e.name AS TEXT)
- FROM employees e
- INNER JOIN employee_hierarchy eh ON e.manager_id = eh.employee_id
- )
- SELECT * FROM employee_hierarchy;
sql
结果:
| employee_id | name | manager_id | hierarchy_path |
|---|---|---|---|
| 1 | 张三 | NULL | 张三 |
| 2 | 李四 | 1 | 张三 → 李四 |
| 3 | 王五 | 2 | 张三 → 李四 → 王五 |
这个例子展示了如何用递归 CTE 构建一个组织层级路径,非常适合处理树状结构数据。
| 对比项 | 使用 WITH(CTE) | 不使用 CTE(嵌套子查询) |
|---|---|---|
| 可读性 | 更高,结构清晰 | 较低,嵌套多层时难读 |
| 复用性 | 可多次引用 | 每次都要重复写 |
| 调试方便性 | 易于单独测试每个 CTE | 难以调试嵌套部分 |
| 递归支持 | 支持(RECURSIVE) | 不支持 |
| 性能 | 与子查询基本一致,但逻辑优化后可能更好 | 视具体实现而定 |
4. 总结对比表
| 功能 | SQL 示例 |
|---|---|
| 定义单个 CTE | WITH cte AS (...) SELECT * FROM cte; |
| 定义多个 CTE | WITH a AS (...), b AS (...) SELECT * FROM a JOIN b... |
| 递归 CTE | WITH RECURSIVE ... |
| 提高代码可读性 | 将复杂查询拆分为多个逻辑块 |
| 支持重用 | 同一查询中可多次引用 CTE 名 |
</div>
活到老,学到老。

浙公网安备 33010602011771号