|
Replies:
10
-
Pages:
1
-
Last Post:
Apr 15, 2008 10:45 PM
Last Post By: afthab_e
|
|
|
Posts:
227
Registered:
02/27/06
|
|
|
|
Creating N Copies of a Row using "CONNECT BY CONNECT_BY_ROOT"
Posted:
Jul 14, 2007 10:28 AM
|
|
|
This isn't a question. I just wanted to share a technique I stumbled upon while
jamming with Vadim Tropashko on his blog recently.
Occasionally the ability to create n copies of a row is required.
For example, given a table with this data
create table t ( key integer, string varchar2(10), qty integer );
insert into t values ( 1, 'a', 2 );
insert into t values ( 2, 'b', 3 );
commit;
KEY STRING QTY
----------
1 a 2
2 b 3
sometimes we need queries that produce 2 copies of row 1 and 3 copies of row 2,
like this.
KEY STRING QTY
----------
1 a 2
1 a 2
2 b 3
2 b 3
2 b 3
A query like the following would be ideal, but unfortunately Oracle doesn't
like the existence of a CONNECT BY loop.
select key, string, qty, level from t
CONNECT BY STRING = PRIOR STRING AND LEVEL <= QTY
order by 1, 4 ;
select key, string, qty, level from t
*
ERROR at line 1:
ORA-01436: CONNECT BY loop in user data
Without the CONNECT BY loop we won't get the right number of rows however.
select key, string, qty, level, sys_connect_by_path( string, '/' ) path
from t
CONNECT BY LEVEL <= QTY
order by 1, 4 ;
KEY STRING QTY LEVEL PATH
----------
----------
1 a 2 1 /a
1 a 2 2 /b/a
1 a 2 2 /a/a
2 b 3 1 /b
2 b 3 2 /b/b
2 b 3 2 /a/b
2 b 3 3 /b/a/b
2 b 3 3 /b/b/b
2 b 3 3 /a/a/b
2 b 3 3 /a/b/b
The final solution employs CONNECT_BY_ROOT to yield the desired result
without triggering an error.
select key, string, qty, level, sys_connect_by_path( string, '/' ) path
from t
CONNECT BY CONNECT_BY_ROOT KEY = KEY AND LEVEL <= QTY
order by 1, 4 ;
KEY STRING QTY LEVEL PATH
----------
----------
1 a 2 1 /a
1 a 2 2 /a/a
2 b 3 1 /b
2 b 3 2 /b/b
2 b 3 3 /b/b/b
Here's an example of this "CONNECT BY CONNECT_BY_ROOT" technique in action.
It splits a comma separated value string into one row per value.
delete from t;
insert into t values( 1, 'a,bb,ccc' , null );
insert into t values( 2, 'dddd,eeeee', null );
commit;
select
string ,
level as position ,
regexp_substr( string, '^,+', 1, level ) as val
from
t
connect by
connect_by_root key = key and
regexp_substr( string, '^,+', 1, level ) is not null
order by
1, 2
;
STRING POSITION VAL
----------
a,bb,ccc 1 a
a,bb,ccc 2 bb
a,bb,ccc 3 ccc
dddd,eeeee 1 dddd
dddd,eeeee 2 eeeee
--
Joe Fuda
SQL Snippets
|
|
|
Posts:
986
Registered:
04/14/07
|
|
|
|
Re: Creating N Copies of a Row using "CONNECT BY CONNECT_BY_ROOT"
Posted:
Jul 14, 2007 10:50 AM
in response to: Joe Fuda
|
|
|
Nice approach.
BTW it is just another way of workaround for CONNECT BY LOOP error.
You can also do this like:
SQL> select key, string, qty, level
2 from t
3 CONNECT BY STRING = PRIOR STRING
4 AND LEVEL <= QTY
5 and prior dbms_random.value is not null
6 order by 1, 4;
KEY STRING QTY LEVEL
--------------------------------------- ---------- --------------------------------------- ----------
1 a 2 1
1 a 2 2
2 b 3 1
2 b 3 2
2 b 3 3
S
|
|
|
Posts:
227
Registered:
02/27/06
|
|
|
|
Re: Creating N Copies of a Row using "CONNECT BY CONNECT_BY_ROOT"
Posted:
Jul 14, 2007 11:49 AM
in response to: Volder
|
|
|
Cool. I didn't know about the dbms_random hack. I suspect the repeated PL/SQL calls would hurt performance though. Here's a quick and dirty comparison.
SQL*Plus: Release 10.2.0.1.0 - Production on Sat Jul 14 14:45:18 2007
set timing on
select count(*) from t connect by
key = prior key and
prior dbms_random.value is not null and
level <=1000000
;
COUNT(*)
2000000
Elapsed: 00:00:18.32
select count(*) from t connect by
connect_by_root key = key and
level <=1000000
;
COUNT(*)
2000000
Elapsed: 00:00:02.48
Are there any other workarounds for the CONNECT BY LOOP error (other than NOCYCLE, which doesn't yield the desired result)?
--
Joe Fuda
SQL Snippets
|
|
|
Posts:
986
Registered:
04/14/07
|
|
|
|
Re: Creating N Copies of a Row using "CONNECT BY CONNECT_BY_ROOT"
Posted:
Jul 14, 2007 2:00 PM
in response to: Joe Fuda
|
|
|
|
Yep, nice comparison.
I have even more considerable difference in perfomance.
Switching from SQL to PL/SQL engine and vice versa makes it's evil job.
Next time I would prefer your solution.
But it is only for 10g.
I tried to make it for previous version (9i) using a substitute for connect_by_root
(replace(sys_connect_by_path(decode(level,1,key), '~'), '~')),
but it gave me an error that sys_connect_by_path cannot be used in the CONNECT BY part.
I didn't find such notification in the doc, but I've found that about CONNECT_BY_ROOT [url=http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/operators004.htm#i1035022]here[/url]:
Restriction on CONNECT_BY_ROOT You cannot specify this operator in the START WITH condition or the CONNECT BY condition.
So is it a bug?
I don't know other methods of workaround for connect by loop error.
Maybe someone else will share with us.
|
|
|
Posts:
5,875
Registered:
09/24/06
|
|
|
|
Re: Creating N Copies of a Row using "CONNECT BY CONNECT_BY_ROOT"
Posted:
Jul 14, 2007 2:18 PM
in response to: Volder
|
|
|
In 9i (but not in 10g!) SYS_GUID() performs better than dbms_random:
michaels> set timing on
michaels> select count(*) from t connect by key = prior key and prior sys_guid() is not null and level <=1000000
COUNT(*)
----------
2000000
Elapsed: 00:00:09.91
michaels> select count(*) from t connect by key = prior key and prior dbms_random.value is not null and level <=1000000
COUNT(*)
----------
2000000
Elapsed: 00:00:24.25
|
|
|
Posts:
227
Registered:
02/27/06
|
|
|
|
Re: Creating N Copies of a Row using "CONNECT BY CONNECT_BY_ROOT"
Posted:
Jul 14, 2007 3:27 PM
in response to: Volder
|
|
|
You cannot specify this operator in the START WITH condition or the CONNECT BY condition
Bah. That's two strikes against the technique. The doco. also says
" one expression in the CONNECT BY condition must be qualified by the PRIOR operator."
which the query does not do. There's some debate about whether this is a documentation bug or whether Oracle should raise an error when PRIOR isn't used. I was willing to ignore this issue because I'm leaning toward it being a doco. bug, but I don't like having two aspects that contradict the doco. now.
BTW, I tried using SYS_CONNECT_BY_PATH on 10g and it doesn't work there either.
SQL*Plus: Release 10.2.0.1.0 - Production on Sat Jul 14 18:18:13 2007
select key, string, sys_connect_by_path(key,'/') path from t connect by
sys_connect_by_path(key,'/') like '/'||to_char(key)||'/%' and
level <= 3
;
sys_connect_by_path(key,'/') like '/'||to_char(key)||'/%' and
*
ERROR at line 2:
ORA-30002: SYS_CONNECT_BY_PATH function is not allowed here
I wonder if they incorrectly documented this SYS_CONNECT_BY_PATH restriction under the CONNECT_BY_ROOT section by mistake?
--
Joe Fuda
SQL Snippets
|
|
|
Posts:
467
Registered:
01/10/01
|
|
|
|
Re: Creating N Copies of a Row using "CONNECT BY CONNECT_BY_ROOT"
Posted:
Sep 19, 2007 2:07 PM
in response to: Joe Fuda
|
|
|
|
|
|
Posts:
467
Registered:
01/10/01
|
|
|
|
Re: Creating N Copies of a Row using "CONNECT BY CONNECT_BY_ROOT"
Posted:
Sep 19, 2007 2:18 PM
in response to: Joe Fuda
|
|
|
|
|
|
Posts:
4,849
Registered:
09/08/98
|
|
|
|
Re: Creating N Copies of a Row using "CONNECT BY CONNECT_BY_ROOT"
Posted:
Sep 19, 2007 2:19 PM
in response to: Joe Fuda
|
|
|
Interesting technique. Last time I had to solve such a problem, I took the "easy" route:
WITH t AS (SELECT 1 key, 'a' string, 2 qty
FROM dual
UNION
SELECT 2, 'b', 3
FROM dual
)
SELECT t.*
FROM t
, (SELECT ROWNUM rn
FROM all_objects
WHERE ROWNUM <= (SELECT MAX(qty)
FROM t
)
) t2
WHERE t2.rn <= t.qty
;
C.
|
|
|
Posts:
467
Registered:
01/10/01
|
|
|
|
Re: Creating N Copies of a Row using "CONNECT BY CONNECT_BY_ROOT"
Posted:
Sep 19, 2007 4:13 PM
in response to: Joe Fuda
|
|
|
|
|
|
Posts:
2
Registered:
12/02/00
|
|
|
|
Re: Creating N Copies of a Row using "CONNECT BY CONNECT_BY_ROOT"
Posted:
Apr 15, 2008 10:45 PM
in response to: Joe Fuda
|
|
|
|
Here is another solution to the same problem not using CONNECT_BY_ROOT
SELECT key,value,qty
FROM t a, (SELECT level lvl FROM dual CONNECT BY level < (SELECT max(qty)+1 FROM t)) b
WHERE a.qty >= b.lvl
ORDER BY 2
|
|
|
|
Legend
|
|
Guru : 2500
- 1000000
pts
|
|
Expert : 1000
- 2499
pts
|
|
Pro : 500
- 999
pts
|
|
Journeyman : 200
- 499
pts
|
|
Newbie : 0
- 199
pts
|
|
Oracle ACE Director
|
|
Oracle ACE Member
|
|
Oracle Employee ACE
|
|
Helpful Answer
(5 pts)
|
|
Correct Answer
(10 pts)
|
|