1
1
---
2
- title : 公共表表达式( CTEs)
2
+ title : 通用表表达式 ( CTEs)
3
3
---
4
4
5
- Databend 支持带有 WITH 子句的公共表表达式(CTEs),允许您定义一个或多个命名的临时结果集,供后续查询使用。"临时"一词意味着结果集不会永久存储在数据库架构中。它们仅作为临时视图,只能被后续查询访问。
5
+ import FunctionDescription from ' @ site/src /components/FunctionDescription';
6
6
7
- 当执行带有 WITH 子句的查询时,WITH 子句中的 CTEs 首先被评估和执行。这会产生一个或多个临时结果集。然后使用 WITH 子句产生的临时结果集执行查询。
7
+ < FunctionDescription description = " 引入或更新于: v1.2.530 " />
8
8
9
- 这是一个简单的演示,帮助您理解 CTEs 在查询中的工作方式:WITH 子句定义了一个 CTE 并产生了一个结果集,该结果集包含所有来自魁北克省的客户。主查询从魁北克省的客户中筛选出居住在蒙特利尔地区的客户。
9
+ Databend 支持使用 WITH 子句的通用表表达式 (CTEs),允许你定义一个或多个命名的临时结果集,供后续查询使用。术语“临时”意味着这些结果集不会永久存储在数据库架构中。它们仅作为临时视图,供后续查询访问。
10
+
11
+ 当执行带有 WITH 子句的查询时,WITH 子句中的 CTEs 首先被评估和执行。这会产生一个或多个临时结果集。然后,查询使用由 WITH 子句产生的临时结果集执行。
12
+
13
+ 这是一个简单的演示,帮助你理解查询中 CTEs 的工作原理:WITH 子句定义了一个 CTE,并生成一个结果集,其中包含所有来自魁北克省的客户。主查询从魁北克省的客户中筛选出居住在蒙特利尔地区的客户。
10
14
11
15
``` sql
12
16
WITH customers_in_quebec
@@ -20,7 +24,7 @@ WHERE city = 'Montréal'
20
24
ORDER BY customername;
21
25
```
22
26
23
- CTEs 简化了使用子查询的复杂查询,并使您的代码更易于阅读和维护 。如果不使用 CTE,前面的示例将会是这样 :
27
+ CTEs 简化了使用子查询的复杂查询,并使你的代码更易于阅读和维护 。如果不使用 CTE,前面的示例将如下所示 :
24
28
25
29
``` sql
26
30
SELECT customername
@@ -32,17 +36,17 @@ WHERE city = 'Montréal'
32
36
ORDER BY customername;
33
37
```
34
38
35
- ## 内联还是物化 ?
39
+ ## 内联或物化 ?
36
40
37
- 在查询中使用 CTE 时,您可以通过使用 MATERIALIZED 关键字来控制 CTE 是内联还是物化。内联意味着 CTE 的定义直接嵌入到主查询中,而物化 CTE 意味着一次计算其结果并将其存储在内存中 ,减少重复的 CTE 执行。
41
+ 在使用查询中的 CTE 时,你可以通过使用 MATERIALIZED 关键字来控制 CTE 是内联还是物化。内联意味着 CTE 的定义直接嵌入到主查询中,而物化 CTE 意味着计算其结果一次并将其存储在内存中 ,减少重复的 CTE 执行。
38
42
39
43
假设我们有一个名为 _ orders_ 的表,存储客户订单信息,包括订单号、客户 ID 和订单日期。
40
44
41
45
import Tabs from '@theme/Tabs ';
42
46
import TabItem from '@theme/TabItem ';
43
47
44
48
<Tabs >
45
- <TabItem value =" Inline " label =" 内联 " default >
49
+ <TabItem value =" Inline " label =" Inline " default >
46
50
47
51
在这个查询中,CTE _ customer_orders_ 将在查询执行期间内联。Databend 将直接将 _ customer_orders_ 的定义嵌入到主查询中。
48
52
@@ -60,9 +64,9 @@ WHERE co1.order_count > 2
60
64
```
61
65
62
66
</TabItem >
63
- <TabItem value =" Materialized " label =" 物化 " >
67
+ <TabItem value =" Materialized " label =" Materialized " >
64
68
65
- 在这种情况下,我们使用了 MATERIALIZED 关键字,这意味着 CTE _ customer_orders_ 将不会内联 。相反,在执行 CTE 定义时, CTE 的结果将被计算并存储在内存中。在主查询中执行 CTE 的两个实例时,Databend 将直接从内存中检索结果,避免了重复的计算 ,并可能提高性能。
69
+ 在这种情况下,我们使用 MATERIALIZED 关键字,这意味着 CTE _ customer_orders_ 不会被内联 。相反,CTE 的结果将在 CTE 定义执行时计算并存储在内存中。当在主查询中执行 CTE 的两个实例时,Databend 将直接从内存中检索结果,避免重复计算 ,并可能提高性能。
66
70
67
71
``` sql
68
72
WITH customer_orders AS MATERIALIZED (
@@ -77,7 +81,7 @@ WHERE co1.order_count > 2
77
81
AND co2 .order_count > 5 ;
78
82
```
79
83
80
- 这可以显著提高性能,特别是在 CTE 的结果被多次使用的情况下。然而,由于 CTE 不再内联,查询优化器可能难以将 CTE 的条件推入主查询或优化连接顺序,可能导致整体查询性能下降。
84
+ 这可以显著提高性能,在 CTE 的结果被多次使用的情况下。然而,由于 CTE 不再内联,查询优化器可能难以将 CTE 的条件推入主查询或优化连接顺序,可能导致整体查询性能下降。
81
85
82
86
</TabItem >
83
87
</Tabs >
89
93
< cte_name1> [ ( < cte_column_list> ) ] AS [MATERIALIZED] ( SELECT ... )
90
94
[ , < cte_name2> [ ( < cte_column_list> ) ] AS [MATERIALIZED] ( SELECT ... ) ]
91
95
[ , < cte_nameN> [ ( < cte_column_list> ) ] AS [MATERIALIZED] ( SELECT ... ) ]
96
+ SELECT ... | UPDATE ... | DELETE FROM ...
97
+ ```
98
+
99
+ | 参数 | 描述 |
100
+ | ----------------------- | ---------------------------------------------------------------------- |
101
+ | WITH | 启动 WITH 子句。 |
102
+ | cte_name1 ... cte_nameN | CTE 的名称。当你有多个 CTE 时,用逗号将它们分开。 |
103
+ | cte_column_list | CTE 中列的名称。CTE 可以引用在同一 WITH 子句中定义在其之前的任何 CTE。 |
104
+ | MATERIALIZED | ` Materialized ` 是一个可选关键字,用于指示 CTE 是否应该被物化。 |
105
+
106
+ ## 递归 CTEs
107
+
108
+ 递归 CTE 是一个临时结果集,它引用自身来执行递归操作,允许处理层次或递归数据结构。
109
+
110
+ ### 语法
111
+
112
+ ``` sql
113
+ WITH RECURSIVE < cte_name> AS (
114
+ < initial_query>
115
+ UNION ALL
116
+ < recursive_query> )
92
117
SELECT ...
93
118
```
94
119
95
- | 参数 | 描述 |
96
- | ----------------------- | --------------------------------------------------------------------- |
97
- | WITH | 开始 WITH 子句。 |
98
- | cte_name1 ... cte_nameN | CTE 的名称。当您有多个 CTE 时,用逗号分隔它们。 |
99
- | cte_column_list | CTE 中的列名。一个 CTE 可以引用同一个 WITH 子句中定义的任何其他 CTE。 |
100
- | MATERIALIZED | “物化”是在定义 CTE 时使用的可选关键字,用来指示 CTE 是否应该物化。 |
101
- | SELECT ... | CTE 主要与 SELECT 语句一起使用。 |
120
+ | 参数 | 描述 |
121
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
122
+ | ` cte_name ` | CTE 的名称。 |
123
+ | ` initial_query ` | 初始查询,在递归开始时执行一次,通常返回一组行。 |
124
+ | ` recursive_query ` | 引用 CTE 本身的查询,重复执行直到返回空结果集。此查询必须包含对 CTE 名称的引用。查询中不得包含聚合函数(如 MAX, MIN, SUM, AVG, COUNT)、窗口函数、GROUP BY 子句、ORDER BY 子句、LIMIT 子句或 DISTINCT。 |
125
+
126
+ ### 工作原理
127
+
128
+ 以下描述了递归 CTE 的详细执行顺序:
129
+
130
+ 1 . ** 初始查询执行** :此查询形成基础结果集,记为 R0。该结果集为递归提供起点。
131
+
132
+ 2 . ** 递归查询执行** :此查询使用前一次迭代的结果集(从 R0 开始)作为输入,并产生新的结果集(Ri+1)。
133
+
134
+ 3 . ** 迭代与组合** :递归执行持续迭代进行。递归查询每次产生的新结果集(Ri)成为下一次迭代的输入。此过程重复直至递归查询返回空结果集,表明终止条件已满足。
135
+
136
+ 4 . ** 最终结果集形成** :使用 ` UNION ALL ` 操作符,将每次迭代的结果集(R0 至 Rn)合并成单一结果集。` UNION ALL ` 操作符确保每个结果集中的所有行均包含在最终合并结果中。
137
+
138
+ 5 . ** 最终选择** :最终的 ` SELECT ... ` 语句从 CTE 中检索合并结果集。此语句可在合并结果集上应用额外的过滤、排序或其他操作,以产生最终输出。
102
139
103
140
## 使用示例
104
141
105
- 假设您管理位于 GTA 地区不同地区的几家书店,并使用一个表来保存它们的店铺 ID、地区和上个月的交易量。
142
+ ### 非递归 CTE
143
+
144
+ 假设您管理着位于 GTA 地区不同区域的多家书店,并使用一张表来记录它们的店铺 ID、区域以及上个月的交易量。
106
145
107
146
``` sql
108
147
CREATE TABLE sales
@@ -121,10 +160,10 @@ INSERT INTO sales VALUES (6, 'Markham', 4350);
121
160
INSERT INTO sales VALUES (7 , ' North York' , 2490 );
122
161
```
123
162
124
- 以下代码返回交易量低于平均值的商店 :
163
+ 以下代码返回交易量低于平均水平的店铺 :
125
164
126
165
``` sql
127
- -- 定义一个包含一个CTE的WITH子句
166
+ -- 定义包含一个 CTE 的 WITH 子句
128
167
WITH avg_all
129
168
AS (SELECT Avg (amount) AVG_SALES
130
169
FROM sales)
@@ -148,10 +187,10 @@ WHERE sales.amount < avg_sales;
148
187
└──────────────────────────────────────────────────────────────────────────┘
149
188
```
150
189
151
- 以下代码返回每个地区的平均和总交易量 :
190
+ 以下代码返回各区域的平均和总交易量 :
152
191
153
192
``` sql
154
- -- 定义一个包含两个CTE的WITH子句
193
+ -- 定义包含两个 CTE 的 WITH 子句
155
194
WITH avg_by_region
156
195
AS (SELECT region,
157
196
Avg (amount) avg_by_region_value
@@ -175,11 +214,111 @@ WHERE avg_by_region.region = sum_by_region.region;
175
214
``` text
176
215
┌──────────────────────────────────────────────────────────────┐
177
216
│ region │ avg_by_region_value │ sum_by_region_value │
178
- │ Nullable(String) │ Nullable(Float64) │ Nullable(Int64) │
179
217
├──────────────────┼─────────────────────┼─────────────────────┤
180
218
│ North York │ 7645 │ 15290 │
181
219
│ Downtown │ 17035 │ 34070 │
182
220
│ Markham │ 5535 │ 11070 │
183
221
│ Mississauga │ 4990 │ 4990 │
184
222
└──────────────────────────────────────────────────────────────┘
185
223
```
224
+
225
+ 以下代码将各店铺的交易量更新为 0,条件是其交易量低于各自区域的平均交易量:
226
+
227
+ ``` sql
228
+ WITH region_avg_sales_cte AS (
229
+ SELECT region, AVG (amount) AS avg_sales
230
+ FROM sales
231
+ GROUP BY region
232
+ )
233
+ UPDATE sales
234
+ SET amount = 0
235
+ WHERE amount < (
236
+ SELECT avg_sales
237
+ FROM region_avg_sales_cte AS cte
238
+ WHERE cte .region = sales .region
239
+ );
240
+ ```
241
+
242
+ 假设我们还有一张名为 "store_details" 的表,其中包含每个店铺的额外信息,如店铺的开业日期和店主。
243
+
244
+ ``` sql
245
+ CREATE TABLE store_details (
246
+ storeid INTEGER ,
247
+ store_name TEXT ,
248
+ opening_date DATE ,
249
+ owner TEXT
250
+ );
251
+
252
+ INSERT INTO store_details VALUES (1 , ' North York Store' , ' 2022-01-01' , ' John Doe' );
253
+ INSERT INTO store_details VALUES (12 , ' Downtown Store' , ' 2022-02-15' , ' Jane Smith' );
254
+ INSERT INTO store_details VALUES (3 , ' Markham Store' , ' 2021-12-10' , ' Michael Johnson' );
255
+ INSERT INTO store_details VALUES (9 , ' Mississauga Store' , ' 2022-03-20' , ' Emma Brown' );
256
+ INSERT INTO store_details VALUES (5 , ' Scarborough Store' , ' 2022-04-05' , ' David Lee' );
257
+ ```
258
+
259
+ 我们希望删除 "store_details" 表中与 "sales" 表中无销售记录的店铺对应的行:
260
+
261
+ ``` sql
262
+ WITH stores_with_sales AS (
263
+ SELECT DISTINCT storeid
264
+ FROM sales
265
+ )
266
+ DELETE FROM store_details
267
+ WHERE storeid NOT IN (SELECT storeid FROM stores_with_sales);
268
+ ```
269
+
270
+ ### 递归 CTE
271
+
272
+ 首先,我们创建一个表来存储员工数据,包括他们的 ID、姓名和经理 ID。
273
+
274
+ ``` sql
275
+ CREATE TABLE Employees (
276
+ EmployeeID INT ,
277
+ EmployeeName VARCHAR (100 ),
278
+ ManagerID INT
279
+ );
280
+ ```
281
+
282
+ 接下来,我们向表中插入示例数据,以表示一个简单的组织结构。
283
+
284
+ ``` sql
285
+ INSERT INTO Employees (EmployeeID, EmployeeName, ManagerID) VALUES
286
+ (1 , ' Alice' , NULL ), -- Alice 是 CEO
287
+ (2 , ' Bob' , 1 ), -- Bob 向 Alice 报告
288
+ (3 , ' Charlie' , 1 ), -- Charlie 向 Alice 报告
289
+ (4 , ' David' , 2 ), -- David 向 Bob 报告
290
+ (5 , ' Eve' , 2 ), -- Eve 向 Bob 报告
291
+ (6 , ' Frank' , 3 ); -- Frank 向 Charlie 报告
292
+ ```
293
+
294
+ 现在,我们使用递归 CTE 来查找特定经理(例如 Alice,员工 ID = 1)下属的员工层级结构。
295
+
296
+ ``` sql
297
+ WITH RECURSIVE EmployeeHierarchy AS (
298
+ -- 初始查询:从指定的经理(Alice)开始
299
+ SELECT EmployeeID, EmployeeName, ManagerID
300
+ FROM Employees
301
+ WHERE ManagerID IS NULL -- Alice,因为她没有经理
302
+ UNION ALL
303
+ -- 递归查询:查找当前级别的下属员工
304
+ SELECT e .EmployeeID , e .EmployeeName , e .ManagerID
305
+ FROM Employees e
306
+ INNER JOIN EmployeeHierarchy eh ON e .ManagerID = eh .EmployeeID
307
+ )
308
+ SELECT * FROM EmployeeHierarchy;
309
+ ```
310
+
311
+ 输出将列出 Alice 下属的所有员工:
312
+
313
+ ``` sql
314
+ ┌──────────────────────────────────────────────────────┐
315
+ │ employeeid │ employeename │ managerid │
316
+ ├─────────────────┼──────────────────┼─────────────────┤
317
+ │ 1 │ Alice │ NULL │
318
+ │ 2 │ Bob │ 1 │
319
+ │ 3 │ Charlie │ 1 │
320
+ │ 4 │ David │ 2 │
321
+ │ 5 │ Eve │ 2 │
322
+ │ 6 │ Frank │ 3 │
323
+ └──────────────────────────────────────────────────────┘
324
+ ```
0 commit comments