MariaDB表表达式之公用表表达式(CTE)

目录
  • 前言
  • 1.非递归CTE
  • 2.递归CTE
    • 2.1 语法
    • 2.2 递归CTE示例(1)
    • 2.2 递归CTE示例(2)
    • 2.2 递归CTE示例(3)
  • 总结

前言

公用表表达式(Common Table Expression,CTE)和派生表类似,都是虚拟的表,但是相比于派生表,CTE具有一些优势和方便之处。

CTE有两种类型:非递归的CTE和递归CTE。

CTE是标准SQL的特性,属于表表达式的一种,MariaDB支持CTE,MySQL 8才开始支持CTE。

1.非递归CTE

CTE是使用WITH子句定义的,包括三个部分:CTE名称cte_name、定义CTE的查询语句inner_query_definition和引用CTE的外部查询语句outer_query_definition。

它的格式如下:

WITH cte_name1[(column_name_list)] AS (inner_query_definition_1)
   [,cte_name2[(column_name_list)] AS (inner_query_definition_2)]
[,...]
outer_query_definition

其中column_name_list指定inner_query_definition中的列列表名,如果不写该选项,则需要保证在inner_query_definition中的列都有名称且唯一,即对列名有两种命名方式:内部命名和外部命名。

注意,outer_quer_definition必须和CTE定义语句同时执行,因为CTE是临时虚拟表,只有立即引用它,它的定义才是有意义的。

下面语句是一个简单的CTE的用法。首先定义一张虚拟表,也就是CTE,然后在外部查询中引用它。

CREATE OR REPLACE TABLE t(id INT NOT NULL PRIMARY KEY,sex CHAR(3),NAME CHAR(20));
INSERT INTO t VALUES (1,'nan','David'),(2,'nv','Mariah'),(3,'nv','gaoxiaofang'),(4,'nan','Jim'),
        (5,'nv','Selina'),(6,'nan','John'),(7,'nan','Monty'),(8,'nv','xiaofang');

# 定义CTE,顺便为每列重新命名,且使用ORDER BY子句
WITH nv_t(myid,mysex,myname) AS (
    SELECT * FROM t WHERE sex='nv' ORDER BY id DESC
)
# 使用CTE
SELECT * FROM nv_t;
+------+-------+-------------+
| myid | mysex | myname      |
+------+-------+-------------+
|    2 | nv    | Mariah      |
|    3 | nv    | gaoxiaofang |
|    5 | nv    | Selina      |
|    8 | nv    | xiaofang    |
+------+-------+-------------+

从结果中可以看到,在CTE的定义语句中使用ORDER BY子句是没有任何作用的。

在这里可以发现,CTE和派生表需要满足的几个共同点:每一列要求有列名,包括计算列;列名必须唯一;不能使用ORDER BY子句,除非使用了TOP关键字(标准SQL严格遵守不能使用ORDER BY的规则,但MySQL/MariaDB中允许)。不仅仅是CTE和派生表,其他表表达式(内联表值函数(sql server才支持)、视图)也都要满足这些条件。究其原因,表表达式的本质是表,尽管它们是虚拟表,也应该满足形成表的条件。

一方面,在关系模型中,表对应的是关系,表中的行对应的是关系模型中的元组,表中的字段(或列)对应的是关系中的属性。属性由三部分组成:属性的名称、属性的类型和属性值。因此要形成表,必须要保证属性的名称,即每一列都有名称,且唯一。

另一方面,关系模型是基于集合的,在集合中是不要求有序的,因此不能在形成表的时候让数据按序排列,即不能使用ORDER BY子句。之所以在使用了TOP后可以使用ORDER BY子句,是因为这个时候的ORDER BY只为TOP提供数据的逻辑提取服务,并不提供排序服务。例如使用ORDER BY帮助TOP选择出前10行,但是这10行数据在形成表的时候不保证是顺序的。

相比派生表,CTE有几个优点:

1.多次引用:避免重复书写。

2.多次定义:避免派生表的嵌套问题。

3.可以使用递归CTE,实现递归查询。

例如:

# 多次引用,避免重复书写
WITH nv_t(myid,mysex,myname) AS (
    SELECT * FROM t WHERE sex='nv'
)
SELECT t1.*,t2.*
FROM nv_t t1 JOIN nv_t t2
WHERE t1.myid = t2.myid+1;

# 多次定义,避免派生表嵌套
WITH
nv_t1 AS (          /* 第一个CTE */
    SELECT * FROM t WHERE sex='nv'
),
nv_t2 AS (          /* 第二个CTE */
    SELECT * FROM nv_t1 WHERE id>3
)
SELECT * FROM nv_t2;

如果上面的语句不使用CTE而使用派生表的方式,则它等价于:

SELECT * FROM
(SELECT * FROM
(SELECT * FROM t WHERE sex='nv') AS nv_t1) AS nv_t2;

2.递归CTE

SQL语言是结构化查询语言,它的递归特性非常差。使用递归CTE可稍微改善这一缺陷。

公用表表达式(CTE)具有一个重要的优点,那就是能够引用其自身,从而创建递归CTE。递归CTE是一个重复执行初始CTE以返回数据子集直到获取完整结果集的公用表表达式。

当某个查询引用递归CTE时,它即被称为递归查询。递归查询通常用于返回分层数据,例如:显示某个组织图中的雇员或物料清单方案(其中父级产品有一个或多个组件,而那些组件可能还有子组件,或者是其他父级产品的组件)中的数据。

递归CTE可以极大地简化在SELECT、INSERT、UPDATE、DELETE或CREATE VIEW语句中运行递归查询所需的代码。

也就是说,递归CTE通过引用自身来实现。它会不断地重复查询每一次递归得到的子集,直到得到最后的结果。这使得它非常适合处理"树状结构"的数据或者有"层次关系"的数据。

2.1 语法

递归cte中包含一个或多个定位点成员,一个或多个递归成员,最后一个定位点成员必须使用"union [all]"(mariadb中的递归CTE只支持union [all]集合算法)联合第一个递归成员。

以下是单个定位点成员、单个递归成员的递归CTE语法:

with recursive cte_name as (
    select_statement_1       /* 该cte_body称为定位点成员 */
  union [all]
    cte_usage_statement      /* 此处引用cte自身,称为递归成员 */
)
outer_definition_statement    /* 对递归CTE的查询,称为递归查询 */

其中:

select_statement_1:称为"定位点成员",这是递归cte中最先执行的部分,也是递归成员开始递归时的数据来源。

cte_usage_statement:称为"递归成员",该语句中必须引用cte自身。它是递归cte中真正开始递归的地方,它首先从定位点成员处获取递归数据来源,然后和其他数据集结合开始递归,每递归一次都将递归结果传递给下一个递归动作,不断重复地查询后,当最终查不出数据时才结束递归。

outer_definition_statement:是对递归cte的查询,这个查询称为"递归查询"。

2.2 递归CTE示例(1)

举个最经典的例子:族谱。

例如,下面是一张族谱表

CREATE OR REPLACE TABLE fork(id INT NOT NULL UNIQUE,NAME CHAR(20),father INT,mother INT);
INSERT INTO fork VALUES
    (1,'chenyi',2,3),(2,'huagner',4,5),(3,'zhangsan',NULL,NULL),
    (4,'lisi',6,7),(5,'wangwu',8,9),(6,'zhaoliu',NULL,NULL),(7,'sunqi',NULL,NULL),
    (8,'songba',NULL,NULL),(9,'yangjiu',NULL,NULL);

MariaDB [test]> select * from fork;
+----+----------+--------+--------+
| id | name     | father | mother |
+----+----------+--------+--------+
|  1 | chenyi   |      2 |      3 |
|  2 | huagner  |      4 |      5 |
|  3 | zhangsan |   NULL |   NULL |
|  4 | lisi     |      6 |      7 |
|  5 | wangwu   |      8 |      9 |
|  6 | zhaoliu  |   NULL |   NULL |
|  7 | sunqi    |   NULL |   NULL |
|  8 | songba   |   NULL |   NULL |
|  9 | yangjiu  |   NULL |   NULL |
+----+----------+--------+--------+

该族谱表对应的结构图:

如果要找族谱中某人的父系,首先在定位点成员中获取要从谁开始找,例如上图中从"陈一"开始找。那么陈一这个记录就是第一个递归成员的数据源,将这个数据源联接族谱表,找到陈一的父亲黄二,该结果将通过union子句结合到上一个"陈一"中。再次对黄二递归,找到李四,再对李四递归找到赵六,对赵六递归后找不到下一个数据,所以这一分支的递归结束。

递归cte的语句如下:

WITH recursive fuxi AS (
    SELECT * FROM fork WHERE `name`='chenyi'
    UNION
    SELECT f.* FROM fork f JOIN fuxi a WHERE f.id=a.father
)
SELECT * FROM fuxi;

演变结果如下:

首先执行定位点部分的语句,得到定位点成员,即结果中的第一行结果集:

根据该定位点成员,开始执行递归语句:

递归时,按照f.id=a.father的条件进行筛选,得到id=2的结果,该结果通过union和之前的数据结合起来,作为下一次递归的数据源fuxi。

再进行第二次递归:

第三次递归:

由于第三次递归后,id=6的father值为null,因此第四次递归的结果为空,于是递归在第四次之后结束。

2.2 递归CTE示例(2)

该CTE示例主要目的是演示切换递归时的字段名称。

例如,有几个公交站点,它们之间的互通性如下图:

对应的表为:

CREATE OR REPLACE TABLE bus_routes (src char(50), dst char(50));
INSERT INTO bus_routes VALUES
  ('stopA','stopB'),('stopB','stopA'),('stopA','stopC'),('stopC','stopB'),('stopC','stopD');
MariaDB [test]> select * from bus_routes;
+-------+-------+
| src   | dst   |
+-------+-------+
| stopA | stopB |
| stopB | stopA |
| stopA | stopC |
| stopC | stopB |
| stopC | stopD |
+-------+-------+

要计算以stopA作为起点,能到达哪些站点的递归CTE如下:

WITH recursive dst_stop AS (
    SELECT src AS dst FROM bus_routes WHERE src='stopA'   /* note: src as dst */
    UNION
    SELECT b.dst FROM bus_routes b
      JOIN dst_stop d
    WHERE d.dst=b.src
)
SELECT * FROM dst_stop;

结果如下:

+-------+
| dst   |
+-------+
| stopA |
| stopB |
| stopC |
| stopD |
+-------+

首先执行定位点语句,得到定位点成员stopA,字段名为dst。

再将定位点成员结果和bus_routes表联接进行第一次递归,如下图:

再进行第二次递归:

再进行第三次递归,但第三次递归过程中,stopD找不到对应的记录,因此递归结束。

2.2 递归CTE示例(3)

仍然是公交路线图:

计算以stopA为起点,可以到达哪些站点,并给出路线图。例如:stopA-->stopC-->stopD。

以下是递归CTE语句:

WITH recursive bus_path(bus_path,bus_dst) AS (
    SELECT src,src FROM bus_routes WHERE src='stopA'
    UNION
    SELECT CONCAT(b2.bus_path,'-->',b1.dst),b1.dst
    FROM bus_routes b1
      JOIN bus_path b2
    WHERE b2.bus_dst = b1.src AND LOCATE(b1.dst,b2.bus_path)=0
)
SELECT * FROM bus_path;

首先获取起点stopA,再获取它的目标stopB和stopC,并将起点到目标使用"-->"连接,即concat(src,"-->","dst")。再根据stopB和stopC,获取它们的目标。stopC的目标为stopD和stopB,stopB的目标为stopA。如果连接成功,那么路线为:

stopA-->stopB-->stopA   目标:stopA
stopA-->stopC-->stopD   目标:stopD
stopA-->stopC-->stopB   目标:stopB

这样会无限递归下去,因此我们要判断何时结束递归。判断的方法是目标不允许出现在路线中,只要出现,说明路线会重复计算。

总结

到此这篇关于MariaDB表表达式之公用表表达式(CTE)的文章就介绍到这了,更多相关MariaDB公用表表达式CTE内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Mac中MariaDB数据库的安装步骤

    前言 MariaDB由MySQL的创始人Michael Widenius主导开发,他早前曾以10亿美元的价格,将自己创建的公司MySQL卖给了SUN,此后,随着SUN被甲骨文收购,MySQL的所有权也落入Oracle的手中.MariaDB名称来自Michael Widenius的女儿Maria的名字.那么在Mac中如何安装MariaDB数据库呢?下面小编就给大家介绍Mac中安装配置MariaDB数据库的方法. MariaDB安装步骤 如果你是Mac上的开发者,通过本文你可以在OS X上通过Hom

  • CentOS 7中成功安装MariaDB的方法教程

    前言 在自己的VPS的CentOS7安装Oracle的Mysql失败以后,我又开始找CentOS7上面安装MariaDB的方法,于是从网上找到了安装MariaDB一篇文章便实践起来,一步步的安装成功了,所以将自己安装的步骤总结处理分享给大家,下面话不多说,来看看详细的介绍吧. 1.查看MariaDB的yum包 执行命令:yum list mariadb* 2.执行安装 执行命令:yum -y install mariadb* 3.启动MariaDB 执行命令:systemctl start ma

  • MySQL分支选择参考:Percona还是MariaDB

    在MySQL被Oracle收购以后,越来越多的人对于MySQL的前景表示了担忧,对于开源的MySQL,或多或少对于Oracle自家的数据库产品产生冲击,这个开源免费的MySQL 对于Oracle更多的是包袱而不是资产.比如淘宝就从Oracle转成了MySQL,一些大型互联网公司也在推行去IOE(I:IBM,O:Oracle,E:EMC),甲骨文公司收购了MySQL后,有将MySQL闭源的潜在风险,因此社区采用分支的方式来避开这个风险. Percona 在介绍 Percona 之前,首要要介绍的是

  • 记一次mariadb数据库无法连接

    自从自己维护blog以后,基本一个月内,都会出现1,2次这种错误,以前解决的办法很简单,就是把虚拟机重启一下就可以.经常是网友在微信,qq,微博提醒我blog挂掉. 刚好国庆期间碰上,就顺便提高一下自己的运维能力,看看具体的原因. tail /var/log/mariadb/mariadb.log 看到大概的错误 161004 11:21:05 InnoDB: Fatal error: cannot allocate memory for the buffer pool 161004 11:21

  • 浅谈MySQL和mariadb区别

    MariaDB是MySQL源代码的一个分支,在意识到Oracle会对MySQL许可做什么后分离了出来(MySQL先后被Sun.Oracle收购).除了作为一个Mysql的"向下替代品",MariaDB包括的一些新特性使它优于MySQL. 这两个数据库究竟有什么本质的区别,我看mariadb文件夹BIN中还是mysql*.exe,除了MySQL会被ORACLE闭源外,而mariadb则开源,他俩之间到底还有什么本质区别没有? 区别一: MariaDB不仅仅是Mysql的一个替代品,它的主

  • MariaDB表表达式之公用表表达式(CTE)

    目录 前言 1.非递归CTE 2.递归CTE 2.1 语法 2.2 递归CTE示例(1) 2.2 递归CTE示例(2) 2.2 递归CTE示例(3) 总结 前言 公用表表达式(Common Table Expression,CTE)和派生表类似,都是虚拟的表,但是相比于派生表,CTE具有一些优势和方便之处. CTE有两种类型:非递归的CTE和递归CTE. CTE是标准SQL的特性,属于表表达式的一种,MariaDB支持CTE,MySQL 8才开始支持CTE. 1.非递归CTE CTE是使用WIT

  • SqlServer使用公用表表达式(CTE)实现无限级树形构建

    SQL Server 2005开始,我们可以直接通过CTE来支持递归查询,CTE即公用表表达式 公用表表达式(CTE),是一个在查询中定义的临时命名结果集将在from子句中使用它.每个CTE仅被定义一次(但在其作用域内可以被引用任意次),并且在该查询生存期间将一直生存.可以使用CTE来执行递归操作. DECLARE @Level INT=3 ;WITH cte_parent(CategoryID,CategoryName,ParentCategoryID,Level) AS ( SELECT c

  • 关于SQL中CTE(公用表表达式)(Common Table Expression)的总结

    一.WITH AS的含义 WITH AS短语,也叫做子查询部分(subquery factoring),可以让你做很多事情,定义一个SQL片断,该SQL片断会被整个SQL语句所用到.有的时候,是为了让SQL语句的可读性更高些,也有可能是在UNION ALL的不同部分,作为提供数据的部分. 特别对于UNION ALL比较有用.因为UNION ALL的每个部分可能相同,但是如果每个部分都去执行一遍的话,则成本太高,所以可以使用WITH AS短语,则只要执行一遍即可.如果WITH AS短语所定义的表名

  • SQL Server 公用表表达式(CTE)实现递归的方法

    公用表表达式简介: 公用表表达式 (CTE) 可以认为是在单个 SELECT.INSERT.UPDATE.DELETE 或 CREATE VIEW 语句的执行范围内定义的临时结果集.CTE 与派生表类似,具体表现在不存储为对象,并且只在查询期间有效.与派生表的不同之处在于,公用表表达式 (CTE) 具有一个重要的优点,那就是能够引用其自身,从而创建递归 CTE.递归 CTE 是一个重复执行初始 CTE 以返回数据子集直到获取完整结果集的公用表表达式. 下面先创建一个表,并插入一些数据: crea

  • SQL2005 学习笔记 公用表表达式(CTE)

    公用表表达式 (CTE) 可以认为是在单个 SELECT.INSERT.UPDATE.DELETE 或 CREATE VIEW 语句的执行范围内定义的临时结果集. CTE 与派生表类似,具体表现在不存储为对象,并且只在查询期间有效. 与派生表的不同之处在于,CTE 可自引用,还可在同一查询中引用多次. CTE可用于: 1.创建递归查询(我个人认为CTE最好用的地方) 2.在同一语句中多次引用生成的表 CTE优点: 使用 CTE 可以获得提高可读性和轻松维护复杂查询的优点. 查询可以分为单独块.简

  • sql server使用公用表表达式CTE通过递归方式编写通用函数自动生成连续数字和日期

    问题: 在数据库脚本开发中,有时需要生成一堆连续数字或者日期,例如yearly report就需要连续数字做年份,例如daily report就需要生成一定时间范围内的每一天日期. 而自带的系统表master..spt_values存在一定的局限性,只是从0到2047(验证脚本:select * from master..spt_values b where b.type = 'P'),也不能直接生成连续日期. 可能大部分人会想到一个笨办法,通过while循环去逐条插入数据到临时表,每次数字加1

  • mysql8 公用表表达式CTE的使用方法实例分析

    本文实例讲述了mysql8 公用表表达式CTE的使用方法.分享给大家供大家参考,具体如下: 公用表表达式CTE就是命名的临时结果集,作用范围是当前语句. 说白点你可以理解成一个可以复用的子查询,当然跟子查询还是有点区别的,CTE可以引用其他CTE,但子查询不能引用其他子查询. 一.cte的语法格式: with_clause: WITH [RECURSIVE] cte_name [(col_name [, col_name] ...)] AS (subquery) [, cte_name [(co

  • SQL Server使用T-SQL进阶之公用表表达式(CTE)

    在编写T-SQL代码时,往往需要临时存储某些结果集.前面我们已经广泛使用和介绍了两种临时存储结果集的方法:临时表和表变量.除此之外,还可以使用公用表表达式的方法. 公用表表达式(Common Table Expression)是SQL Server2005版本的引入的一个特性.CTE可以看组是一个临时的结果集,可以再接下来来的一个SELECT,INSERT,UPDATE,DELETE,MERGE语句中多次引用. 一.3种方法比较 使用公用表达式CTE可以让语句更加清晰简练.与公用表达式作用类似的

  • MySQL/MariaDB 如何实现数据透视表的示例代码

    前文介绍了Oracle 中实现数据透视表的几种方法,今天我们来看看在 MySQL/MariaDB 中如何实现相同的功能. 本文使用的示例数据可以点此下载. 使用 CASE 表达式和分组聚合 数据透视表的本质就是按照行和列的不同组合进行数据分组,然后对结果进行汇总:因此,它和数据库中的分组(GROUP BY)加聚合函数(COUNT.SUM.AVG 等)的功能非常类似. 我们首先使用以下 GROUP BY 子句对销售数据进行分类汇总: select coalesce(product, '[全部产品]

  • MariaDB Spider 数据库分库分表实践记录

    目录 分库分表 部署 MariaDB 实例 Docker 部署 虚拟机部署 MariaDB 配置 检查每个实例 配置 Spider 远程表 基准性能测试 加入后端数据库 哈希分片 根据值范围分片 根据列表分片 分库分表 一般来说,数据库分库分表,有以下做法: 按哈希分片:根据一条数据的标识计算哈希值,将其分配到特定的数据库引擎中: 按范围分片:根据一条数据的标识(一般是值),将其分配到特定的数据库引擎中: 按列表分片:根据某些字段的标识,如果符合条件则分配到特定的数据库引擎中. 分库分表的做法有

随机推荐