最近工做中遇到了一个问题,须要根据保存的流程数据,构建流程图。数据库中保存的流程数据是树形结构的,表结构及数据以下图:html
仔细观察表结构,会发现其树形结构的特色:算法
图中的流程为:
销售合同-->销售订单-->发货通知单-->销售出库单sql
首先想到的办法就是把流程数据取回来,而后代码构造流程图。
第一个思路:根据根节点循环往下找,吭呲半天,发现没那么简单。
由于任何一个源头单据均可以屡次下推目标单据:
第二个思路:先找到终极节点,在从终极节点往上找只至根节点为0。
这个思路实现起来也没有那么复杂,逻辑理清,循环遍历,最终也能实现结果。(但在大数据量状况下,易致使性能瓶颈。)数据库
这一次咱们换一个思路,让SQL来替咱们作这一复杂的递归查询。express
公用表表达式 (CTE) 能够认为是在单个 SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 语句的执行范围内定义的临时结果集。公用表表达式能够包括对自身的引用,这种表达式称为递归公用表表达式。oracle
MSDN上对CTE的介绍
T-SQL查询进阶--详解公用表表达式(CTE)函数
CTE 的基本语法结构以下:sqlserver
WITH expression_name [ ( column_name [,...n] ) ] AS ( CTE_query_definition ) --只有在查询定义中为全部结果列都提供了不一样的名称时,列名称列表才是可选的。 --运行 CTE 的语句为: SELECT <column_list> FROM expression_name;
即三个部分:性能
根据官网示例咱们很简单就能够写出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)
是用来限制递归次数,以免无限递归致使数据库性能消耗严重。
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中的递归查询语句为start with…connect by prior
,为中序遍历算法。
可参考Oracle 树操做、递归查询(select…start with…connect by…prior)了解更多。
其基本语法是:
select colname from tablename start with 条件1 connect by 条件2 where 条件3;
CONNECT BY PRIOR Id = Parent_Id
就是说上一条记录的Id 是本条记录的Parent_Id。PRIOR关键字
运算符PRIOR被放置于等号先后的位置,决定着查询时的检索顺序。
CONNECT BY PRIOR Id=Parent_Id
CONNECT BY Id=PRIOR Parent_Id
PS:当CONNECT BY后指定多个链接条件时,每一个条件都应指定PRIOR
关键字
理清了用法,咱们用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进行关联迭代不可行。
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
用来拼接递归路径。
这个时候咱们要用到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
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递归查询后进行筛选啊。