oracle递归with

简介

递归with(Recursive WITH Clauses)是一个主要用于层次查询(Hierarchical Queries)的语法。要使用它,需要oracle的版本为Oracle 11g Release 2及以上。这个语法可以看成是对connect语法的补充。

基本语法

建立测试数据

DROP TABLE tab1 PURGE;

CREATE TABLE tab1 (
  id        NUMBER,
  parent_id NUMBER,
  CONSTRAINT tab1_pk PRIMARY KEY (id),
  CONSTRAINT tab1_tab1_fk FOREIGN KEY (parent_id) REFERENCES tab1(id)
);

CREATE INDEX tab1_parent_id_idx ON tab1(parent_id);

INSERT INTO tab1 VALUES (1, NULL);
INSERT INTO tab1 VALUES (2, 1);
INSERT INTO tab1 VALUES (3, 2);
INSERT INTO tab1 VALUES (4, 2);
INSERT INTO tab1 VALUES (5, 4);
INSERT INTO tab1 VALUES (6, 4);
INSERT INTO tab1 VALUES (7, 1);
INSERT INTO tab1 VALUES (8, 7);
INSERT INTO tab1 VALUES (9, 1);
INSERT INTO tab1 VALUES (10, 9);
INSERT INTO tab1 VALUES (11, 10);
INSERT INTO tab1 VALUES (12, 9);
COMMIT;
IDPARENT_ID
1
21
32
42
54
64
71
87
91
109
1110
129

这是一个有父子关系的表。
传统的写法是这样的

select *
  from tab1 t1
 start with t1.parent_id is null
connect by prior t1.id = t1.parent_id;
IDPARENT_ID
1
21
32
42
54
64
71
87
91
109
1110
129

现在,我们有了一个新的写法。

with t1(id, parent_id) as (

select*from tab1 t0 where t0.parent_id is null  -- Anchor member.

union all

select t2.id, t2.parent_id from tab1 t2, t1  -- Recursive member.
where t2.parent_id = t1.id
)

select*from t1;
IDPARENT_ID
1
21
71
91
32
42
87
109
129
54
64
1110

来自官方的解释

Basic Hierarchical Query

A recursive subquery factoring clause must contain two query blocks combined by a UNION ALL set operator. The first block is known as the anchor member, which can not reference the query name. It can be made up of one or more query blocks combined by the UNION ALL, UNION, INTERSECT or MINUS set operators. The second query block is known as the recursive member, which must reference the query name once.

The following query uses a recursive WITH clause to perform a tree walk. The anchor member queries the root nodes by testing for records with no parents. The recursive member successively adds the children to the root nodes.

这真是个很奇葩的语法。union all不是一个合并结果集的语法,而是成为了一个构成语法的关键词。

with中的语句必须是由union all分开的两部分。第一部分的作用是确定根节点,这一部分不会参与到递归当中。它相当于start with。第二部分可以接收来自上一层级的数据。相当于connect之后的语句。

结果集顺序

The ordering of the rows is specified using the SEARCH clause, which can use two methods.

BREADTH FIRST BY : Sibling rows are returned before child rows are processed.
DEPTH FIRST BY : Child rows are returned before siblings are processed.

根据刚才的例子可以看出,默认的排序是广度优先的。如果要改成深度优先,需要这么写

WITH t1(id, parent_id) AS (
  -- Anchor member.
  SELECT id,
         parent_id
  FROM   tab1
  WHERE  parent_id IS NULL
  UNION ALL
  -- Recursive member.
  SELECT t2.id,
         t2.parent_id
  FROM   tab1 t2, t1
  WHERE  t2.parent_id = t1.id
)
SEARCH DEPTH FIRST BY id SET order1
SELECT id,
       parent_id
FROM   t1
ORDER BY order1;

与connect相关语法的等效替换

LEVEL

WITH t1(id, parent_id, lvl) AS (
  -- Anchor member.
  SELECT id,
         parent_id,
         1 AS lvl
  FROM   tab1
  WHERE  parent_id IS NULL
  UNION ALL
  -- Recursive member.
  SELECT t2.id,
         t2.parent_id,
         lvl+1
  FROM   tab1 t2, t1
  WHERE  t2.parent_id = t1.id
)
SEARCH DEPTH FIRST BY id SET order1
SELECT id,
       parent_id,
       RPAD('.', (lvl-1)*2, '.') || id AS tree,
       lvl
FROM t1
ORDER BY order1;
IDPARENT_IDTREELVL
111
21..22
32….33
42….43
54……54
64……64
71..72
87….83
91..92
109….103
1110……114
129….123

CONNECT_BY_ROOT

WITH t1(id, parent_id, lvl, root_id) AS (
  -- Anchor member.
  SELECT id,
         parent_id,
         1 AS lvl,
         id AS root_id
  FROM   tab1
  WHERE  parent_id IS NULL
  UNION ALL
  -- Recursive member.
  SELECT t2.id,
         t2.parent_id,
         lvl+1,
         t1.root_id
  FROM   tab1 t2, t1
  WHERE  t2.parent_id = t1.id
)
SEARCH DEPTH FIRST BY id SET order1
SELECT id,
       parent_id,
       RPAD('.', (lvl-1)*2, '.') || id AS tree,
       lvl,
       root_id
FROM t1
ORDER BY order1;
IDPARENT_IDTREELVLROOT_ID
1111
21..221
32….331
42….431
54……541
64……641
71..721
87….831
91..921
109….1031
1110……1141
129….1231

SYS_CONNECT_BY_PATH

WITH t1(id, parent_id, lvl, root_id, path) AS (
  -- Anchor member.
  SELECT id,
         parent_id,
         1 AS lvl,
         id AS root_id,
         TO_CHAR(id) AS path
  FROM   tab1
  WHERE  parent_id IS NULL
  UNION ALL
  -- Recursive member.
  SELECT t2.id,
         t2.parent_id,
         lvl+1,
         t1.root_id,
         t1.path || '-' || t2.id AS path
  FROM   tab1 t2, t1
  WHERE  t2.parent_id = t1.id
)
SEARCH DEPTH FIRST BY id SET order1
SELECT id,
       parent_id,
       RPAD('.', (lvl-1)*2, '.') || id AS tree,
       lvl,
       root_id,
       path
FROM t1
ORDER BY order1;
IDPARENT_IDTREELVLROOT_IDPATH
11111
21..2211-2
32….3311-2-3
42….4311-2-4
54……5411-2-4-5
64……6411-2-4-6
71..7211-7
87….8311-7-8
91..9211-9
109….10311-9-10
1110……11411-9-10-11
129….12311-9-12

NOCYCLE and CONNECT_BY_ISCYCLE

UPDATE tab1 SET parent_id = 9 WHERE id = 1;
COMMIT;


WITH t1(id, parent_id, lvl, root_id, path) AS (
  -- Anchor member.
  SELECT id,
         parent_id,
         1 AS lvl,
         id AS root_id,
         TO_CHAR(id) AS path
  FROM   tab1
  WHERE  id = 1
  UNION ALL
  -- Recursive member.
  SELECT t2.id,
         t2.parent_id,
         lvl+1,
         t1.root_id,
         t1.path || '-' || t2.id AS path
  FROM   tab1 t2, t1
  WHERE  t2.parent_id = t1.id
)
SEARCH DEPTH FIRST BY id SET order1
SELECT id,
       parent_id,
       RPAD('.', (lvl-1)*2, '.') || id AS tree,
       lvl,
       root_id,
       path
FROM t1
ORDER BY order1;
     *
ERROR at line 27:
ORA-32044: cycle detected while executing recursive WITH query

如果不作处理的话,毫无疑问会报错。

WITH t1(id, parent_id, lvl, root_id, path) AS (
  -- Anchor member.
  SELECT id,
         parent_id,
         1 AS lvl,
         id AS root_id,
         TO_CHAR(id) AS path
  FROM   tab1
  WHERE  id = 1
  UNION ALL
  -- Recursive member.
  SELECT t2.id,
         t2.parent_id,
         lvl+1,
         t1.root_id,
         t1.path || '-' || t2.id AS path
  FROM   tab1 t2, t1
  WHERE  t2.parent_id = t1.id
)
SEARCH DEPTH FIRST BY id SET order1
CYCLE id SET cycle TO 1 DEFAULT 0
SELECT id,
       parent_id,
       RPAD('.', (lvl-1)*2, '.') || id AS tree,
       lvl,
       root_id,
       path,
       cycle
FROM t1
ORDER BY order1;
IDPARENT_IDTREELVLROOT_IDPATHCYCLE
1911110
21..2211-20
32….3311-2-30
42….4311-2-40
54……5411-2-4-50
64……6411-2-4-60
71..7211-70
87….8311-7-80
91..9211-90
19….1311-9-11
109….10311-9-100
1110……11411-9-10-110
129….12311-9-120

The NOCYCLE and CONNECT_BY_ISCYCLE functionality is replicated using the CYCLE clause. By specifying this clause, the cycle is detected and the recursion stops, with the cycle column set to the specified value, to indicate the row where the cycle is detected. Unlike the CONNECT BY NOCYCLE method, which stops at the row before the cycle, this method stops at the row after the cycle.

需要注意的是,递归with语法在检测到循环后,依然会再向下递归一级,而connect语句则不会。

例子

select t1.*, level
  from tab1 t1
 start with t1.id = 1 
connect by nocycle prior t1.id = t1.parent_id;
IDPARENT_IDLEVEL
191
212
323
423
544
644
712
873
912
1093
11104
1293

递归with语法对connect语法的改进

很多人都喜欢把connect语法称为递归查询,然而严格来说这是一个错误的叫法。因为它无法把当前层所计算得到的值传递到下一层。就连官方对它的称呼都是Hierarchical Queries in Oracle (CONNECT BY)
而递归with则彻底改变了这个情况。它的名字都带着递归 Recursive WITH Clauses

辗转相除法求最大公约数,我觉得这是最能验证递归能力的方法。它需要将两个值交换并做一个取余数的操作,再把这个结果传递到下一层中。
先用java简单复习一下

    public static int gcd(int a, int b){
        return a % b == 0 ? b : gcd(b, a % b);
    }

用sql则需要这么写

with t1(id, a1, a2) as (
select 1, 176, 34
  from dual
union all
select id + 1, a2, mod(a1, a2)
  from t1
 where mod(a1, a2) > 0
)
select distinct first_value(t1.a2) over(order by t1.id desc) res from t1;

--2

为了方便理解,我把每一步的结果输出一下

with t1(id, a1, a2) as (
select 1, 176, 34
  from dual
union all
select id + 1, a2, mod(a1, a2)
  from t1
 where mod(a1, a2) > 0
)
select t1.* from t1;
IDA1A2
117634
2346
364
442

这是一个大幅增加sql适用范围的语法,大牛们可以用这个来搞出不少骚操作。

参考资料

https://oracle-base.com/articles/11g/recursive-subquery-factoring-11gr2#setup

  • 9
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Oracle递归查询是一种查询技术,用于查询表中的层次结构数据,例如查询某个节点的父节点或子节点。在Oracle中,可以使用start with connect by prior或with递归查询来实现递归查询。 使用start with connect by prior递归查询,可以查询所有子节点、所有父节点、指定节点的根节点以及指定节点的递归路径。这种查询方法通过在查询条件中使用prior关键字来指定当前数据和下一条数据之间的关系。例如,使用START WITH子句指定起始节点,然后使用CONNECT BY子句指定节点之间的关系,可以实现向上或向下递归查询。 使用with递归查询,可以通过递归调用查询多层结构的子节点或父节点。这种查询方法使用WITH子句定义递归查询的初始条件和递归关系,并使用递归子查询来实现递归查询。 需要注意的是,递归查询可能会导致查询时间特别长,特别是在数据量特别大的情况下。因此,在进行递归查询时,需要谨慎考虑查询的效率和性能。 综上所述,Oracle提供了递归查询语句来实现对层次结构数据的查询,包括start with connect by prior和with递归查询。这些查询方法可以帮助我们方便地查询父节点和子节点的关系。 #### 引用[.reference_title] - *1* [Oracle递归查询](https://blog.csdn.net/Michael_lcf/article/details/124433725)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Oracle递归查询树形数据](https://blog.csdn.net/weixin_40017062/article/details/127653569)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [ORACLE递归查询](https://blog.csdn.net/m0_46636892/article/details/122984132)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值