Sql 简明教程

SQL - Common Table Expression (CTE)

公共表表达式 (CTE) 可以使管理和编写复杂查询变得更容易,让它们更具可读性和简单性,就像数据库视图和派生表一样。通过将复杂查询分解为简单的块,我们可以重用或重写查询。

The SQL Common Table Expression

MySQL 中的 WITH 子句用于指定公共表表达式。

SQL 中的公共表表达式 (CTE) 是一个一次性结果集,即它是一个仅在执行一次查询期间存在的临时表。它允许我们特别在该查询中使用数据,例如在 SELECTUPDATEINSERTDELETECREATEVIEWMERGE 语句中使用数据。

CTE 是临时的,因为无法将其存储在任何地方以备以后使用;一旦执行查询,它就丢失了。

The MySQL WITH Clause

要指定公共表表达式,我们使用 WITH 子句,它由一个或多个逗号分隔的子句组成。在每个子句中,我们可以提供生成结果集的子查询,并为此子查询分配一个名称。

在 8.0 之前的 MySQL 版本中,不能使用 WITH 子句。

Syntax

以下是使用 WITH 子句创建 CTE 的语法 −

WITH CTE_NAME (column_name) AS (query)
SELECT * FROM CTE_NAME;

其中,

  1. CTE_NAME − 为 CTE 分配的名称。

  2. column_name − 这是 CTE 的列名称,可以用来提高查询的可读性。

  3. query − 它定义了 CTE,它可以是任何有效的 SQL 查询。

  4. 定义 CTE 之后,你可以在同一会话中的后续查询中引用它。

Example

假设我们使用 CREATE TABLE 语句在 MySQL 数据库中创建名为 CUSTOMERS 的表,如下所示:

CREATE TABLE CUSTOMERS (
   ID INT NOT NULL,
   NAME VARCHAR (20) NOT NULL,
   AGE INT NOT NULL,
   ADDRESS CHAR (25),
   SALARY DECIMAL (18, 2),
   PRIMARY KEY (ID)
);

现在,我们将向上面创建的表插入一些记录:

INSERT INTO CUSTOMERS VALUES
(1, 'Ramesh', 32, 'Ahmedabad', 2000.00 ),
(2, 'Khilan', 25, 'Delhi', 1500.00 ),
(3, 'Kaushik', 23, 'Kota', 2000.00 ),
(4, 'Chaitali', 25, 'Mumbai', 6500.00 ),
(5, 'Hardik', 27, 'Bhopal', 8500.00 ),
(6, 'Komal', 22, 'Hyderabad', 4500.00 ),
(7, 'Muffy', 24, 'Indore', 10000.00 );

创建的表如下所示:

ID

NAME

AGE

ADDRESS

SALARY

1

Ramesh

32

Ahmedabad

2000.00

2

Khilan

25

Delhi

1500.00

3

Kaushik

23

Kota

2000.00

4

Chaitali

25

Mumbai

6500.00

5

Hardik

27

Bhopal

8500.00

6

Komal

22

Hyderabad

4500.00

7

Muffy

24

Indore

10000.00

在这里,我们创建一个名为 CUSTOMER_AGE 的公用表表达式 (CTE),它选择所有年龄为 23 的客户。然后,我们从 CTE 中检索这些客户的 ID、NAME 和 AGE。

WITH CUSTOMER_AGE AS (
SELECT * FROM customers WHERE AGE = 23)
SELECT ID, NAME, AGE FROM CUSTOMER_AGE;

Output

以下是以上查询的输出:

ID

NAME

AGE

3

Kaushik

23

CTE from Multiple Tables

我们还可以创建一个公用表表达式 (CTE),通过 CTE 的子查询中的 JOIN 操作组合多张表中的数据。为此,我们需要使用逗号运算符来分隔每个 CTE 定义,有效地将它们合并成一条语句。

Syntax

以下是多公用表表达式 (CTE) 的基本语法:

WITH
   CTE_NAME1 (column_name) AS (query),
   CTE_NAME2 (column_name) AS (query)
SELECT * FROM CTE_NAME1
UNION ALL
SELECT * FROM CTE_NAME2;

我们可以将多公用表表达式 (CTE) 与各种 SQL 操作结合使用,例如 UNION、UNION ALL、JOIN、INTERSECT 或 EXCEPT。

Example

在这里,我们定义了两个 CTE,分别是 ‘CUSTOMERS_IN_DELHI’ 和 ‘CUSTOMERS_IN_MUMBAI’,以根据他们的地址将客户分成德里和孟买。然后,我们使用 UNION ALL 运算符将这两个 CTE 中的结果合并成一个结果集,检索两个城市中的客户信息。

WITH
CUSTOMERS_IN_DELHI AS (
   SELECT * FROM CUSTOMERS WHERE ADDRESS = 'Delhi'),
CUSTOMERS_IN_MUMBAI AS (
   SELECT * FROM CUSTOMERS WHERE ADDRESS = 'Mumbai')
SELECT ID, NAME, ADDRESS FROM CUSTOMERS_IN_DELHI
UNION ALL
SELECT ID, NAME, ADDRESS FROM CUSTOMERS_IN_MUMBAI;

Output

上述查询的输出如下所示:

ID

NAME

ADDRESS

2

Khilan

Delhi

4

Chaitali

Mumbai

Recursive CTE

公用表表达式是一个查询,它不断循环地引用自己的结果,直到返回一个空结果。

递归查询在其执行期间不断遍历数据的子集,并以自引用方式定义自身。这种自引用机制允许它重复处理和扩展其结果,直到满足停止条件。

要让 CTE 具有递归性,它必须包含一条 UNION ALL 语句,并提供利用 CTE 本身的查询的第二个定义。这允许 CTE 重复引用其自己的结果,在查询中创建递归行为。

Example

现在,我们使用名为 recursive_cust 的递归 CTE 从上面创建的 ‘CUSTOMERS’ 表中检索数据。最初,我们选择薪水高于 3000 的客户,然后使用 UNION ALL 运算符将年龄超过 25 的客户递归追加到结果集中:

WITH recursive_cust (ID, NAME, ADDRESS, AGE) AS (
   SELECT ID, NAME, ADDRESS, AGE
   FROM CUSTOMERS
   WHERE SALARY > 3000
   UNION ALL
   SELECT ID, NAME, ADDRESS, AGE
   FROM CUSTOMERS
   WHERE AGE > 25
)
SELECT * FROM recursive_cust;

Output

执行上述查询时,客户表中所有年龄大于 25 或薪水大于 3000 的数据将递归显示,如下所示:

ID

NAME

ADDRESS

AGE

4

Chaitali

Mumbai

25

5

Hardik

Bhopal

27

6

Komal

Hyderabad

22

7

Muffy

Indore

24

1

Ramesh

Ahmedabad

32

5

Hardik

Bhopal

27

Example

在以下查询中,我们使用名为 Numbers 的递归 CTE 生成并显示 1 到 5 的数字。递归部分不断将 1 添加到前一个值,直到达到 5,从而创建一个序列:

WITH RECURSIVE Numbers AS (
  SELECT 1 AS N
  UNION ALL
  SELECT N + 1 FROM Numbers WHERE N < 5
)
SELECT n FROM Numbers;

Output

执行上述查询后,我们将获得以下输出:

N

1

2

3

4

5

Advantages of CTE

以下是 CTE 的优点:

  1. CTE 使得代码维护更简单。

  2. 它提高了代码的可读性。

  3. 它提高了查询的性能。

  4. CTE 允许简单地实现递归查询。

Disadvantages of CTE

以下为 CTE 的缺点:

  1. CTE 只能被递归成员引用一次。

  2. 我们不能在存储过程中将表变量和 CTE 用作参数。

  3. CTE 可以代替视图使用,但是 CTE 不能嵌套,而视图可以。