一、CTE
在 PostgreSQL 中,CTE(Common Table Expression,公用表表达式)是一种临时结果集,可在 SQL 语句中被引用,类似于子查询,但语法更清晰、可读性更强,且支持递归操作。
1. CTE 的基本语法
CTE 通常以 WITH 子句开头,语法结构如下:
sql
WITH cte_name (column1, column2, ...) AS (
-- CTE 内部的查询逻辑(返回一个结果集)
SELECT ...
)
-- 主查询:引用 CTE
SELECT ... FROM cte_name;
cte_name:自定义的 CTE 名称(临时结果集的别名)。- 括号中的
(column1, ...)可选,用于指定 CTE 结果集的列名(若省略,默认使用内部查询的列名)。 - 内部查询:生成 CTE 结果集的 SQL 语句(可以是
SELECT、INSERT、UPDATE、DELETE等,视场景而定)。 - 主查询:必须引用 CTE,否则 CTE 定义无意义。
2. 非递归 CTE(普通 CTE)
最常用的 CTE 形式,用于简化复杂查询,将子查询提取为临时结果集,提高可读性。
示例:
假设有一张 employees 表(员工信息),包含 id、name、department、salary 字段,现在需要查询每个部门的平均工资及该部门中工资高于平均的员工。
用 CTE 实现:
sql
-- 定义 CTE:计算各部门平均工资
WITH dept_avg_salary AS (
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
)
-- 主查询:关联员工表和 CTE,筛选高工资员工
SELECT e.name, e.department, e.salary, d.avg_salary
FROM employees e
JOIN dept_avg_salary d ON e.department = d.department
WHERE e.salary > d.avg_salary;
优势:相比嵌套子查询,CTE 将 "计算部门平均工资" 的逻辑独立出来,更易理解和维护。
3. 递归 CTE
PostgreSQL 支持递归 CTE(Recursive CTE),用于处理具有递归结构的数据(如树形结构、层级关系),例如组织架构、评论回复链等。
递归 CTE 的语法:
递归 CTE 必须包含两部分,用 UNION ALL 连接:
- 锚点成员(Anchor Member):初始查询,返回递归的起点结果集。
- 递归成员(Recursive Member):引用 CTE 自身的查询,用于迭代处理上一步的结果,直到无新数据返回。
sql
WITH RECURSIVE cte_name AS (
-- 锚点成员:初始数据
SELECT ...
UNION ALL
-- 递归成员:引用 CTE 自身,迭代处理
SELECT ... FROM cte_name JOIN ...
)
SELECT ... FROM cte_name;
示例:处理树形结构
假设有一张 org_structure 表(组织架构),包含 id(员工 ID)、name(姓名)、manager_id(上级 ID,顶级节点为 NULL):
| id | name | manager_id |
|---|---|---|
| 1 | 老板 | NULL |
| 2 | 经理 A | 1 |
| 3 | 员工 A1 | 2 |
| 4 | 经理 B | 1 |
| 5 | 员工 B1 | 4 |
需求:查询所有员工的层级关系(如 "老板 -> 经理 A -> 员工 A1")。
用递归 CTE 实现:
sql
WITH RECURSIVE org_hierarchy AS (
-- 锚点成员:顶级节点(无上级)
SELECT
id,
name,
manager_id,
1 AS level, -- 层级(顶级为1)
ARRAY[name] AS path -- 路径(用数组存储)
FROM org_structure
WHERE manager_id IS NULL
UNION ALL
-- 递归成员:关联子节点与父节点(CTE 自身)
SELECT
e.id,
e.name,
e.manager_id,
oh.level + 1 AS level, -- 层级+1
oh.path || e.name AS path -- 路径追加当前节点
FROM org_structure e
JOIN org_hierarchy oh ON e.manager_id = oh.id -- 子节点的manager_id = 父节点的id
)
-- 主查询:输出结果
SELECT id, name, level, array_to_string(path, ' -> ') AS full_path
FROM org_hierarchy
ORDER BY level, id;
结果:
id | name | level | full_path
----+---------+-------+--------------------
1 | 老板 | 1 | 老板
2 | 经理A | 2 | 老板 -> 经理A
4 | 经理B | 2 | 老板 -> 经理B
3 | 员工A1 | 3 | 老板 -> 经理A -> 员工A1
5 | 员工B1 | 3 | 老板 -> 经理B -> 员工B1
4. CTE 的特点
- 临时性:CTE 仅在当前 SQL 语句中有效,执行结束后自动消失。
- 可读性:将复杂逻辑拆分,比嵌套子查询更易理解。
- 递归支持:递归 CTE 是处理树形 / 层级数据的高效方式。
二、临时表
在 PostgreSQL 中,临时表(Temporary Table)是一种特殊的表,主要用于存储临时数据,其生命周期有限,通常在会话结束或事务结束后自动删除,适合处理中间结果、临时计算等场景。以下是关于 PostgreSQL 临时表的详细讲解:
1. 临时表的特点
- 生命周期短暂 :默认在当前会话结束时自动删除;若指定
ON COMMIT子句,可在事务结束时删除。 - 会话隔离:临时表仅对创建它的会话可见,不同会话的临时表即使同名也互不干扰。
- 存储位置:数据通常存储在内存或临时磁盘空间(而非永久表空间),减少对磁盘的持久化开销。
- 性能优化:临时表的操作(如创建、插入、查询)通常比永久表更快,适合临时数据处理。
2. 创建临时表
使用 CREATE TEMPORARY TABLE 或 CREATE TEMP TABLE 语句创建临时表,语法与普通表类似,可指定字段、类型、约束等。
基本语法:
sql
CREATE TEMPORARY TABLE [IF NOT EXISTS] 临时表名 (
字段1 类型 [约束],
字段2 类型 [约束],
...
) [ON COMMIT {PRESERVE ROWS | DELETE ROWS | DROP}] [TABLESPACE 表空间名];
关键选项说明:
IF NOT EXISTS:若临时表已存在,不报错(避免重复创建)。ON COMMIT:控制事务提交时的行为(默认PRESERVE ROWS):PRESERVE ROWS:事务提交后保留数据(默认值)。DELETE ROWS:事务提交后清空数据(表结构保留)。DROP:事务提交后删除整个临时表(表结构和数据都删除)。
TABLESPACE:指定临时表的表空间(通常使用默认的pg_temp临时表空间)。
示例:
sql
-- 创建一个临时表,存储用户ID和名称,事务提交后保留数据
CREATE TEMP TABLE temp_users (
id INT PRIMARY KEY,
name TEXT NOT NULL
);
-- 创建一个事务级临时表,事务提交后自动删除
CREATE TEMP TABLE temp_scores (
user_id INT,
score NUMERIC
) ON COMMIT DROP
4. 临时表的生命周期
- 默认行为:会话结束时自动删除(关闭连接后消失)。
- 事务级临时表 :若指定
ON COMMIT DROP,则当前事务提交后立即删除。 - 手动删除 :可通过
DROP TABLE主动删除临时表(即使会话未结束)
三、CTE和临时表区别
1. 定义与生命周期
-
CTE 由
WITH子句定义,是一次性的临时结果集 ,仅在当前 SQL 语句(如SELECT/INSERT/UPDATE等)的执行过程中有效,语句执行结束后立即消失,无法跨多个 SQL 语句复用。示例:sql
WITH cte AS (SELECT 1 AS id) SELECT * FROM cte; -- CTE 仅在这条语句中有效 -
临时表 由
CREATE TEMPORARY TABLE定义,是数据库中实际存在的临时表对象 ,生命周期与当前数据库会话(Session)绑定(默认),或在事务结束后删除(若指定ON COMMIT DROP)。可以在同一会话内的多个 SQL 语句中重复引用。示例:sql
CREATE TEMPORARY TABLE temp_tbl AS SELECT 1 AS id; SELECT * FROM temp_tbl; -- 第一次引用 SELECT * FROM temp_tbl WHERE id > 0; -- 同一会话内再次引用
2. 语法与使用场景
-
CTE
- 语法简洁,适合嵌入在单个 SQL 语句中,用于简化复杂查询(如替代多层嵌套子查询)或处理递归逻辑(递归 CTE)。
- 无法像表一样执行
INSERT/UPDATE/DELETE操作(仅能作为查询结果集)。 - 典型场景:一次性的复杂查询拆分、树形结构遍历(如组织架构、评论层级)。
-
临时表
- 语法更接近普通表,创建后可像常规表一样执行
SELECT/INSERT/UPDATE/DELETE,甚至创建索引、约束。 - 适合需要多次复用临时结果集的场景(如同一会话内多步计算依赖同一中间结果)。
- 典型场景:批量数据处理、多步骤数据分析(如先过滤再聚合,再关联其他表)。
- 语法更接近普通表,创建后可像常规表一样执行
3. 性能与存储
-
CTE
- 在 PostgreSQL 中,CTE 默认是 "优化屏障"(Optimization Fence),即数据库优化器不会将 CTE 与主查询合并优化,而是先 "物化"(生成临时结果集)再处理(PostgreSQL 12+ 可通过
NOT MATERIALIZED取消此特性)。 - 物化的 CTE 结果通常存储在内存中(若数据量小),或临时磁盘中(数据量大),但无持久化存储。
- 递归 CTE 性能高效,适合处理层级数据,避免了多次循环查询。
- 在 PostgreSQL 中,CTE 默认是 "优化屏障"(Optimization Fence),即数据库优化器不会将 CTE 与主查询合并优化,而是先 "物化"(生成临时结果集)再处理(PostgreSQL 12+ 可通过
-
临时表
- 会被显式创建为磁盘上的临时文件(存储在 PostgreSQL 的临时表空间
pg_temp),支持索引,因此对于大数据量且需要多次查询的场景,性能可能更优(索引可加速查询)。 - 但创建临时表本身有额外开销(如磁盘 I/O、元数据维护),频繁创建 / 删除可能影响性能。
- 会被显式创建为磁盘上的临时文件(存储在 PostgreSQL 的临时表空间
4. 可见性与隔离性
-
CTE仅在定义它的 SQL 语句内部可见,完全隔离,不会与其他语句或会话冲突。
-
临时表
- 仅在当前会话可见,不同会话可以创建同名临时表而不冲突(与普通表的命名冲突时,临时表优先级更高)。
- 若指定
GLOBAL TEMPORARY TABLE(部分数据库支持,PostgreSQL 仅支持会话级临时表),可能在多个会话间共享结构,但数据隔离。
总结:如何选择?
| 场景 | 优先选 CTE | 优先选临时表 |
|---|---|---|
| 结果集仅在单个 SQL 语句中使用 | ✅ 语法简洁,无额外开销 | ❌ 多余的创建 / 删除操作 |
| 需要处理递归 / 层级数据 | ✅ 原生支持递归,高效简洁 | ❌ 需手动循环,复杂低效 |
| 结果集需在多个 SQL 语句中复用 | ❌ 无法跨语句引用 | ✅ 会话内可重复使用 |
| 数据量大且需多次查询(需索引优化) | ❌ 无索引,物化开销可能更高 | ✅ 支持索引,适合高频访问 |
| 需要对临时结果进行增删改操作 | ❌ 仅支持查询 | ✅ 可像普通表一样操作 |