MySQL 8.0 新特性之通用表表达式

官方文档:MySQL 8.0 通用表表达式

我在网发布的免费视频讲解 MySQL 8.0 版本新特性

通用表表达式(CTE)是一个在语句级别定义的临时结果集。定义之后,可以在当前语句中多次引用该 CTE。

关于 CTE 优化的信息,可以参考 第 8.2.2.4 节,“使用合并或物化操作优化派生表、视图引用以及通用表表达式”

相关资源

以下文章包含了关于如何在 MySQL 中使用 CTE 的更多信息,包括各种示例:

CTE 语法

通用表表达式使用WITH子句进行定义,该子句可以包含一个或多个逗号分隔的从句。每个从句包含一个子查询,以及指定的名称。以下示例在WITH子句中定义了两个 CTE:cte1 和 cte2,然后在顶层SELECT中进行引用:

WITH
  cte1 AS (SELECT a, b FROM table1),
  cte2 AS (SELECT c, d FROM table2)
SELECT b, d FROM cte1 JOIN cte2
WHERE cte1.a = cte2.c;

在包含WITH子句的查询中,可以使用 CTE 的名称访问相应 CTE 的结果集。

前面定义的 CTE 可以在其他的 CTE 中进行引用,因此 CTE 可以基于前面的 CTE 进行定义。

引用自己的 CTE 被称为递归 CTE。递归 CTE 的使用场景包括生成序列,遍历层次数据或树状结构的数据。

通用表表达式属于 DML 语句的可选部分。它们使用WITH子句进行定义:

with_clause:
    WITH [RECURSIVE]
        cte_name [(col_name [, col_name] ...)] AS (subquery)
        [, cte_name [(col_name [, col_name] ...)] AS (subquery)] ...

cte_name 指定了一个 CTE 名称,它可以在WITH子句中作为一个表进行引用。

AS (subquery) 中的 subquery 被称为“CTE 子查询”,用于产生 CTE 的结果集。AS 后面的括号不能省略。

如果 CTE 的子查询中引用了自己的名称,就被称为递归 CTE。如果WITH子句中包含任何递归 CTE,必须使用关键字RECURSIVE。更多信息,可以参考后文中的递归 CTE。

CTE 的字段名称通过以下方式获得:

  • 如果在 CTE 定义中存在一个带括号的字段名称列表,这些名称就是字段的名称:
WITH cte (col1, col2) AS
(
  SELECT 1, 2
  UNION ALL
  SELECT 3, 4
)
SELECT col1, col2 FROM cte;

列表中的名称数量必须与查询结果中的字段数量相同。

  • 否则,字段名称由 AS (subquery) 中的第一个SELECT语句决定:

    WITH cte AS
    (
      SELECT 1 AS col1, 2 AS col2
      UNION ALL
      SELECT 3, col2 FROM cte;
    

WITH子句可以出现在以下语句中:

  • SELECTUPDATE以及DELETE语句的开始部分:

    WITH ... SELECT ...
    WITH ... UPDATE ...
    WITH ... DELETE ...
    
  • 子查询(包括派生表子查询)的开始部分:

    SELECT ... WHERE id IN (WITH ... SELECT ...) ...
    SELECT * FROM (WITH ... SELECT ...) AS dt ...
    
  • 包含SELECT语句的其他语句中,紧挨着SELECT语句前:

    INSERT ... WITH ... SELECT ...
    REPLACE ... WITH ... SELECT ...
    CREATE TABLE ... WITH ... SELECT ...
    CREATE VIEW ... WITH ... SELECT ...
    DECLARE CURSOR ... WITH ... SELECT ...
    EXPLAIN ... WITH ... SELECT ...
    

在同一个语句级别中只允许存在一个WITH子句。以下语句是无效的:

WITH cte1 AS (...) WITH cte2 AS (...) SELECT ...

有效的语法格式是为一个WITH子句定义多个从句,使用逗号进行分隔:

WITH cte1 AS (...), cte2 AS (...) SELECT ...

不过,不同的语句级别中可以包含多个WITH子句:

WITH cte1 AS (SELECT 1)
SELECT * FROM (WITH cte2 AS (SELECT 2) SELECT * FROM cte2 JOIN cte1) AS dt;

一个WITH子句可以定义一个或多个 CTE,但是每个 CTE 在该子句中必须唯一。以下语法无效:

WITH cte1 AS (...), cte1 AS (...) SELECT ...

正确的语法如下:

WITH cte1 AS (...), cte2 AS (...) SELECT ...

CTE 可以引用自己或者其他的 CTE:

  • 引用自己的 CTE 被称为递归 CTE。

  • CTE 可以引用同一个WITH子句中已经定义的 CTE,但是不能引用后面定义的 CTE。

    这个限制防止了相互递归引用的 CTE,即 cte1 引用 cte2 ,同时 cte2 也引用 cte1。其中一个 CTE 必须引用后面定义的 CTE,而这种方式是不允许的。

  • 查询块中的 CTE 可以引用更外层查询块中定义的 CTE,但是不能引用更内层查询块中定义的 CTE。

如果查询引用的对象存在多个同名的对象,派生表优先级最高,其次是 CTE,最后是基础表、临时表和视图。名称解析时先查找相同查询块中的对象;如果没有找到对象,再查找外部查询块。

与派生表类似,MySQL 8.0.14 之前 CTE 不能引用外部查询中的对象。这不是 SQL 标准的限制,而是 MySQL 的限制,并且在 MySQL 8.0.14 得到了解决。

递归 CTE

如果某个 CTE 在子查询中引用了自己,就称为递归 CTE。例如:

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

以上语句的执行结果是一个连续的数字序列:

+------+
| n    |
+------+
|    1 |
|    2 |
|    3 |
|    4 |
|    5 |
+------+

递归 CTE 包含以下结构:

  • 如果在WITH子句中引用了自己,WITH子句必须使用WITH RECURSIVE。(如果没有 CTE 引用自己,也可以使用RECURSIVE,但不强制。)

    如果在递归CTE语句中缺少了RECURSIVE,将会产生类似以下的错误:

    ERROR 1146 (42S02): Table 'cte_name' doesn't exist
    
  • 递归 CTE 的子查询由两部分组成,中间使用UNION [ALL]或者UNION DISTINCT进行连接:

    SELECT ...      -- return initial row set
    UNION ALL
    SELECT ...      -- return additional row sets
    

    第一个SELECT语句用于生成初始数据行,该语句不会引用 CTE 自身。第二个SELECT语句在它的FROM子句中引用了 CTE自身,通过递归产生更多的结果。当第二个语句不会产生更多的新数据时结束递归。因此,递归 CTE由一个非递归的SELECT语句和一个递归的SELECT语句组成。

    每个SELECT语句自身可以由多个SELECT语句组成。

  • CTE 最终结果中的字段类型由非递归的SELECT语句决定,所有字段都可以为空。查询结果的字段类型与递归SELECT语句无关。

  • 如果递归部分和非递归部分使用UNION DISTINCT进行连接,查询结果将会排除重复的数据行。这种方式可以用于执行传递闭包(transitive closure,例如两个地点之间的乘车路线)的查询,防止无限循环。

  • 递归部分的每次迭代只针对上次迭代生成的新数据行进行操作。如果递归部分包含多个查询块,迭代时每个查询块的执行顺序不固定,每个查询块基于它自己前一次迭代的结果,或者上次迭代结束后其他查询块生成的结果进行操作。

前面递归 CTE 示例中的非递归语句如下,它会产生一条初始化的数据:

SELECT 1

它的递归部分如下:

SELECT n + 1 FROM cte WHERE n < 5

每次迭代时,SELECT语句将会产生一个比上一次结果中的 n 大 1 的新值。第一次迭代基于初始值(1)进行操作,生成 1+1=2;第二次迭代基于第一次迭代的结果(2),生成 2+1=3;如此等等。迭代一直执行到递归结束,此处为 n 的值大于或等于 5。

如果递归部分产生的结果比非递归部分的字段长度更大,需要在非递归部分指定一个更宽的字段类型,避免数据被截断。例如:

WITH RECURSIVE cte AS
(
  SELECT 1 AS n, 'abc' AS str
  UNION ALL
  SELECT n + 1, CONCAT(str, str) FROM cte WHERE n < 3
)
SELECT * FROM cte;

在非严格的 SQL 模式中,该语句将会产生以下结果:

+------+------+
| n    | str  |
+------+------+
|    1 | abc  |
|    2 | abc  |
|    3 | abc  |
+------+------+

字段 str 的值都显示为 ‘abc’,因为非递归SELECT语句决定了字段的宽度。 因此,超过 3 个字符的 str 值都会被截断。

在严格的 SQL 模式中,以上语句将会产生错误:

ERROR 1406 (22001): Data too long for column 'str' at row 1

为了解决这个问题,既不会截断结果,也不会产生错误,可以在非递归SELECT语句中使用 CAST() 函数将 str 定义为更宽的类型:

WITH RECURSIVE cte AS
(
  SELECT 1 AS n, CAST('abc' AS CHAR(20)) AS str
  UNION ALL
  SELECT n + 1, str) FROM cte WHERE n < 3
)
SELECT * FROM cte;

现在,该语句将会产生正确的结果,不会截断数据:

+------+--------------+
| n    | str          |
+------+--------------+
|    1 | abc          |
|    2 | abcabc       |
|    3 | abcabcabcabc |
+------+--------------+

在递归引用部分,字段通过名称进行引用,而不是位置;因此递归部分引用的字段与非递归部分的字段可以顺序不同。例如:

WITH RECURSIVE cte AS
(
  SELECT 1 AS n, 1 AS p, -1 AS q
  UNION ALL
  SELECT n + 1, q * 2, p * 2 FROM cte WHERE n < 5
)
SELECT * FROM cte;

由于每一行中的 p 基于前一行中的 q 获得,而 q 基于前一行中的 p 获得,每一行中正负号将会交替出现:

+------+------+------+
| n    | p    | q    |
+------+------+------+
|    1 |    1 |   -1 |
|    2 |   -2 |    2 |
|    3 |    4 |   -4 |
|    4 |   -8 |    8 |
|    5 |   16 |  -16 |
+------+------+------+

递归 CTE 的子查询存在一些语法限制:

  • 递归SELECT部分不能包含以下内容:
    • 聚合函数,例如 SUM()
    • 窗口函数
    • GROUP BY
    • ORDER BY
    • LIMIT
    • DISTINCT
      递归 CTE 的非递归SELECT部分没有这个限制。UNION两边的查询语句中不允许使用DISTINCTUNION DISTINCT可以使用。
  • 递归SELECT部分只能引用一次 CTE 自身,并且只能在FROM子句中引用,而不能在任何子查询中引用。它可以引用其他的表,并且可以将它们与 CTE 进行连接查询。对于这种连接查询,CTE 不能出现在LEFT JOIN的右侧。

除了不能使用ORDER BYLIMIT以及DISTINCT之外,其他限制来自于 SQL 标准,而不是 MySQL 实现。

对于递归CTE,EXPLAIN 命令的输出结果中,关于递归SELECT部分的信息在 Extra 列显示为 Recursive。

explain
WITH RECURSIVE cte AS
(
  SELECT 1 AS n, p * 2 FROM cte WHERE n < 5
)
SELECT * FROM cte;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+------------------------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                  |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+------------------------+
|  1 | PRIMARY     | <derived2> | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | NULL                   |
|  2 | DERIVED     | NULL       | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | No tables used         |
|  3 | UNION       | cte        | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |    50.00 | Recursive; Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+------------------------+
3 rows in set, 1 warning (0.04 sec)

EXPLAIN 输出的成本评估表示的是每次迭代的成本,与总成本可能相差很大。优化器无法预测迭代次数,因此它不能预测WHERE子句的条件。

CTE 的实际成本还会受到结果集大小的影响。如果 CTE 产生了很多行数据,可能导致需要一个很大的内部临时表,导致内存临时表转换为磁盘临时表,从而产生性能问题。如果发生了这种情况,增加允许创建了内存临时表大大小可以提高性能;参见 第 8.4.4 节, “MySQL 内部使用的临时表”

限制 CTE 递归

对于递归 CTE 而言,需要在递归SELECT部分包含一个终止递归的条件。作为一个防止无限递归 CTE 的方法,可以设置一个执行时间的限制:

  • 系统变量 cte_max_recursion_depth 用于设置 CTE 递归的次数限制。如果 CTE 递归的次数超过了该变量的值,服务器将会强制终止语句的执行。
  • 系统变量 max_execution_time 用于设置当前会话中查询语句的超时时间。
  • 优化器提示 MAX_EXECUTION_TIME 用于设置当前查询语句的超时时间。

考虑以下递归 CTE,它没有包含递归终止条件:

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte
)
SELECT * FROM cte;

默认情况下,cte_max_recursion_depth 的值为 1000,使得以上 CTE 在递归 1000 次之后被终止。应用程序可以通过修改会话级别的参数值,以满足特定的需求:

SET SESSION cte_max_recursion_depth = 10;      -- permit only shallow recursion
SET SESSION cte_max_recursion_depth = 1000000; -- permit deeper recursion

也可以设置全局的 cte_max_recursion_depth 参数值,随后连接的所有会话都会受到影响。

对于执行和递归缓慢的查询,或者需要将 cte_max_recursion_depth 设置为一个很大值的环境中,可以通过会话级别的执行超时来控制递归的次数。方法就是在 CTE 语句之前执行以下语句:

SET max_execution_time = 1000; -- impose one second timeout

或者,也可以在 CTE 语句中使用优化器提示:

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte
)
SELECT /*+ MAX_EXECUTION_TIME(1000) */ * FROM cte;

如果某个递归查询由于没有执行时间限制而导致了死循环,可以从另一个会话中使用 KILL QUERY 命令终止该查询。对于该会话自身而言,运行查询语句的客户端程序也可以能提供了终止查询的方法。例如,在 mysql 客户端中,输入 Control+C 将会终止当前语句。

递归通用表表达式示例

如前所述,递归通用表表达式(CTE)经常被用于生成序列数据或者遍历层级或树状结构数据。以下是一些简单的示例。

  • 生成斐波纳契数列
  • 生成日期序列
  • 遍历层次数据

生成斐波纳契数列

斐波那契数列(Fibonacci series)从数字 0 和 1(或者从 1 和 1)开始,后面的每个数字等于它前面两个数字之和。如果递归SELECT中的每一行都基于前面两个数列值求和,就能生成一个斐波纳契数列。下面这个 CTE 生成了一个 10 个数字的序列,最开始的两个数字分别为 0 和 1:

WITH RECURSIVE fibonacci (n, fib_n, next_fib_n) AS
(
  SELECT 1, 0, 1
  UNION ALL
  SELECT n + 1, next_fib_n, fib_n + next_fib_n
    FROM fibonacci WHERE n < 10
)
SELECT * FROM fibonacci;
The CTE produces this result:

+------+-------+------------+
| n    | fib_n | next_fib_n |
+------+-------+------------+
|    1 |     0 |          1 |
|    2 |     1 |          1 |
|    3 |     1 |          2 |
|    4 |     2 |          3 |
|    5 |     3 |          5 |
|    6 |     5 |          8 |
|    7 |     8 |         13 |
|    8 |    13 |         21 |
|    9 |    21 |         34 |
|   10 |    34 |         55 |
+------+-------+------------+

该 CTE 语句的执行过程如下:

  • 字段 n 表示该行包含了第 n 个斐波那契数列值。例如,第 8 个数列值为 13。
  • 字段 fib_n 表示斐波那契数列值 n。
  • 字段 next_fib_n 表示 n 之后的下一个斐波那契数列值。下一行数据可以使用这个值计算它的 next_fib_n。
  • 当 n 到达 10 之后,结果递归过程。这个值限制了返回的数列大小。

前面的示例显示了整个 CTE 的结果。如果只想返回部分数列值,可以在外层的SELECT语句中添加一个WHERE条件。例如,以下查询只返回第 8 个斐波那契数列值:

mysql> WITH RECURSIVE fibonacci ...
       ...
       SELECT fib_n FROM fibonacci WHERE n = 8;
+-------+
| fib_n |
+-------+
|    13 |
+-------+

生成日期序列

通用表表达式可以用于生成一个连续的日期序列,这种序列可以用于产生汇总信息,对于日期序列中的每个值都存在一个对应的数据,包括汇总数据中不存在的日期。

假设某个销售数据表包含以下内容:

mysql> SELECT * FROM sales ORDER BY date, price;
+------------+--------+
| date       | price  |
+------------+--------+
| 2017-01-03 | 100.00 |
| 2017-01-03 | 200.00 |
| 2017-01-06 |  50.00 |
| 2017-01-08 |  10.00 |
| 2017-01-08 |  20.00 |
| 2017-01-08 | 150.00 |
| 2017-01-10 |   5.00 |
+------------+--------+

以下查询按天对销量进行汇总:

mysql> SELECT date, SUM(price) AS sum_price
       FROM sales
       GROUP BY date
       ORDER BY date;
+------------+-----------+
| date       | sum_price |
+------------+-----------+
| 2017-01-03 |    300.00 |
| 2017-01-06 |     50.00 |
| 2017-01-08 |    180.00 |
| 2017-01-10 |      5.00 |
+------------+-----------+

不过,查询的结果没有显示所有日期的销售数据,比如 2017-01-04。如果想要显示所有日期的数据,可以使用一个递归 CTE 生成完整的日期,然后通过LEFT JOIN与销售数据进行连接查询。

以下 CTE 用于生成一个连续的日期序列:

WITH RECURSIVE dates (date) AS
(
  SELECT MIN(date) FROM sales
  UNION ALL
  SELECT date + INTERVAL 1 DAY FROM dates
  WHERE date + INTERVAL 1 DAY <= (SELECT MAX(date) FROM sales)
)
SELECT * FROM dates;
The CTE produces this result:

+------------+
| date       |
+------------+
| 2017-01-03 |
| 2017-01-04 |
| 2017-01-05 |
| 2017-01-06 |
| 2017-01-07 |
| 2017-01-08 |
| 2017-01-09 |
| 2017-01-10 |
+------------+

该 CTE 语句的执行过程如下:

  • 非递归SELECT生成销售数据中包含的最小日期值。
  • 递归SELECT基于前一行数据,增加一天的时间,生成新的日期。
  • 当递归的日期到达销售数据中的最大日期时,结束递归。

将 CTE 与销售数据表进行LEFT JOIN连接,为日期序列中的每个值生成一个汇总:

WITH RECURSIVE dates (date) AS
(
  SELECT MIN(date) FROM sales
  UNION ALL
  SELECT date + INTERVAL 1 DAY FROM dates
  WHERE date + INTERVAL 1 DAY <= (SELECT MAX(date) FROM sales)
)
SELECT dates.date, COALESCE(SUM(price), 0) AS sum_price
FROM dates LEFT JOIN sales ON dates.date = sales.date
GROUP BY dates.date
ORDER BY dates.date;

最终的结果如下:

+------------+-----------+
| date       | sum_price |
+------------+-----------+
| 2017-01-03 |    300.00 |
| 2017-01-04 |      0.00 |
| 2017-01-05 |      0.00 |
| 2017-01-06 |     50.00 |
| 2017-01-07 |      0.00 |
| 2017-01-08 |    180.00 |
| 2017-01-09 |      0.00 |
| 2017-01-10 |      5.00 |
+------------+-----------+

注意事项:

  • 查询的性能是否存在问题,尤其是递归SELECT中的每一次都需要执行的 MAX() 子查询? 通过EXPLAIN可以看出系统对该子查询进行了优化,只需要执行一次。
  • COALESCE() 函数将销售表中不存在的日期对应的 sum_price 字段显示为 0,而不是 NULL。

遍历层次数据

递归通用表表达式可以用于遍历具有层次结构的数据。以下语句创建了一个小的数据表,用于存储员工的信息,包括员工名字、ID 编号以及员工经理的 ID 编号。最顶层的员工(CEO),对应的经理编号为 NULL(该员工没有经理)。

CREATE TABLE employees (
  id         INT PRIMARY KEY NOT NULL,
  name       VARCHAR(100) NOT NULL,
  manager_id INT NULL,
  INDEX (manager_id),
FOREIGN KEY (manager_id) REFERENCES EMPLOYEES (id)
);
INSERT INTO employees VALUES
(333, "Yasmina", NULL),  # Yasmina is the CEO (manager_id is NULL)
(198, "John", 333),      # John has ID 198 and reports to 333 (Yasmina)
(692, "Tarek",
(29, "Pedro", 198),
(4610, "Sarah", 29),
(72, "Pierre",
(123, "Adil", 692);

表中的数据如下:

mysql> SELECT * FROM employees ORDER BY id;
+------+---------+------------+
| id   | name    | manager_id |
+------+---------+------------+
|   29 | Pedro   |        198 |
|   72 | Pierre  |         29 |
|  123 | Adil    |        692 |
|  198 | John    |        333 |
|  333 | Yasmina |       NULL |
|  692 | Tarek   |        333 |
| 4610 | Sarah   |         29 |
+------+---------+------------+

如果想要生成一个组织结构图,显示每个员工的管理链(也就是从 CEO 到员工的管理路径),可以使用以下递归 CTE 查询:

WITH RECURSIVE employee_paths (id, name, path) AS
(
  SELECT id, CAST(id AS CHAR(200))
    FROM employees
    WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.name, CONCAT(ep.path, ',', e.id)
    FROM employee_paths AS ep JOIN employees AS e
      ON ep.id = e.manager_id
)
SELECT * FROM employee_paths ORDER BY path;
The CTE produces this output:

+------+---------+-----------------+
| id   | name    | path            |
+------+---------+-----------------+
|  333 | Yasmina | 333             |
|  198 | John    | 333,198         |
|   29 | Pedro   | 333,198,29      |
| 4610 | Sarah   | 333,29,4610 |
|   72 | Pierre  | 333,72   |
|  692 | Tarek   | 333,692         |
|  123 | Adil    | 333,692,123     |
+------+---------+-----------------+

示例 CTE 语句的执行过程如下:

  • 非递归的SELECT产生 CEO 的记录(经理 ID 为空的数据行)。

    字段 path 被转化为 CHAR(200) ,以确保能够存储后面的递归SELECT产生的更长 path 值。

  • 对于递归SELECT生成的每一行,都会查找前面一行员工直接管理的所有员工。对于找到的每一个员工,对应的行都会包含该员工的 ID 和名字,以及相应的管理链。管理链包含了经理的管理链,后面加上当前员工的 ID。

  • 如果找不到对应的下属员工,递归执行结束。

如果想要查找某个或者某些指定的员工,可以在最外层的SELECT语句中增加一个WHERE子句。例如,以下语句用于查询Tarek 和 Sarah 的信息:

mysql> WITH RECURSIVE ...
       ...
       SELECT * FROM employees_extended
       WHERE id IN (692, 4610)
       ORDER BY path;
+------+-------+-----------------+
| id   | name  | path            |
+------+-------+-----------------+
| 4610 | Sarah | 333,4610 |
|  692 | Tarek | 333,692         |
+------+-------+-----------------+

通用表表达式与相似概念的比较

通用表表达式(CTE)与派生表(derived table)存在以下相似之处:

  • 两者都有一个名称。
  • 两者都在单个语句范围内有效。

由于这些相似性,CTE 和派生表经常可以互相替换。举个简单的例子,以下是两个等价的语句:

WITH cte AS (SELECT 1) SELECT * FROM cte;
SELECT * FROM (SELECT 1) AS dt;

不过,CTE 相比于派生表,还具有以下优势:

  • 派生表在查询中只能被引用一次,CTE 可以多次引用。如果想要多次使用某个派生表的结果,必须多次生成相同的派生表。
  • CTE 支持自引用(递归)。
  • CTE 可以引用其他 CTE。
  • CTE 可读性更好,它的定义位于语句的开始,而不是包含在其中。

CTE 与通过CREATE [TEMPORARY] TABLE创建的表类似,但是不需要显式定义和删除。使用 CTE 时不需要拥有创建表的权限。

其他数据库产品功能实现:
Oracle 18c 子查询因子从句
SQL Server 2017 通用表表达式
PostgreSQL 11 通用表表达式
Db2 11 通用表表达式
SQLite 通用表表达式

人生本来短暂,你又何必匆匆!点个赞再走吧!

原文地址:https://tonydong.blog.csdn.net

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


在正式开始之前,我们先来看下 MySQL 服务器的配置和版本号信息,如下图所示: “兵马未动粮草先行”,看完了相关的配置之后,我们先来创建一张测试表和一些测试数据。 -- 如果存在 person 表先删除 DROP TABLE IF EXISTS person; -- 创建 person 表,其中
&gt; [合辑地址:MySQL全面瓦解](https://www.cnblogs.com/wzh2010/category/1859594.html &quot;合辑地址:MySQL全面瓦解&quot;) # 1 为什么需要数据库备份 - 灾难恢复:当发生数据灾难的时候,需要对损坏的数据进行恢复和
物理服务机的CPU、内存、存储设备、连接数等资源有限,某个时段大量连接同时执行操作,会导致数据库在处理上遇到性能瓶颈。为了解决这个问题,行业先驱门充分发扬了分而治之的思想,对大库表进行分割,&#xA;然后实施更好的控制和管理,同时使用多台机器的CPU、内存、存储,提供更好的性能。而分治有两种实现方式:垂直拆
1 回顾 上一节我们详细讲解了如何对数据库进行分区操作,包括了 垂直拆分(Scale Up 纵向扩展)和&#160;水平拆分(Scale Out 横向扩展) ,同时简要整理了水平分区的几种策略,现在来回顾一下。 2 水平分区的5种策略 2.1 Hash(哈希) 这种策略是通过对表的一个或多个列的Ha
navicat查看某个表的所有字段的详细信息 navicat设计表只能一次查看一个字段的备注信息,那怎么才能做到一次性查询表的信息呢?SELECT COLUMN_NAME,COLUMN_COMMENT,COLUMN_TYPE,COLUMN_KEY FROM information_schema.CO
文章浏览阅读4.3k次。转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52768613前言:数据库每天的数据不断增多,自动删除机制总体风险太大,想保留更多历史性的数据供查询,于是从小的hbase换到大的hbase上,势在必行。今天记录下这次数据仓库迁移。看下Agenda:彻底卸载MySQL安装MySQL_linux服务器进行数据迁移
文章浏览阅读488次。恢复步骤概要备份frm、ibd文件如果mysql版本发生变化,安装回原本的mysql版本创建和原本库名一致新库,字符集都要保持一样通过frm获取到原先的表结构,通过的得到的表结构创建一个和原先结构一样的空表。使用“ALTER TABLE DISCARD TABLESPACE;”命令卸载掉表空间将原先的ibd拷贝到mysql的仓库下添加用户权限 “chown . .ibd”,如果是操作和mysql的使用权限一致可以跳过通过“ALTER TABLE IMPORT TABLESPACE;”命令恢_alter table discard tablespace
文章浏览阅读225次。当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化:单表优化除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑、部署、运维的各种复杂度,一般以整型值为主的表在千万级以下,字符串为主的表在五百万以下是没有太大问题的。而事实上很多时候MySQL单表的性能依然有不少优化空间,甚至能正常支撑千万级以上的数据量:字段尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNEDVARCHAR的长度只分配_开发项目 浏览记录表 过大怎么办
文章浏览阅读1.5k次。Mysql创建、删除用户MySql中添加用户,新建数据库,用户授权,删除用户,修改密码(注意每行后边都跟个;表示一个命令语句结束):1.新建用户登录MYSQL:@>mysql -u root -p@>密码创建用户:mysql> insert into mysql.user(Host,User,Password) values("localhost_删除mysql用户组
MySQL是一种开源的关系型数据库管理系统,被广泛应用于各类应用程序的开发中。对于MySQL中的字段,我们需要进行数据类型以及默认值的设置,这对于数据的存储和使用至关重要。其中,有一个非常重要的概念就是MySQL字段默认字符串。 CREATE TABLE `my_...
MySQL是一个流行的开源关系型数据库管理系统,广泛应用于Web应用程序开发、数据存储和管理。在使用MySQL时,正确设置字符集非常重要,以确保数据的正确性和可靠性。 在MySQL中,字符集表示为一系列字符和字母的集合。MySQL支持多种字符集,包括ASCII、UTF...
MySQL存储函数 n以内偶数 MySQL存储函数能够帮助用户简化操作,提高效率,常常被用于计算和处理数据。下面我们就来了解一下如何使用MySQL存储函数计算n以内的偶数。 定义存储函数 首先,我们需要定义一个MySQL存储函数,以计算n以内的偶数。下...
MySQL是一个流行的关系型数据库管理系统,基于客户机-服务器模式,可在各种操作系统上运行。 MySQL支持多种字符集,不同的字符集包括不同的字符,如字母、数字、符号等,并提供不同的排序规则,以满足不同语言环境的需求。 //查看MySQL支持的字符集与校对规...
在MySQL数据库中,我们有时需要对特定的字符串进行截取并进行分组统计。这种操作对于数据分析和报表制作有着重要的应用。下面我们将讲解一些基本的字符串截取和分组统计的方法。 首先,我们可以使用substring函数对字段中的字符串进行截取。假设我们有一张表stude...
MySQL提供了多种字符串的查找函数。下面我们就一一介绍。 1. LIKE函数 SELECT * FROM mytable WHERE mycolumn LIKE 'apple%'; 其中"apple%"表示以apple开头的字符串,%表示任意多个字符...
MySQL 是一种关系型数据库管理系统,广泛应用于各种不同规模和类型的应用程序中。在 MySQL 中,处理字符串数据是很常见的任务。有时候,我们需要在字符串的开头添加一定数量的 0 ,以达到一定的位数。比如,我们可能需要将一个数字转换为 4 位或 5 位的字符串,不足的...
MySQL是一种流行的关系型数据库管理系统,支持多种数据类型。以下是MySQL所支持的数据类型: 1. 数值型数据类型: - TINYINT 保存-128到127范围内的整数 - SMALLINT 保存-32768到32767范围内的整数 - MEDIU...
MySQL中存储Emoji表情字段类型 在现代互联网生态中,表情符号已经成为人们展示情感和思想的重要方式之一,因此将表情符号存储到数据库中是一个经常出现的问题。MySQL作为最流行的开源关系型数据库管理系统之一,也需要能够存储和管理这些表情符号的字段类型。 UT...
MySQL是一种关系型数据库管理系统。在MySQL数据库中,有多种不同的数据类型。而其中,最常见的数据类型之一就是字符串类型。在MySQL中,字符串类型的数据通常会被存储为TEXT或VARCHAR类型。 首先,让我们来看一下VARCHAR类型。VARCHAR是My...
MySQL字符串取整知识详解 MySQL是一种开源的关系型数据库管理系统,广泛应用于各个领域。在使用MySQL过程当中,我们经常需要对数据进行取整操作。本文将介绍如何使用MySQL字符串取整来处理数据取整问题。 什么是MySQL字符串取整? MySQL...