来自兔子大神blog地址:http://www.itpub.net/thread-1570306-1-1.html
对于有N条记录的来说,如果没有递归条件,直接connect by level,先深度搜索,再广度,则 每个节点作为根节点,然后自身和其他节点为子节点,然后下个子节点还包括自身和其他节点,然后同样迭代 所以,总共记录数有N*2^0+N*2^1+......... 其中0,1....为level 则记F(N,l)为 select id,level from t connect by level F(N,1)=N F(N,l) = F(N,l-1)*N+N 于是可以总结出 F(N,l)=∑power(N,p), p取值为[1,l) 总记录数N,level层数P 结果集数:T=∑N^x(x=1...p) 比如,总记录数为3,层数为3 则结果集数:3^1 +3^2 + 3^3 = 3+9+27=39 |
SELECT rowid,a,b,level,rownum FROM test1 CONNECT BY level<=3; 对于记录数N,则第m层记录数为N^m N=3,LEVEL<=1 s=N LEVEL<=2 s=N^1+N^2 LEVEL<=3 s=N^1+N^2+N^3 ... LEVEL<=m s=N^1+N^2+N^3+....N^m 等比数列q=N,a1=N s=N(1-N^m)/(1-N) 其中m为需要迭代的level最大值 dingjun123@ORADB> select * from t; ID ---------- 1 1 已选择2行。 已用时间: 00: 00: 00.04 dingjun123@ORADB> select rownum from t connect by rownum <= 2; ROWNUM ---------- 1 2 3 已选择3行。 已用时间: 00: 00: 00.01 connect by 的本质分析 执行下面两句: select level,rownum from dual connect by level<=10; select level,rownum from dual connect by rownum<=10; 两句显示的效果完全一样, 但是,如果select有2条或更多,效果就大不一样 select A.TABLE_NAME,level,rownum from (select * from USER_TABLES A WHERE ROWNUM<=2 ) A connect by level<=10; --有2046条 select A.TABLE_NAME, level,rownum from (select * from USER_TABLES WHERE ROWNUM<=2) A connect by rownum<=10 ; --有11条; 所以,想咨询专家 connect by level 和 connect by rownum 的本质区别; 列个关系列表,也许可以通过数学归纳法,得出其中规律 select 的记录数(m) connect by 层数(n) by level 的记录数 by rownum的记录数 1 10 10 10 2 10 2046 11 3 10 88572 12 4 10 1398100 13 by rownum的记录数 规律很明显: connect by 层数(n) +select 的记录数(m) -1; by level 的记录数 的规律还没找出 --深刻理解connect by的原理,理解statr with,connect by,理解层次如何生成的 --理解connect by level和connect by rownum的区别,比如掌握level如何生成,rownum如何生成 --connect by level是先做递归,然后判断level是否满足条件,如果有多行,则每行为根,没有其他条件则 --子节点是自身的子,其他节点也是自身的子。。。循环,因此,connect by level<0也是有结果的,相当于没有写 --connect by level<0是先做递归,先做1次递归,发现level=1,不满足条件,之后结束,并不是丢弃行(和where不同),根总会递归 --但是如果有下属的,不满足条件的,则会丢弃,详细见后面的例子 --connect by rownum,没有其他条件,是直接递归迭代第一行(反复),然后判断rownum条件,不满足条件结束。正是因为 --递归如果没有start with则每行都是根,先第一轮递归,递归到rownum --connect by 1=1就不结束了 --没有start with,没有prior...的迭代,不管是level,还是rownum,每行都是根节点,自身或其它节点是子节点 --区别是connect by level会先深度搜索,也就是根---自身---其他节点 --connect by rownum则是不停迭代自身(第一行),然后判断rownum。。。。 ---有3行 --它只对第一行做循环,到ROWNUM=4的时候停止。然后另外两条叠加上去,这是因为没有START WITH, 所以第一层所有行都会选中。 如果是先把第一层数据拿出来,再循环,那么结果就不是这样了。 CONNECT BY里面最好用LEVEL控制,ROWNUM的生成顺序不是我们能控制的。 dingjun123@ORADB> SELECT rowid,a,b,level,rownum FROM test1 CONNECT BY rownum<7; --迭代第一行,到第rownum=6结束,其他2行直接显示,select rownum不一样 ROWID A B LEVEL ROWNUM ------------------ ---------- ---------- ---------- ---------- AAAVu6AAEAAAsF2AAA 1 1 1 1 --第1到第6反复递归第一行 AAAVu6AAEAAAsF2AAA 1 1 2 2 AAAVu6AAEAAAsF2AAA 1 1 3 3 AAAVu6AAEAAAsF2AAA 1 1 4 4 AAAVu6AAEAAAsF2AAA 1 1 5 5 AAAVu6AAEAAAsF2AAA 1 1 6 6 AAAVu6AAEAAAsF2AAB 1 1 7 --不满足条件,递归结束 AAAVu6AAEAAAsF2AAC 1 2 1 8 --同上 已选择8行。 已用时间: 00: 00: 00.01 dingjun123@ORADB> SELECT rowid,a,b,level,rownum FROM test1 CONNECT BY rownum<-1; --不满足条件,相当于没有写 ROWID A B LEVEL ROWNUM ------------------ ---------- ---------- ---------- ---------- AAAVu6AAEAAAsF2AAA 1 1 1 1 AAAVu6AAEAAAsF2AAB 1 1 2 AAAVu6AAEAAAsF2AAC 1 2 1 3 已选择3行。 已用时间: 00: 00: 00.01 dingjun123@ORADB> SELECT rowid,a,b,level,rownum FROM test1 CONNECT BY rownum<0; --同上 ROWID A B LEVEL ROWNUM ------------------ ---------- ---------- ---------- ---------- AAAVu6AAEAAAsF2AAA 1 1 1 1 AAAVu6AAEAAAsF2AAB 1 1 2 AAAVu6AAEAAAsF2AAC 1 2 1 3 已选择3行。 已用时间: 00: 00: 00.01 dingjun123@ORADB> SELECT rowid,a,b,level,rownum FROM test1 CONNECT BY 1=0; --同上 ROWID A B LEVEL ROWNUM ------------------ ---------- ---------- ---------- ---------- AAAVu6AAEAAAsF2AAA 1 1 1 1 AAAVu6AAEAAAsF2AAB 1 1 2 AAAVu6AAEAAAsF2AAC 1 2 1 3 已选择3行。 已用时间: 00: 00: 00.01 dingjun123@ORADB> SELECT rowid,a,b,level,rownum FROM test1 CONNECT BY level<0; --同上 ROWID A B LEVEL ROWNUM ------------------ ---------- ---------- ---------- ---------- AAAVu6AAEAAAsF2AAA 1 1 1 1 AAAVu6AAEAAAsF2AAB 1 1 2 AAAVu6AAEAAAsF2AAC 1 2 1 3 已选择3行。 dingjun123@ORADB> SELECT rowid,a,b,level,rownum FROM test1 CONNECT BY level<3; --每行为根迭代,自身和其他行是子节点 ROWID A B LEVEL ROWNUM ------------------ ---------- ---------- ---------- ---------- AAAVu6AAEAAAsF2AAA 1 1 1 1 --根,其他包括自身的3行是子 AAAVu6AAEAAAsF2AAA 1 1 2 2 AAAVu6AAEAAAsF2AAB 1 2 3 AAAVu6AAEAAAsF2AAC 1 2 2 4 AAAVu6AAEAAAsF2AAB 1 1 5 AAAVu6AAEAAAsF2AAA 1 1 2 6 AAAVu6AAEAAAsF2AAB 1 2 7 AAAVu6AAEAAAsF2AAC 1 2 2 8 AAAVu6AAEAAAsF2AAC 1 2 1 9 AAAVu6AAEAAAsF2AAA 1 1 2 10 AAAVu6AAEAAAsF2AAB 1 2 11 AAAVu6AAEAAAsF2AAC 1 2 2 12 已选择12行。 SELECT rowid,a,b,level,rownum FROM test1 CONNECT BY level<4; --3条记录有39行 ROWID A B LEVEL ROWNUM AAAVu6AAEAAAsF0AAA 1 1 1 1 ---- AAAVu6AAEAAAsF0AAA 1 1 2 2 AAAVu6AAEAAAsF0AAA 1 1 3 3 第一行到第5行是level=2,第1行自身level=1,level=2 AAAVu6AAEAAAsF0AAB 1 3 4 对于level=3的又从自身开始,N条记录 AAAVu6AAEAAAsF0AAC 1 2 3 5 总数目等比数列和a1(1-q^n)/(1-q)=N(1-N^level)/(1-N) =3(1-3^3)(1-3) AAAVu6AAEAAAsF0AAB 1 2 6 ---- AAAVu6AAEAAAsF0AAA 1 1 3 7 AAAVu6AAEAAAsF0AAB 1 3 8 AAAVu6AAEAAAsF0AAC 1 2 3 9 AAAVu6AAEAAAsF0AAC 1 2 2 10 AAAVu6AAEAAAsF0AAA 1 1 3 11 AAAVu6AAEAAAsF0AAB 1 3 12 AAAVu6AAEAAAsF0AAC 1 2 3 13 AAAVu6AAEAAAsF0AAB 1 1 14 AAAVu6AAEAAAsF0AAA 1 1 2 15 AAAVu6AAEAAAsF0AAA 1 1 3 16 AAAVu6AAEAAAsF0AAB 1 3 17 AAAVu6AAEAAAsF0AAC 1 2 3 18 AAAVu6AAEAAAsF0AAB 1 2 19 AAAVu6AAEAAAsF0AAA 1 1 3 20 AAAVu6AAEAAAsF0AAB 1 3 21 AAAVu6AAEAAAsF0AAC 1 2 3 22 AAAVu6AAEAAAsF0AAC 1 2 2 23 AAAVu6AAEAAAsF0AAA 1 1 3 24 AAAVu6AAEAAAsF0AAB 1 3 25 AAAVu6AAEAAAsF0AAC 1 2 3 26 AAAVu6AAEAAAsF0AAC 1 2 1 27 AAAVu6AAEAAAsF0AAA 1 1 2 28 AAAVu6AAEAAAsF0AAA 1 1 3 29 AAAVu6AAEAAAsF0AAB 1 3 30 AAAVu6AAEAAAsF0AAC 1 2 3 31 AAAVu6AAEAAAsF0AAB 1 2 32 AAAVu6AAEAAAsF0AAA 1 1 3 33 AAAVu6AAEAAAsF0AAB 1 3 34 AAAVu6AAEAAAsF0AAC 1 2 3 35 AAAVu6AAEAAAsF0AAC 1 2 2 36 AAAVu6AAEAAAsF0AAA 1 1 3 37 AAAVu6AAEAAAsF0AAB 1 3 38 AAAVu6AAEAAAsF0AAC 1 2 3 39 ----------------例子--------------- DROP TABLE tt; CREATE TABLE tt(ID number,NAME VARCHAR2(10)); INSERT INTO tt VALUES(1,'aa'); INSERT INTO tt VALUES(2,'bb'); INSERT INTO tt VALUES(3,'cc'); COMMIT; 加个level就明白了,connect by level递归,无其他条件,就是每行都是根,然后自身和其他行是子孙,反复迭代 SQL> select ID,NAME,LEVEL from tt connect by level < 3; ID NAME LEVEL ---------- ---------- ---------- 1 aa 1 --根 1 aa 2 2 bb 2 3 cc 2 --包括自身的3个子孙,满足level<3条件结束 2 bb 1 1 aa 2 2 bb 2 3 cc 2 3 cc 1 1 aa 2 2 bb 2 3 cc 2 12 rows selected 也就是对于记录数为N的,如果level最大为m,那么相应的层次c上面的记录数是N^c,因此所有的记录数就是N+N^2+N^3+....N^m=N(1-N^m)/(1-m), 比如3条记录数的,当level<4的话,m=3,则总记录数为39条。找个规律就行了,也就是先深度搜索 同样,下面的也是每行为根,不同的是只搜索level 比如第一行,虽然id=1,不满足level SQL> select ID,NAME,LEVEL from tt connect by LEVEL < ID; ID NAME LEVEL ---------- ---------- ---------- 1 aa 1 3 cc 2 --bb的id=2不满足条件 2 bb 1 3 cc 2 3 cc 1 3 cc 2 6 rows selected --因为level<=1或level<0都会返回一条数据 select rnum from (select level rnum from dual connect by level <= 2 - 1) where 2 <> 1 / 竟然返回 RNUM --------- 1 with t as (select 1 id from dual union all select 2 from dual) select id,level from t connect by level<=10 order by id,level; select 2*(1-power(2,10))/-1 from dual; q≠1时 Sn=a1(1-q^n)/(1-q)=(a1-anq)/(1-q) q=1时Sn=na1 select * from all_objects connect by rownum<=10; 在某个啥啥帖子里看到 rownum,也就是 COUNT 这个操作是最后生成的,在where之后 SELECT ROWNUM n2 FROM DUAL where level=4 CONNECT BY ROWNUM<=5 意味着. 先 递归第一次,然后level=1 ,此时该level不满足level=4的条件,因此 rownum是0,依然满足rownum<=5. 然后递归第二次,然后level=2,此时该level不满足level=4的条件,因此 rownum是0,依然满足rownum<=5. 然后递归第三次,然后level=3,此时该level不满足level=4的条件,因此 rownum是0,依然满足rownum<=5. 然后递归第四次,然后level=4,此时该level满足level=4的条件,因此 rownum是1,依然满足rownum<=5. 然后递归第五次,然后level=5,此时该level不满足level=4的条件,因此 rownum是1,依然满足rownum<=5. ..... 然后就是天荒地老..海枯石烂..精尽人亡...内存溢出.. 大概是这样 connect by level<=-1也会产生一行,这个要注意 =3不行,必须<=3 SELECT LEVEL,ID,manager_id FROM s_emp START WITH manager_id IS NULL CONNECT BY PRIOR ID=manager_id AND LEVEL<=3; 第1条和第2条要理解递归的本质 dingjun123@ORADB> with t as 2 (select 1 id from dual union all select 2 from dual) 3 select level 4 from t 5 connect by level<3 6 ; LEVEL ---------- 1 2 2 1 2 2 已选择6行。 已用时间: 00: 00: 00.01 dingjun123@ORADB> ed 已写入 file afiedt.buf 1 with t as 2 (select 1 id from dual union all select 2 from dual) 3 select level 4 from t 5 connect by level<3 6* and prior dbms_random.value is not null 7 / LEVEL ---------- 1 2 2 1 2 2 已选择6行。 已用时间: 00: 00: 00.03 已用时间: 00: 00: 00.01 --加上id自连接,必须加上dbms_random,这样相当于每行单独递归的次数,不向上面一样了 dingjun123@ORADB> ed 已写入 file afiedt.buf 1 with t as 2 (select 1 id from dual union all select 2 from dual) 3 select level 4 from t 5 connect by level<3 6 and prior id = id 7* and prior dbms_random.value is not null dingjun123@ORADB> / LEVEL ---------- 1 2 1 2 已选择4行。 已用时间: 00: 00: 00.01 dingjun123@ORADB> ed 已写入 file afiedt.buf 1 with t as 2 (select 1 id from dual union all select 2 from dual) 3 select level 4 from t 5 connect by level<3 6* and prior id = id dingjun123@ORADB> / ERROR: ORA-01436: 用户数据中的 CONNECT BY 循环 未选定行 对于connect by,现在大多数人已经很熟悉了 connect by中的条件就表示了父子之间的连接关系 比如 connect by id=prior pid 但如果connect by中的条件没有表示记录之间的父子关系 那会出现什么情况? 常见的,connect by会在构造序列的时候使用 用select rownum from dual connect by rownum 我们注意到,dual是一个只有一条记录的表,如果表有多条记录,将会怎样? 下面开始实验 环境:windows xp sp2 + Oracle 9208 (10.1版本connect by有问题) CREATE TABLE T ( ID VARCHAR2(1 BYTE) ); INSERT INTO T ( ID ) VALUES ( 'A'); INSERT INTO T ( ID ) VALUES ( 'B'); INSERT INTO T ( ID ) VALUES ( 'C'); COMMIT;SQL> select id,level from t connect by level<2; I LEVEL - ---------- A 1 B 1 C 1 SQL> select id,level from t connect by level<3; I LEVEL - ---------- A 1 A 2 B 2 C 2 B 1 A 2 B 2 C 2 C 1 A 2 B 2 C 2 已选择12行。 SQL> select id,level from t connect by level<4; I LEVEL - ---------- A 1 A 2 A 3 B 3 C 3 B 2 A 3 B 3 C 3 C 2 A 3 B 3 C 3 B 1 A 2 A 3 B 3 C 3 B 2 A 3 B 3 C 3 C 2 A 3 B 3 C 3 C 1 A 2 A 3 B 3 C 3 B 2 A 3 B 3 C 3 C 2 A 3 B 3 C 3 已选择39行。 复制代码无需多说,我们很快可以找到其中的规律,假设表中有N条记录 则记F(N,l)为 select id,level from t connect by level F(N,1)=N F(N,l) = F(N,l-1)*N+N 于是可以总结出 F(N,l)=∑power(N,p), p取值为[1,l) 要解释,也很容易 当连接条件不能限制记录之间的关系时 每一条记录都可以作为自己或者其他记录的叶子 如下所示: A 1 A 2 A 3 B 3 C 3 B 2 A 3 B 3 C 3 C 2 A 3 B 3 C 3 在这里,我们看到的是 Oracle采用了深度优先的算法 我们接着看一个例子,看看在SQL中通过connect by如何将任意一个整数(不要太大就行)拆分为若干个power(2,n)的和的方法。 先构造测试数据: create table ba(n number); insert into ba select 5*rownum from dual connect by rownum<5; commit; select * from ba; 复制代码展示ba中的数据为: N ----------------------- 5 10 15 20 一个得出结果的简单的SQL为 select distinct a.n , level, bitand(a.n,power(2,level-1)) from ba a connect by level<=floor(log(2,n)+1) 复制代码这里为什么要加distinct?你可以尝试去掉distinct ,看看结果与保持distinct有多大差别。 然后我们先来看,如果只对其中的一条记录进行操作,那么加不加distinct,结果是否是一样的?比如我们只看第一条记录5的拆分结果 select distinct a.n , level, bitand(a.n,power(2,level-1)) from (select * from ba where rownum=1) a connect by level<=floor(log(2,n)+1); 复制代码结果为: N LEVEL BITAND(A.N,POWER(2,LEVEL-1)) ---------------------------------------------------------------- 5 1 1 5 2 0 5 3 4 复制代码去掉distinct的sql为 select a.n , level, bitand(a.n,power(2,level-1)) from (select * from ba where rownum=1) a connect by level<=floor(log(2,n)+1); 复制代码输出结果,自己运行一下看看。然后你就该思考了,为什么你看到的结果会是这样??? 这里不做过多解释,做完上面的实验,然后结合1楼中所说的,我想你应该就能明白了。 ———————————————————————我是Long Long Ago的大坑的分界线——————————————————————————— 事实上我们有更好的办法来处理: with a as (select n, floor(log(2,n)+1) lc from ba) select a.n, bitand(a.n,power(2,b.rn-1)) from a, (select rownum rn from (select max(lc) mlc from a) connect by level<=mlc )b where rn<=a.lc order by 1,2 复制代码内层SQL先取得所有记录中可拆分出来的power(2,n)中的n最大可能是多少,然后由此构造出序列,最后再做一次关联查询,用限制条件rn<=a.lc限制住每个N中可拆分出来的power(2,n)中的n的最大值,由此可以高效得出结果。 上例实质上与 对多记录按各自指定次数重复 的性质是一样的。 简单总结: 对单记录/单条数据使用connect by,没问题 但对多条记录使用connect by,就会碰到问题,千万要注意。 |