【新星计划】数据库 CTE 初识
- CTE
- CTE 语法
- CTE 递归
- CTE 递归语法
- 递归示例
- 递归机制
- 几个CTE 递归的示例
- 阶乘
- 斐波那契序列
- 无限级分类路径
- rand 函数
- 小结
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 使用示例或方法,也请告知老顾哦。