oracle connect by 递归查询用法

基本语法

  • start with :设置起点,省略后默认以全部行为起点。
  • connect by [condition] :与一般的条件一样作用于当前列,但是在满足条件后,会以全部列作为下一层级递归(没有其他条件的话)。
  • prior : 表示上一层级的标识符。经常用来对下一层级的数据进行限制。不可以接伪列。
  • level :伪列,表示当前深度。
  • connect_by_root() :显示根节点列。经常用来分组。
  • connect_by_isleaf :1是叶子节点,0不是叶子节点。在制作树状表格时必用关键字。
  • sys_connect_by_path() :将递归过程中的列进行拼接。
  • nocycle , connect_by_iscycle : 在有循环结构的查询中使用。
  • siblings : 保留树状结构,对兄弟节点进行排序

举几个例子。
先建一张测试表。

	create table nayi_180328_connect_test(
	dept_id varchar2(50), 
	parent_id varchar2(50), 
	dept_name varchar2(100), 
	dept_rank varchar2(2000), 
	val number);
	
	insert into nayi_180328_connect_test
	select 'root', '', '全国', '', 0 from dual
	union all
	select 'root_1', 'root', '北京市', '', 2000 from dual
	union all
	select 'ln_root', 'root', '辽宁省', '', 200 from dual
	union all
	select 'ln_ys', 'ln_root', '辽宁省沈阳市', '', 1000 from dual
	union all
	select 'ln_sy_hp', 'ln_ys', '辽宁省沈阳和平区', '', 500 from dual
	union all
	select 'ln_ys_dd', 'ln_ys', '辽宁省沈阳大东区', '', 600 from dual
	union all
	select 'jl_root', 'root', '吉林省', '', 0 from dual
	union all
	select 'jl_jl', 'jl_root', '吉林省吉林市', '', 200 from dual
	union all
	select 'jl_cc', 'jl_root', '吉林省长春市', '', 500 from dual
	;
	commit;

数据:

DEPT_IDPARENT_IDDEPT_NAMEDEPT_RANKVAL
root全国0
root_1root北京市2000
ln_rootroot辽宁省200
ln_ysln_root辽宁省沈阳市1000
ln_sy_hpln_ys辽宁省沈阳和平区500
ln_ys_ddln_ys辽宁省沈阳大东区600
jl_rootroot吉林省0
jl_jljl_root吉林省吉林市200
jl_ccjl_root吉林省长春市500
无prior
	select t.*, level, CONNECT_BY_ROOT(num) root, lpad('---', level * 3 - 1, '-') || '>' || num num, connect_by_isleaf isleaf
	  from (select 7 as num from dual
	        union all
	        select 8 as num from dual
	        ) t
	connect by level <= 3;
NUMLEVELROOTNUM_LEVELISLEAF
717–>70
727----->70
737-------->71
837-------->81
827----->80
737-------->71
837-------->81
818–>80
728----->70
738-------->71
838-------->81
828----->80
738-------->71
838-------->81

可以很明显的看出当满足当前行条件(level ❤️)时,下一层为全部行(两行)。这是对于每一个“当前行”都生效的。
它也相当于对每一层级做层级数的笛卡尔积。即与下面的语句等效。

	with tab as (
	select 7 as num from dual
	union all
	select 8 as num from dual)
	
	select t1.*, 1 l_level from tab t1
	union all
	select t1.*, 2 from tab t1, tab t2
	union all
	select t1.*, 3 from tab t1, tab t2, tab t3;

当初始数据为一行时最为常用。此时会生成层级数目的行,再配合level生成数据。
例:

	select level * 2 "生成线性数字",
	       to_char(add_months(sysdate, -level + 1), 'yyyymm') "当前月份前的n个月份",
	       regexp_substr('/root/ln_root/ln_ys/ln_sy_hp', '[^/]+', 1, level) "配合正则表达式分隔字符串"
	  from dual connect by level <= 5;
生成线性数字当前月份前的n个月份配合正则表达式分隔字符串
2201804root
4201803ln_root
6201802ln_ys
8201801ln_sy_hp
10201712
有prior

最简单的树状查询

	select t1.*
	  from nayi_180328_connect_test t1
	 where 1 = 1
	 start with t1.dept_id = 'root'
	connect by prior t1.dept_id = t1.parent_id;
DEPT_IDPARENT_IDDEPT_NAMEDEPT_RANKVAL
root全国0
jl_rootroot吉林省0
jl_ccjl_root吉林省长春市500
jl_jljl_root吉林省吉林市200
ln_rootroot辽宁省200
ln_ysln_root辽宁省沈阳市1000
ln_sy_hpln_ys辽宁省沈阳和平区500
ln_ys_ddln_ys辽宁省沈阳大东区600
root_1root北京市2000

prior可以有多个
比如这样

	select *
	  from nayi_180328_connect_test t1
	 where 1 = 1
	 start with t1.dept_id = 'root'
	connect by prior t1.dept_id = t1.parent_id
	       and prior t1.dept_id != prior t1.dept_name

表示当前行需满足上一层级的id与name不可相等,显然与第一句结果集相等。
或者这样

	select *
	  from nayi_180328_connect_test t1
	 where 1 = 1
	 start with t1.dept_id = 'root'
	connect by prior t1.dept_id = t1.parent_id
	       and prior t1.dept_id || prior t1.parent_id != 'root'

表示上一行的id与name拼接后不能等于root,它只能查出起始行。

具体条件要根据实际需要去调整。

#### 查询语句中的prior。
同样查出上一层级的相应数据

	select t1.*, 
	       prior dept_id
	  from nayi_180328_connect_test t1
	 where 1 = 1
	 start with t1.dept_id = 'root'
	connect by prior t1.dept_id = t1.parent_id
	;
DEPT_IDPARENT_IDDEPT_NAMEDEPT_RANKVALPRIORDEPT_ID
root全国0
jl_rootroot吉林省0root
jl_ccjl_root吉林省长春市500jl_root
jl_jljl_root吉林省吉林市200jl_root
ln_rootroot辽宁省200root
ln_ysln_root辽宁省沈阳市1000ln_root
ln_sy_hpln_ys辽宁省沈阳和平区500ln_ys
ln_ys_ddln_ys辽宁省沈阳大东区600ln_ys
root_1root北京市2000root

相当于是lead函数的简易实现。缺陷是只能查前一级。

	with nayi as (
	select t1.*, 
	       prior dept_id,
	       connect_by_isleaf isleaf
	  from nayi_180328_connect_test t1
	 where 1 = 1
	 start with t1.dept_id = 'root'
	connect by prior t1.dept_id = t1.parent_id
	)
	select distinct t1.*,
	       lead(t1.dept_id, 1) 
	          over(partition by connect_by_root(t1.dept_id) order by level) p_dept,
	       lead(t1.dept_id, 2) 
	          over(partition by connect_by_root(t1.dept_id) order by level) p_dept_2
	  from nayi_180328_connect_test t1
	 start with t1.dept_id in
	            (select v1.dept_id from nayi v1 where v1.isleaf = 1)
	connect by prior t1.parent_id = t1.dept_id
	;
DEPT_IDPARENT_IDDEPT_NAMEDEPT_RANKVALP_DEPTP_DEPT_2
ln_sy_hpln_ys辽宁省沈阳和平区500ln_ys_ddln_root
jl_rootroot吉林省0root
root全国0
ln_ys_ddln_ys辽宁省沈阳大东区600jl_jlln_root
jl_rootroot吉林省0ln_ys
ln_rootroot辽宁省200root
root全国0root
root全国0ln_root
jl_jljl_root吉林省吉林市200jl_ccroot
ln_ysln_root辽宁省沈阳市1000rootroot
jl_ccjl_root吉林省长春市500ln_ysroot
ln_ysln_root辽宁省沈阳市1000jl_rootroot
root_1root北京市2000ln_sy_hp
root全国0jl_root
ln_rootroot辽宁省200ln_root
siblings

保留树状结果顺序,并对兄弟节点进行排序

	select t1.*
	  from nayi_180328_connect_test t1
	 where 1 = 1
	 start with t1.dept_id = 'root'
	connect by prior t1.dept_id = t1.parent_id
	 order siblings by t1.val desc
	;
DEPT_IDPARENT_IDDEPT_NAMEDEPT_RANKVAL
root全国0
root_1root北京市2000
ln_rootroot辽宁省200
ln_ysln_root辽宁省沈阳市1000
ln_ys_ddln_ys辽宁省沈阳大东区600
ln_sy_hpln_ys辽宁省沈阳和平区500
jl_rootroot吉林省0
jl_ccjl_root吉林省长春市500
jl_jljl_root吉林省吉林市200
sys_connect_by_path

常使用生成的数据来简化其他查询

	update (
	select /*+ BYPASS_UJVC */ t1.*, t2.dept_path
	  from nayi_180328_connect_test t1,
	       (select t1.dept_id, sys_connect_by_path(t1.dept_id, '/') dept_path
	          from nayi_180328_connect_test t1
	         where 1 = 1
	         start with t1.dept_id = 'root'
	        connect by prior t1.dept_id = t1.parent_id) t2
	 where t1.dept_id = t2.dept_id
	) t1 set t1.dept_rank = t1.dept_path
	;
	commit;
DEPT_IDPARENT_IDDEPT_NAMEDEPT_RANKVAL
root全国/root0
root_1root北京市/root/root_12000
ln_rootroot辽宁省/root/ln_root200
ln_ysln_root辽宁省沈阳市/root/ln_root/ln_ys1000
ln_sy_hpln_ys辽宁省沈阳和平区/root/ln_root/ln_ys/ln_sy_hp500
ln_ys_ddln_ys辽宁省沈阳大东区/root/ln_root/ln_ys/ln_ys_dd600
jl_rootroot吉林省/root/jl_root0
jl_jljl_root吉林省吉林市/root/jl_root/jl_jl200
jl_ccjl_root吉林省长春市/root/jl_root/jl_cc500

例如通过dept_rank列来查询辽宁省的全部节点

	select *
	  from nayi_180328_connect_test t1
	 start with t1.dept_id = 'ln_root'
	connect by prior t1.dept_id = t1.parent_id
	;
DEPT_IDPARENT_IDDEPT_NAMEDEPT_RANKVAL
ln_rootroot辽宁省/root/ln_root200
ln_ysln_root辽宁省沈阳市/root/ln_root/ln_ys1000
ln_sy_hpln_ys辽宁省沈阳和平区/root/ln_root/ln_ys/ln_sy_hp500
ln_ys_ddln_ys辽宁省沈阳大东区/root/ln_root/ln_ys/ln_ys_dd600

最后再举一些我平时练习时使用的例子

枚举法求最大公约数

	with a1 as (select level num from dual connect by level <= 100),
	     a2 as (select level num from dual connect by level <= 64)
	select max(t1.n1) greatest_divisor
	  from (select a1.num n1, a2.num n2
	          from a1, a2
	         where 1 = 1
	           and mod(100, least(a1.num, a2.num)) = 0
	           and mod(64, least(a1.num, a2.num)) = 0) t1
	 where t1.n1 = t1.n2
	;

结果为4。

按组去除相同前缀

	with tab1 as (
	select '12388554gf' str, 1 group_id from dual
	union all
	select '12311' str, 1 from dual
	union all
	select '1234rg' str, 1 from dual
	union all
	select '1238822f' str, 1 from dual
	union all
	select 'asdf123' str, 2 from dual
	union all
	select 'asdf789' str, 2 from dual),
	tab2 as (
	select t1.str,
	       t1.group_id,
	       level - 1 ll,
	       substr(t1.str, 1, level - 1) sub_str,
	       count(1) over(partition by t1.group_id, level, substr(t1.str, 1, level - 1)) max_num
	  from (select t1.str, 
	               t1.group_id,
	               min(length(t1.str)) over(partition by t1.group_id) min_length, 
	               count(1) over(partition by t1.group_id) row_num from tab1 t1) t1
	connect by level <= min_length + 1
	       and prior t1.str = t1.str
	       and prior sys_guid() is not null),
	tab3 as (
	select distinct first_value(t1.ll) over(partition by t1.group_id order by t1.max_num desc, t1.ll desc) sub_num,
	       t1.group_id
	from tab2 t1)
	select t1.str,
	       substr(t1.str,
	              (select max(sub_num)
	                 from tab3 v1
	                where v1.group_id = t1.group_id) + 1) sub_str,
	       t1.group_id
	  from tab1 t1
	;
STRSUB_STRGROUP_ID
12388554gf88554gf1
12311111
1234rg4rg1
1238822f8822f1
asdf1231232
asdf7897892

总结

递归查询主要使用的地方

  • 通过递归特点生成数据。
  • 构建树状表格。
  • 通过sys_connect_by_path生成的字段做其他处理。
  • 对数据进行树状梳理,使用各种关键字与函数实现想要的结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值