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;
即三个部分:
- 公用表表达式的名字(在WITH关键字之后)
- 查询的列名(可选)
- 紧跟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递归查询后进行筛选啊。
SQL递归查询知多少的更多相关文章
- 读书笔记汇总 - SQL必知必会(第4版)
本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...
- 读书笔记--SQL必知必会--建立练习环境
书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL in 10 Minutes - Fourth Edition> MyS ...
- 读书笔记--SQL必知必会12--联结表
12.1 联结 联结(join),利用SQL的SELECT在数据查询的执行中联结表. 12.1.1 关系表 关系数据库中,关系表的设计是把信息分解成多个表,一类数据一个表,各表通过某些共同的值互相关联 ...
- 读书笔记--SQL必知必会18--视图
读书笔记--SQL必知必会18--视图 18.1 视图 视图是虚拟的表,只包含使用时动态检索数据的查询. 也就是说作为视图,它不包含任何列和数据,包含的是一个查询. 18.1.1 为什么使用视图 重用 ...
- 《SQL必知必会》学习笔记(一)
这两天看了<SQL必知必会>第四版这本书,并照着书上做了不少实验,也对以前的概念有得新的认识,也发现以前自己有得地方理解错了.我采用的数据库是SQL Server2012.数据库中有一张比 ...
- SQL 必知必会
本文介绍基本的 SQL 语句,包括查询.过滤.排序.分组.联结.视图.插入数据.创建操纵表等.入门系列,不足颇多,望诸君指点. 注意本文某些例子只能在特定的DBMS中实现(有的已标明,有的未标明),不 ...
- 0005 《SQL必知必会》笔记01-SELECT语句
1.SELECT基本语句: SELECT 字段名1,···,字段名n FROM 表名 2.检索所有字段,用"*"替换字段名,这会导致效率低下 SELECT * FROM 表名; 3 ...
- SQL递归查询实现跟帖盖楼效果
网易新闻的盖楼乐趣多,某一天也想实现诸如网易新闻跟帖盖楼的功能,无奈技术不佳(基础不牢),网上搜索了资料才发现SQL查询方法有一种叫递归查询,整理如下: 一.查询出 id = 1 的所有子结点 wit ...
- 《SQL必知必会》学习笔记二)
<SQL必知必会>学习笔记(二) 咱们接着上一篇的内容继续.这一篇主要回顾子查询,联合查询,复制表这三类内容. 上一部分基本上都是简单的Select查询,即从单个数据库表中检索数据的单条语 ...
随机推荐
- PAT (Advanced Level) 1018. Public Bike Management (30)
先找出可能在最短路上的边,图变成了一个DAG,然后在新图上DFS求答案就可以了. #include<iostream> #include<cstring> #include&l ...
- SQL复习五(索引)
SQL索引在数据库优化中占有一个非常大的比例, 一个好的索引的设计,可以让你的效率提高几十甚至几百倍,在这里将带你一步步揭开他的神秘面纱. 1.1 什么是索引? SQL索引有两种,聚集索引和非聚集索引 ...
- (中等) POJ 2482 Stars in Your Window,静态二叉树。
Description Here comes the problem: Assume the sky is a flat plane. All the stars lie on it with a l ...
- 从内存中加载DLL Delphi版(转)
源:从内存中加载DLL DELPHI版 原文 : http://www.2ccc.com/article.asp?articleid=5784 MemLibrary.pas //从内存中加载DLL D ...
- Django 缓存系统
Django 是动态网站,一般来说需要实时地生成访问的网页,展示给访问者,这样,内容可以随时变化,但是从数据库读多次把所需要的数据取出来,要比从内存或者硬盘等一次读出来 付出的成本大很多. 缓存系统工 ...
- 关于cin的用法一些小结
在写二叉树的时候遇到if(!cin)那几个标志位弄得并不清楚,还遇到了诸如cin.clear()等函数,感觉C++又白学了,于是打算去网上搜了几篇靠谱的文章,有时候看来,一些事件处理类的工程代码,在A ...
- minor gc 和 full gc
JAVA中关于GC的分析中,需要搞清楚,GC线程在什么时候,对什么东西,做了什么操作. 1-在什么时候 首先需要知道,GC分为minor GC和full GC,JAVA内存分为新生代和老年代,新生代中 ...
- UDP传输包大小(转)
源:UDP传输包大小 在进行UDP编程的时候,我们最容易想到的问题就是,一次发送多少bytes好? 当然,这个没有唯一答案,相对于不同的系统,不同的要求,其得到的答案是不一样的,我这里仅对 像ICQ一 ...
- 关于Java通过JNI调用C 动态链接库(DLL)
JNI介绍 用JNI实现Java和C语言的数据传递 JNI原理分析和详细步骤截图说明 jni的JNIEnv指针和jobject指针 JNI实现回调| JNI调用JAVA函数|参数和返回值的格式 Jni ...
- NSDateFormatter调整时间格式的代码
NSDateFormatter调整时间格式的代码 在开发iOS程序时,有时候需要将时间格式调整成自己希望的格式,这个时候我们可以用NSDateFormatter类来处理.例如://实例化一个NSDat ...