0. WITH
在 PL/pgSQL 中,WITH 子句通常用于创建一个临时结果集,这个结果集在执行 SQL 查询时使用。这个临时结果集通常被称为一个公共表表达式(Common Table Expression, CTE)。CTE 允许您在查询中引用它,就像引用一个表一样。
基本语法:
sql
WITH [RECURSIVE] cte_name AS (
[query]
)
-- 使用
SELECT * FROM cte_name;
- cte_name:您为 CTE 指定的名称。
- [RECURSIVE]:可选的,用于标记递归 CTE。
- [query]:定义 CTE 的查询。
案例:
-
下面是一个使用 WITH 语句的简单具体案例。假设我们有一个名为orders的表,它记录了订单的信息,包括订单ID、客户ID、订单日期和订单金额。我们想要找出每个客户的总订单金额,并找出订单金额超过1000的客户。
-
不使用 WITH 语句的查询可能会像这样:
sql
SELECT customer_id, SUM(order_amount) AS total_amount
FROM orders
GROUP BY customer_id
HAVING SUM(order_amount) > 1000;
要求:
- 如果我们想要进一步筛选这些客户,比如只选择那些在某个日期之后下过订单的客户,我们可以使用 WITH 语句来使查询更清晰:
sql
WITH customer_totals AS (
SELECT customer_id, SUM(order_amount) AS total_amount
FROM orders
GROUP BY customer_id
)
SELECT ct.customer_id, ct.total_amount
FROM customer_totals ct
JOIN orders o ON ct.customer_id = o.customer_id
WHERE o.order_date > '2023-01-01' -- 假设我们只关心2023年1月1日之后的订单
AND ct.total_amount > 1000; -- 订单金额超过1000
解析:
- 在这个例子中,我们首先使用 WITH 语句创建了一个名为customer_totals的CTE,它包含了每个客户的总订单金额。然后,在主查询中,我们从这个CTE中选择数据,并与orders表进行连接,以找出在指定日期之后下过订单且总订单金额超过1000的客户。
- 使用 WITH 语句可以使查询更加模块化,并提高可读性,特别是当查询变得复杂时。
1. WITH RECURSIVE - 递归结果集
具体案例:with recursive
-
问题:
我们有一个简单的员工表employees,其中包含了员工的ID、姓名、职位和经理ID(表示上级经理的ID)。
我们想要构建一个CTE来找到某个员工的所有上级经理,直到CEO(假设CEO没有上级经理)。
-
实现:
表的结构可能如下:
sql
employees table:
+----+-------+---------+----------+
| id | name | title | manager_id |
+----+-------+---------+----------+
| 1 | Alice | CEO | NULL |
| 2 | Bob | Manager | 1 |
| 3 | Carol | Staff | 2 |
| 4 | Dave | Staff | 2 |
| 5 | Eve | Manager | 1 |
| 6 | Frank | Staff | 5 |
+----+-------+---------+----------+
我们想要找到员工Carol的所有上级经理。我们可以使用WITH RECURSIVE语句和UNION来构建一个CTE,如下所示:
sql
-- 创建查询过程
WITH RECURSIVE managers_chain AS (
-- 非递归部分:找到Carol的直接上级
SELECT manager_id
FROM employees
WHERE id = 3 -- 假设Carol的ID是3
UNION
-- 递归部分:找到上级经理的上级,直到CEO
SELECT e.manager_id
FROM employees e
INNER JOIN managers_chain mc ON e.id = mc.manager_id
WHERE e.manager_id IS NOT NULL
)
-- 查询
SELECT * FROM managers_chain;
- 解析:
这个CTE的工作原理如下:
非递归部分(种子查询)选择了Carol的直接上级的ID。
递归部分通过UNION与非递归部分合并,它会反复执行,每次都使用前一次查询结果中的manager_id来找到下一级的上级经理,直到达到CEO(即manager_id为NULL)。
最终,CTE的结果将是一个包含Carol所有上级经理ID的列表。当我们执行SELECT * FROM managers_chain;时,我们将得到以下结果:
sql
manager_id
-----------
2
1
这表示Carol的直接上级是Bob(ID为2),Bob的上级是Alice(ID为1),即CEO。
2. UNION
UNION 是 SQL(结构化查询语言)中的一个操作符,用于合并两个或多个 SELECT 语句的结果集。这些 SELECT 语句必须选择相同数量的列,并且这些列的数据类型也必须相似或兼容。UNION 会自动去除重复的行,而如果你想要包含重复的行,可以使用 UNION ALL。
基本语法:
sql
SELECT column_name(s) FROM table1
UNION
SELECT column_name(s) FROM table2;
案例:
假设我们有两个表,employees_ny 和 employees_sf,分别存储纽约和旧金山的员工信息。这两个表的结构是相同的,都有 id、name 和 salary 三个字段。
- 表结构
employees_ny 表:
sql
id name salary
1 Alice 5000
2 Bob 6000
3 Carol 5500
employees_sf 表:
sql
id name salary
4 Dave 7000
5 Eve 5000
6 Frank 6500
- 问题:
如果我们想要查询纽约和旧金山所有工资超过 5000 的员工,并合并结果,可以使用 UNION:
sql
SELECT name, salary FROM employees_ny WHERE salary >= 5000
UNION
SELECT name, salary FROM employees_sf WHERE salary >= 5000;
注:
结果集可能如下(注意结果中的 "Alice" 和 "Eve" 工资都是 5000,但由于 UNION 会去除重复行,所以 "Alice" 只会出现一次):
- 结果:
sql
name salary
Bob 6000
Carol 5500
Dave 7000
Eve 5000
Frank 6500
如果你想要保留所有行,包括重复的行,可以使用 UNION ALL:
sql
SELECT name, salary FROM employees_ny WHERE salary >= 5000
UNION ALL
SELECT name, salary FROM employees_sf WHERE salary >= 5000;
这样,"Alice" 将会在两次结果中都出现,因为 UNION ALL 不会去除重复的行。