暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

迁移到PG 递归函数没有了Level伪列怎么办

IT那活儿 2020-11-19
3811

今天又遇到一个问题,在Oracle数据库中有Connectby这类的递归SQL。结果迁移到PostgreSQL中,发现没有level伪列。

作为去O实力派,没办法只能安排。

由于开发的表比较复杂,我们这里来造一个cats表,说明一下这个问题。我们先看看Oracle中的查询结果。

create table hbdx_zhaoyou.cats(  

catno   number(4,0),  

catname varchar2(15),  

job     varchar2(15),  

mgr     number(4,0),  

constraint pk_emp primary key (catno)

);

insert into hbdx_zhaoyou.cats values (0, 'king', 'ceo', null );

insert into hbdx_zhaoyou.cats values (1, 'jones', 'cio', 0);

insert into hbdx_zhaoyou.cats values (2, 'blake', 'cfo', 0);

insert into hbdx_zhaoyou.cats values (3, 'clark', 'hr exec', 0);

insert into hbdx_zhaoyou.cats values (4, 'scott', 'it mgr', 1);

insert into hbdx_zhaoyou.cats values (5, 'turner', 'architect',1);

insert into hbdx_zhaoyou.cats values (6, 'adams', 'fin.mgr',2 );

insert into hbdx_zhaoyou.cats values (7, 'james', 'hr.mgr',3);

insert into hbdx_zhaoyou.cats values (8, 'ford', 'it support',4);

insert into hbdx_zhaoyou.cats values (9, 'miller', 'developer', 4);

insert into hbdx_zhaoyou.cats values (10, 'smith', 'accountant',6);

insert into hbdx_zhaoyou.cats values (11, 'allen', 'payroll clerk',6);

insert into hbdx_zhaoyou.cats values (12, 'ward', 'hr officer',7 );

commit;

我们在Oracle中执行层次查询得到下列结果。

SQL> SELECT  catno,catname,job,level FROM hbdx_zhaoyou.cats CONNECT BY PRIOR catno = mgr START WITH mgr IS NULL order by level ;

CATNO CATNAME         JOB                  LEVEL

---------- --------------- --------------- ----------

0 king            ceo                      1

1 jones           cio                      2

2 blake           cfo                      2

3 clark           hr exec                  2

6 adams           fin.mgr                  3

7 james           hr.mgr                   3

5 turner          architect                3

4 scott           it mgr                   3

8 ford            it support               4

11 allen           payroll clerk            4

12 ward            hr officer               4

9 miller          developer                4

10 smith         accountant               4

类似于上图,我们知道King是老大,他的下面一级是经理级别,一共有三个经理,分别是jones、blake、clark,然后以此往下推。

这里Oracle的语法:

  • CONNECT BY :定义了父级别和子级别之间的关系。

  • PRIOR:则指定了父级

  • START WITH:定义我们希望查询开始的记录。

  • level:指示层次结构级别的伪列。

那么在PostgreSQL中有两种实现的方法,第一种叫CTE(commontable expressions),简称公用表表达式。第二种是安装自带插件,使用PG的connectby函数。

CTE(common tableexpressions)实现

CTE又叫commontable expressions,它隶属于SQL:1999标准,在Oracle11gR2版本、MySQL8.0版本、PostgreSQL9.4以上版本都支持。因为是标准的语法,我们这里用mariadb官方文档(最容易懂)的图来说明一下原理。

首选我们要使用recursive关键字来表示,这是一个递归的CTE(公用表达式)。然后第一部分叫AnchorPart,翻译过来就是锚点。这个锚点我觉得就代表了树形查询的一个展开的点,比如你要从我们CATS表的ITMGR这个节点查询,那么这个条件就是锚点。

接下来就是Recursivepart,递归的部分。这里会告诉我们每个递归的步骤将要做什么。每次执行出来的结果,就会存放到结果表中,一直到整个递归结束。

下面的图详细的说明了递归到结果集这一过程。

首选查询锚点,取出name=‘Alex’的记录放入到结果表。

通过锚点查询的数据和原来的数据表关联,查询出锚点的下一层数据。比如这里查的是Alex的father和mother。

这里将上面查出来的数据Dad和Mom存到结果表中。

接下来继续根据上面查询的Dad和Mom,继续查询他们的father和mother。

这里将上面查出来的数据GrandpaBill存到结果表中

就这样一直查,直到没有结果为止。

上面的原理和语法介绍完了,我们可以在PG中写同样的SQL实现上述Oracle中connectby功能。

with recursive cte as (  

select catno, catname, mgr  from cats  where mgr is null

union all                    

select e.catno, e.catname, e.mgr from  cte c join cats e on e.mgr = c.catno  

)  

select * from  cte;

catno | catname | mgr

-------+---------+-----

0 | king    |    

1 | jones   |   0

2 | blake   |   0

3 | clark   |   0

4 | scott   |   1

5 | turner  |   1

6 | adams   |   2

7 | james   |   3

8 | ford    |   4

9 | miller  |   4

10 | smith   |   6

11 | allen   |   6

12 | ward    |   7

可以看到结果类似,但是缺少像Oracle中的伪列level。这个伪列是需要我们自己构造一个的。

with recursive cte as (  

select catno, catname, mgr,1 AS level from cats  where mgr is null

union all                    

select e.catno, e.catname, e.mgr,c.level + 1 from cte c join cats e on e.mgr = c.catno )

select * from  cte;

catno | catname | mgr | level

-------+---------+-----+-------

0 | king    |     |     1

1 | jones   |   0 |     2

2 | blake   |   0 |     2

3 | clark   |   0 |     2

4 | scott   |   1 |     3

5 | turner  |   1 |     3

6 | adams   |   2 |     3

7 | james   |   3 |     3

8 | ford    |   4 |     4

9 | miller  |   4 |     4

10 | smith   |   6 |     4

11 | allen   |   6 |     4

12 | ward    |   7 |     4

手动增加了一个列,就可以把level伪列功能实现了,至此我们就解决了开发的问题。

connectby实现

上面介绍了比较标准的CTE表达式,是在各种数据库都已经兼容的语法。而PG也有他自己独有的一种方法叫connectby,PG的connectby和Oracle中的connectby使用方式有很大的不同。

首先我们要安装插件tablefunc,这个插件是软件自带的。安装很简单直接createextension tablefunc就可以了。

这个插件有很多功能,我们这里只用最后一个功能connectby。

connectby(text relname, text keyid_fld, text parent_keyid_fld

[, text orderby_fld ], text start_with, int max_depth

[, text branch_delim ])

relname

源表的名称

keyid_fld

关键字段

parent_keyid_fld

父键的关键字段

orderby_fld

排序同级别字段(可选)

start_with

起始行的键值

max_depth

要向下展开的最大深度,零表示无限深度

branch_delim

在分支输出中用于分隔键值的字符串(可

接下来就是见证这个函数魅力的时候了。

SELECT *  FROM connectby('cats', 'catno', 'mgr', '0', 0, '->')  

AS t(keyid numeric, parent_keyid numeric, level int, branch text)

order by level asc;

keyid | parent_keyid | level |   branch    

-------+--------------+-------+-------------

0 |            |     0 | 0

1 |          0 |     1 | 0->1

2 |          0 |     1 | 0->2

3 |          0 |     1 | 0->3

4 |          1 |     2 | 0->1->4

6 |          2 |     2 | 0->2->6

7 |          3 |     2 | 0->3->7

5 |          1 |     2 | 0->1->5

9 |          4 |     3 | 0->1->4->9

8 |          4 |     3 | 0->1->4->8

10 |          6 |     3 | 0->2->6->10

11 |          6 |     3 | 0->2->6->11

12 |            7 |     3 | 0->3->7->12

可以看到使用起来非常方便,直接就输入参数就行了。当然这里有一个问题是我们没办法通过connectby函数展示全部的列,它的函数参数写死了类型。如果我们要展示cats表中的catname,就需要我们把当前结果集和原表cats在做一次关联。

select keyid,catname,parent_keyid,level+1 from connectby('cats', 'catno', 'mgr', '0', 0, '->')  AS t(keyid numeric, parent_keyid numeric, level int, branch text)

inner join cats on catno = keyid

order by level,mgr asc;

keyid | catname | parent_keyid | ?column?

-------+---------+--------------+----------

0 | king    |              |        1

1 | jones   |            0 |        2

2 | blake   |            0 |        2

3 | clark   |            0 |        2

5 | turner  |            1 |        3

4 | scott   |            1 |        3

6 | adams   |            2 |        3

7 | james   |            3 |        3

9 | miller  |            4 |        4

8 | ford    |            4 |        4

10 | smith   |            6 |        4

11 | allen   |            6 |        4

12 | ward    |            7 |      4

那么这么写就和我前面的withrecursive的结果集完全一致了。

谁的效率高?

如果拿我们想要的结果来看的话。通过执行计划来看,很明显是CTE快一些。

就算我们不取catname,取消掉关联,connecyby函数执行的效率也是比CTE低的。

终于有人喊出了口号:

CONNECTBY Is Dead,

LongLive CTE!

参考文档:

RecursiveCommon Table Expressions Overview

https://mariadb.com/kb/en/recursive-common-table-expressions-overview/

Hierarchicaland recursive queries in SQL

https://en.wikipedia.org/wiki/Hierarchical_and_recursive_queries_in_SQL#Common_table_expression

F.38.tablefunc

https://www.postgresql.org/docs/12/tablefunc.html

文章转载自IT那活儿,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论