SQL递归查询知多少

最近工作中遇到了一个问题,需要根据保存的流程数据,构建流程图。数据库中保存的流程数据是树形结构的,表结构及数据如下图:

流程表结构数据举例

仔细观察表结构,会发现其树形结构的特点:

  • FFIRSTNODE:标记是否为根节点
  • FSTABLENAME:标记来源单据名称
  • FSID:标记来源单据分录ID
  • FTTABLENAME :标记目标单据名称
  • FTID:标记目标单据分录ID

图中的流程为:
销售合同-->销售订单-->发货通知单-->销售出库单

首先想到的办法就是把流程数据取回来,然后代码构造流程图。
第一个思路:根据根节点循环往下找,吭呲半天,发现没那么简单。
因为任何一个源头单据都可以多次下推目标单据:
第二个思路:先找到终极节点,在从终极节点往上找只至根节点为0。
这个思路实现起来也没有那么复杂,逻辑理清,循环遍历,最终也能实现结果。(但在大数据量情况下,易导致性能瓶颈。)

这一次我们换一个思路,让SQL来替我们做这一复杂的递归查询。

一、SqlServer 递归查询

1、基本概念

公用表表达式 (CTE) 可以认为是在单个 SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 语句的执行范围内定义的临时结果集。公用表表达式可以包括对自身的引用,这种表达式称为递归公用表表达式。

  • 创建递归查询。有关详细信息,请参阅使用公用表表达式的递归查询
  • 在不需要常规使用视图时替换视图,也就是说,不必将定义存储在元数据中。
  • 启用按从标量嵌套 select 语句派生的列进行分组,或者按不确定性函数或有外部访问的函数进行分组。
  • 在同一语句中多次引用生成的表。

MSDN上对CTE的介绍
T-SQL查询进阶--详解公用表表达式(CTE)

CTE 的基本语法结构如下:

WITH expression_name [ ( column_name [,...n] ) ]
AS
( CTE_query_definition )
--只有在查询定义中为所有结果列都提供了不同的名称时,列名称列表才是可选的。
--运行 CTE 的语句为:
SELECT <column_list> FROM expression_name;

即三个部分:

  1. 公用表表达式的名字(在WITH关键字之后)
  2. 查询的列名(可选)
  3. 紧跟AS之后的SELECT语句(如果AS之后有多个对公用表的查询,则只有第一个查询有效

2、动手实践

根据官网示例我们很简单就可以写出CTE语句应用于我们的应用场景:

WITH TEST_CTE 
AS
(
SELECT TBIE.FSTABLENAME,TBIE.FSID,TBIE.FTTABLENAME,TBIE.FTID,TBIE.FROUTEID FROM T_BF_INSTANCEENTRY TBIE
WHERE TBIE.FTTABLENAME = 'T_SAL_ORDERENTRY' AND TBIE.FTID = 121625
UNION ALL
SELECT CTBIE.FSTABLENAME,CTBIE.FSID,CTBIE.FTTABLENAME,CTBIE.FTID,CTBIE.FROUTEID  FROM T_BF_INSTANCEENTRY CTBIE
INNER JOIN TEST_CTE CTE ON CTBIE.FSID=CTE.FTID AND CTBIE.FSTABLENAME = CTE.FTTABLENAME
)
SELECT * FROM TEST_CTE  
--限制递归次数
OPTION(MAXRECURSION 10)

在查询中我们指定条件参数WHERE TBIE.FTTABLENAME = 'T_SAL_ORDERENTRY' AND TBIE.FTID = 121625,即可查询到指定节点的完整流程数据。
其中在与公用表TEST_CTE进行关联时,我指定了两个条件CTBIE.FSID=CTE.FTID AND CTBIE.FSTABLENAME = CTE.FTTABLENAME,因为不同类型的单据各有一套自增的ID,直接用ID进行关联迭代不可行。

查询结果

需要注意的是OPTION(MAXRECURSION 10)是用来限制递归次数,以避免无限递归导致数据库性能消耗严重。

3、扩展:构造递归路径

WITH TEST_CTE 
AS
(
SELECT TBIE.FSTABLENAME,TBIE.FSID,TBIE.FTTABLENAME,TBIE.FTID,TBIE.FROUTEID,Cast(TBIE.FTID as nvarchar(4000)) AS PATH
FROM T_BF_INSTANCEENTRY TBIE
WHERE TBIE.FTTABLENAME = 'T_SAL_ORDERENTRY' AND TBIE.FTID = 121625
UNION ALL
SELECT CTBIE.FSTABLENAME,CTBIE.FSID,CTBIE.FTTABLENAME,CTBIE.FTID,CTBIE.FROUTEID,CTE.PATH+'->'+Cast(CTBIE.FTID as nvarchar(4000)) PATH  
FROM T_BF_INSTANCEENTRY CTBIE
INNER JOIN TEST_CTE CTE ON CTBIE.FSID=CTE.FTID AND CTBIE.FSTABLENAME = CTE.FTTABLENAME
)
SELECT * FROM TEST_CTE  
--限制递归次数
OPTION(MAXRECURSION 10)

基于上一个查询,增加一列手动拼接递归路径。注意sql中将PATH设置的类型为navarchar(4000),在union中,两边的表结构类型必须保持一致,否则会报错定位点类型和递归部分的类型不匹配。可参考此篇博文
解决CTE定位点类型和递归部分的类型不匹配

递归路径查询结果

二、Oracle 递归查询

1、基本概念

Oracle中的递归查询语句为start with…connect by prior,为中序遍历算法。
可参考Oracle 树操作、递归查询(select…start with…connect by…prior)了解更多。

查询顺序

其基本语法是:

select colname from tablename
start with 条件1
connect by 条件2
where 条件3;
  • 条件1: 是根结点的限定语句,当然可以放宽限定条件,以遍历多个根结点,实际就是多棵树。
  • 条件2:是连接条件,其中用PRIOR表示上一条记录。
    比如CONNECT BY PRIOR Id = Parent_Id就是说上一条记录的Id 是本条记录的Parent_Id
  • 条件3:过滤返回的结果集。

PRIOR关键字

运算符PRIOR被放置于等号前后的位置,决定着查询时的检索顺序。

  • PRIOR被置于CONNECT BY子句中等号的前面时,则强制从根节点到叶节点的顺序检索,为自顶向下查找。
    如:CONNECT BY PRIOR Id=Parent_Id
  • PIROR运算符被置于CONNECT BY 子句中等号的后面时,则强制从叶节点到根节点的顺序检索,为自底向上的查找。
    如:CONNECT BY Id=PRIOR Parent_Id

PS:当CONNECT BY后指定多个连接条件时,每个条件都应指定PRIOR关键字

2、动手实践

理清了用法,我们用Oracle来对查询一下业务流程。

SELECT * FROM T_BF_INSTANCEENTRY 
START WITH (FTID=100501 AND FTTABLENAME = 'T_SAL_ORDERENTRY')
CONNECT BY  FSID= PRIOR FTID AND FSTABLENAME =PRIOR FTTABLENAME
查询结果

该流程为:销售订单-->发货通知单-->销售出库单-->退货通知单-->销售退货单
其中在指定连接条件时,我指定了两个条件FSID= PRIOR FTID AND FSTABLENAME =PRIOR FTTABLENAME,因为不同类型的单据各有一套自增的ID,直接用ID进行关联迭代不可行。

3、扩展:构造递归路径

Oracle中提供了SYS_CONNECT_BY_PATH函数用来进行连接路径。

SELECT TBIE.*, SUBSTR(SYS_CONNECT_BY_PATH(FTID,'->'),3)  NAME_PATH FROM T_BF_INSTANCEENTRY TBIE
START WITH (FTID=100501 AND FTTABLENAME = 'T_SAL_ORDERENTRY')
CONNECT BY  FSID= PRIOR FTID AND FSTABLENAME =PRIOR FTTABLENAME

基于上个查询,增加了一列SUBSTR(SYS_CONNECT_BY_PATH(FTID,'->'),3) NAME_PATH用来拼接递归路径。

递归路径查询结果

4、显示当前节点的根节点

这个时候我们要用到connect_by_root函数,用来记录当前节点的根节点信息。

SELECT TBIE.*, SUBSTR(SYS_CONNECT_BY_PATH(FTID,'->'),3)NAME_PATH, (connect_by_root FTID) ROOT  FROM T_BF_INSTANCEENTRY TBIE
START WITH (FTID=100501 AND FTTABLENAME = 'T_SAL_ORDERENTRY')
CONNECT BY  FSID= PRIOR FTID AND FSTABLENAME =PRIOR FTTABLENAME
当前节点的根节点的查询结果

5、Oracle中的with...as语句

Oracle也有with..as 查询语法,一般用来进行子查询,提高查询效率。
语法:

with tempTableName as ( select * from table1 )
select * from tempTableName

拿我们的案例举例就是:

with flow_temp as (
SELECT TBIE.*, SUBSTR(SYS_CONNECT_BY_PATH(FTID,'->'),3)NAME_PATH, (connect_by_root FTID) ROOT  FROM T_BF_INSTANCEENTRY TBIE
START WITH (FTID=100501 AND FTTABLENAME = 'T_SAL_ORDERENTRY')
CONNECT BY  FSID= PRIOR FTID AND FSTABLENAME =PRIOR FTTABLENAME
)

select * from flow_temp

为啥要讲这个呢,我们可以在oracle递归查询后进行筛选啊。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容