Thread: Creating N Copies of a Row using "CONNECT BY CONNECT_BY_ROOT"


Permlink Replies: 10 - Pages: 1 - Last Post: Apr 15, 2008 10:45 PM Last Post By: afthab_e
Joe Fuda

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
Click to report abuse...   Click to reply to this thread Reply
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
Volder

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 in response to: Joe Fuda
Click to report abuse...   Click to reply to this thread Reply
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
Joe Fuda

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 in response to: Volder
Click to report abuse...   Click to reply to this thread Reply
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
Volder

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 in response to: Joe Fuda
Click to report abuse...   Click to reply to this thread Reply
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.
michaels2

Posts: 6,119
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 response to: Volder
Click to report abuse...   Click to reply to this thread Reply
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
Joe Fuda

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 in response to: Volder
Click to report abuse...   Click to reply to this thread Reply
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
Vadim Tropashko

Posts: 505
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 in response to: Joe Fuda
Click to report abuse...   Click to reply to this thread Reply
.
Vadim Tropashko

Posts: 505
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 in response to: Joe Fuda
Click to report abuse...   Click to reply to this thread Reply
.
cd_2

Posts: 4,882
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 in response to: Joe Fuda
Click to report abuse...   Click to reply to this thread Reply
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.
Vadim Tropashko

Posts: 505
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 in response to: Joe Fuda
Click to report abuse...   Click to reply to this thread Reply
afthab_e

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 in response to: Joe Fuda
Click to report abuse...   Click to reply to this thread Reply
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 Guru : 2500 - 1000000 pts
Expert Expert : 1000 - 2499 pts
Pro Pro : 500 - 999 pts
Journeyman Journeyman : 200 - 499 pts
Newbie Newbie : 0 - 199 pts
Oracle ACE Director
Oracle ACE Member
Oracle Employee ACE
Helpful Answer (5 pts)
Correct Answer (10 pts)

Point your RSS reader here for a feed of the latest messages in all forums