【新星计划】数据库 CTE 初识

CTE

CTE指的是公共表表达式(Common Table Expression)

在日常我们使用数据库的时候,通常在一些数据汇总计算的时候,会用到临时表,或者变量或者其他辅助手段,来保存一些数据,然后与现有数据进行计算。

一个非常典型的例子,比如有一次考试,统计各个分数段的考生各有多少人,占考生比例为多少,平均分是多少,大于平均分的有多少人,占比多少。

在这个需求中,我们可以用以下几种方法

1、使用变量存储两个数据,平均值和总数
2、使用临时表存储这两个数据
3、使用子查询得出这两个数据

然后和汇总数据进行比较得出最后结果,但是,如果需求再复杂一点的时候,比如各个班的平均分是多少,总平均分是多少,那么使用变量方式就无法做到了。

而使用子查询方式,有一个很大的痛点,那就是,每个子查询只能使用一次,无法复用。

最后,临时表方式能解决这些问题,但是,也有不方便的地方,我们需要将查询分成多个部分,最后还要记得注销临时表

由于以上三种方式都有各自的痛点,所以在 SqlServer 2005 的时候,追加了另外一种方式,也就是 CTE 查询方式。而在 MySql ,则在 MySql v8 以上版本也得到了支持。

使用 CTE 有一些基本的优点:

1、查询结果可复用
2、可对查询结果生成多个 CTE 结果,互不干扰
3、数据存放在内存中,每次使用完毕后自动注销

CSDN 文盲老顾的博客https://blog.csdn.net/supewrfei

CTE 语法

# MySql & MsSql
with cte as (
    select data1 colName1,data2 colName2
)
select * from cte;

with cte (colName1,colName2) as (
	select 1,2
)
select * from cte

基本 CTE 语法,在 MySql 和 MsSql 都一样,唯一需要注意的是,当出现多个查询时,MySql 因为每个指令都要求有分号,所以不会出现异常情况。而 MsSql 因为可以多个指令之间不用分号间隔,所以在多个指令中使用 cte 时,要在 with 前加一个分号,强制结束之前的指令,避免出现将 with 当做前一个指令查询后续部分。

老顾用 CTE 基本查询,更多的是用来模拟数据,减少了临时表的操作。

CTE 递归

在数据库中,使用了CTE后,发现优势比临时表多不到哪里去,一点也体现不出存放在内存中的优势,所以 CTE 做了一个骚操作:递归。

CTE 递归语法

# MySql 
with RECURSIVE CTE as (
    select data1 
    from sourceTable
    union all
    select * 
    from sourceTable,CTE
)
select * from CTE

# MsSql
    select data1 
    from sourceTable
    union all
    select * 
    from sourceTable,CTE
)
select * from CTE

在这里稍微有一点点不同,MySql 如果要使用CTE递归,必须对这个 CTE 做一个RECURSIVE 关键词修饰,而 MsSql 则无需多余的修饰。

递归示例

对于递归,我们先用一个最简单的例子来展示一下,生成100行数据,每行数据一个列,存放一个数字表示当前行顺序

with RECURSIVE t as (
	select 1 id
	union all
	select id + 1 
	from t
	where id < 100
)
select * from t;

指令通用的,MsSql 去掉 recursive 关键字修饰即可使用。通过这个例子,我们可以很方便的得到一个连续的数字区间。这对于数据补全丢失部分,有很大的帮助。

在 MsSql 中,递归默认不允许超出递归100次,如果一定要超出这个限制,需要通过 option 来指定递归限制数量,需要注意的是,该参数是在使用递归数据的查询位置,而不是在递归内

with t as (
	select 1 id
	union all
	select id+ 1 from t where id<200
) 
select * from t option(MAXRECURSION 0)

递归机制

那么,如果我们想知道,到底递归是怎么进行的,比如一次递归可以产生多少数据,每次递归都传递了哪些内容。

那么老顾做一个查询,来测试一下。

with RECURSIVE t as (
	select 1 id
	union all
	select id + 1 
	from t
	where id < 100
),t1 as (
	select *,1 tm 
	from t 
	where id < 5
	union all
	select t.*,tm + 1 
	from t,t1 
	where t.id=t1.id+1
)
select * from t1
order by tm,id;

在这里插入图片描述
我们对于 t1 这个 cte 递归表的第一部分,选择了四条数据,然后递归的时候,每次都会得到另外四条数据,我们通过 tm 就可以看到递归的层级。
在这里插入图片描述
然后,我们借助一下排名函数,来确定一下递归的更进一步的机制。

3575 - Recursive Common Table Expression ‘t1’ can contain neither aggregation nor window functions in recursive query block

但由于 MySql 不能在递归内使用排名函数,所以这次用MsSql 的指令看看结果。

with t as (
	select 1 id
	union all
	select id + 1 
	from t
	where id < 100
),t1 as (
	select *,1 tm,row_number() over(order by id) grp 
	from t 
	where id < 5
	union all
	select t.*,tm + 1,row_number() over(order by t.id) grp  
	from t,t1 
	where t.id=t1.id+1
)
select * from t1;

在这里插入图片描述
在这里插入图片描述

可以看到,除了递归入口的四条数据,其他数据的排名结果都是1,所以递归机制基本可以确定了。

1、递归方式为每次选出一条数据,作为递归数据传递
2、递归可以由一条数据产生多条数据,但不得产生与递归数据相同的数据,避免进入死循环
3、递归应该有跳出条件,或者是数据遍历完,或者是条件限制不满足
4、递归前后的列数保持一致,数据类型保持一致

然后就是不同的数据库,对 CTE 使用的限制了,这个需要各位自己慢慢体会了。比如刚才提到,MySql 中,CTE 递归不能使用排名函数。之前老顾的文章中提到,MsSql CTE 递归中,不能使用关联,不能使用行转列之类的。

几个CTE 递归的示例

由于 MsSql 和 MySql 的基础查询都差不多,我就只写一个了,大家自行调整适配自己的环境即可。

阶乘

嗯,就是一个简单示例,因为数据库中有数据类型,整型范围较小,所以生成不了多少数据

with t as (
	select 1 id,convert(bigint,1) as p
	union all
	select id + 1,p * (id + 1) 
	from t
	where id <= 15
)
select * from t order by id

在这里插入图片描述

斐波那契序列

with t as (
	select 1 num,convert(bigint,1) n1,convert(bigint,1) n2
	union all
	select num + 1,n2,n1 + n2 
	from t where num<50
) 
select * from t

在这里插入图片描述

无限级分类路径

with t as (
	select 1 id,0 parent,'顶级分类1' name
	union all select 2,0,'顶级分类2'
	union all select 3,1,'大项1'
	union all select 4,2,'大项2'
	union all select 5,1,'大项3'
	union all select 6,3,'小项1'
	union all select 7,3,'小项2'
	union all select 8,5,'小项3'
	union all select 9,5,'小项4'
	union all select 10,7,'细项1'
	union all select 11,7,'细项2'
),t1 as (
	select *,convert(varchar(max),id) pth,convert(nvarchar(max),name) npth 
	from t
	where parent=0
	union ALL
	select a.*,pth + '|' + convert(varchar,a.id),npth + ' => ' + a.name
	from t a,t1 b
	where a.parent=b.id
) 
select * from t1 order by pth

在这里插入图片描述
这个算是比较常用的了,扩展一下,加上每个项的排序也生成一个路径,那么基本就可以当做最后的内容使用了,直接做成视图。

rand 函数

不管是 mysql还是mssql,随机函数都是 rand,我们可以用 select rand() 得到一个随机的浮点数。

但是,在这两个数据库中, rand 的表现不尽相同,例如,我们在刚才的 cte 生成的 100行数据里,加上 rand() 方法,看看最后的区别。

# MySql
with RECURSIVE t as (
	select 1 id
	union all
	select id + 1 
	from t
	where id < 100
)
select *,rand() from t
order by id

在这里插入图片描述
MySql一切正常,每行都有一个随机数。

在看 MsSql 的表现。

#MsSql
with t as (
	select 1 id
	union all
	select id + 1 
	from t
	where id < 100
)
select *,rand() from t order by id

在这里插入图片描述
这是见了鬼了,好在不管是 MySql 还是 MsSql 都可以对 rand 函数指定一个种子,我们可以先生成一次固定的100行数据。

#MySql
with RECURSIVE t as (
	select 1 id
	union all
	select id + 1 
	from t
	where id < 100
)
select *,rand(id) from t
order by id

#MsSql
with t as (
	select 1 id
	union all
	select id + 1 
	from t
	where id < 100
)
select *,rand(id * 10000) from t order by id

MySql 结果
在这里插入图片描述
MsSql 结果
在这里插入图片描述
当然,MsSql 可以使用 checksum 和 newid 来辅助生成真正的随机数,毕竟 newid 是可以跨行的随机序列。

那么,在有了随机数的情况下,我们就可以使用 cte 递归来生成一些数据,而不用通过循环、游标之类的方式了。

小结

本文主要介绍了 CTE 的一些基本知识点,在无限级分类示例中,可以看到 cte 可以多次定义,方便我们对数据进一步处理,当查询需求比较复杂时,可以做多一些CTE内容,将数据都处理好了之后,最后再用一个总的查询将结果输出。以这种方式,对比子查询方式,执行计划更稳定。

在数据库中建立视图的时候,我们无法使用临时表之类的东西,而 CTE 却不受限制,所以用好 CTE ,对一些比较复杂的查询做成视图是有很大帮助的。

如果小伙伴们还有什么比较有趣的 CTE 使用示例或方法,也请告知老顾哦。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文盲老顾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值