Home>Question Details



Peter -- Thanks for the question regarding "String Concatenation", version 8.1.6

Submitted on 11-Dec-2001 19:54 Central time zone
Last updated 14-Jul-2009 14:30

You Asked

Hi Tom,

Is there anyway that I can just use SQL to do string
concatenation on a VARCHAR2 field much the same way
that a SUM() function is used on a NUMBER field.

For example,

   SELECT SUM(revenue), department
   FROM   sales_table
   GROUP  BY department;

Here revenue is a NUMBER field.
I'd like to do the same thing when the column is 
a VARCHAR2 field. But instead of SUM, I'd like to
do string concatenation (||).

Can this be done without using PL/SQL?

THanks

- peter 

and we said...

Not in 8i, in 9i yes -- it is very very doable.

see
http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:229614022562
for 8i methods (not aggregates)

in 9i,


scott@ORA9I.WORLD> create or replace type string_agg_type as object
  2  (
  3     total varchar2(4000),
  4  
  5     static function
  6          ODCIAggregateInitialize(sctx IN OUT string_agg_type )
  7          return number,
  8  
  9     member function
 10          ODCIAggregateIterate(self IN OUT string_agg_type ,
 11                               value IN varchar2 )
 12          return number,
 13  
 14     member function
 15          ODCIAggregateTerminate(self IN string_agg_type,
 16                                 returnValue OUT  varchar2,
 17                                 flags IN number)
 18          return number,
 19  
 20     member function
 21          ODCIAggregateMerge(self IN OUT string_agg_type,
 22                             ctx2 IN string_agg_type)
 23          return number
 24  );
 25  /

Type created.

scott@ORA9I.WORLD> 
scott@ORA9I.WORLD> create or replace type body string_agg_type
  2  is
  3  
  4  static function ODCIAggregateInitialize(sctx IN OUT string_agg_type)
  5  return number
  6  is
  7  begin
  8      sctx := string_agg_type( null );
  9      return ODCIConst.Success;
 10  end;
 11  
 12  member function ODCIAggregateIterate(self IN OUT string_agg_type,
 13                                       value IN varchar2 )
 14  return number
 15  is
 16  begin
 17      self.total := self.total || ',' || value;
 18      return ODCIConst.Success;
 19  end;
 20  
 21  member function ODCIAggregateTerminate(self IN string_agg_type,
 22                                         returnValue OUT varchar2,
 23                                         flags IN number)
 24  return number
 25  is
 26  begin
 27      returnValue := ltrim(self.total,',');
 28      return ODCIConst.Success;
 29  end;
 30  
 31  member function ODCIAggregateMerge(self IN OUT string_agg_type,
 32                                     ctx2 IN string_agg_type)
 33  return number
 34  is
 35  begin
 36      self.total := self.total || ctx2.total;
 37      return ODCIConst.Success;
 38  end;
 39  
 40  
 41  end;
 42  /

Type body created.

scott@ORA9I.WORLD> 
scott@ORA9I.WORLD> CREATE or replace
  2  FUNCTION stragg(input varchar2 )
  3  RETURN varchar2
  4  PARALLEL_ENABLE AGGREGATE USING string_agg_type;
  5  /

Function created.

scott@ORA9I.WORLD> 
scott@ORA9I.WORLD> select deptno, stragg(ename)
  2    from emp
  3   group by deptno
  4  /

    DEPTNO
----------
STRAGG(ENAME)
------------------------------------------------------------------------------------------
-----------------------------------------
        10
CLARK,KING,MILLER

        20
SMITH,FORD,ADAMS,SCOTT,JONES

        30
ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD


would be the approach.  

Reviews    
5 stars Wowsers ...   December 11, 2001 - 11pm Central time zone
Reviewer: Mike Wilson from Los Angeles, CA
I just spent a few hours wrapping this logic up in a VB function for my db hitting Oracle 8i:
a) 9i seems to have a few hundred things I need.  This would have completely simplified my logic 
(an put it into the database layer, security, speed, etc ...).
b) Oh, yeah, Tom rocks also ... 


5 stars   December 12, 2001 - 1am Central time zone
Reviewer: A reader 


5 stars And in case if you want to pivot it back...   December 13, 2001 - 12pm Central time zone
Reviewer: Mikito harakiri 
Given

create table COMMAGGR
(
        NUM           NUMBER,
        AGGR           VARCHAR2(2000)
);

insert into COMMAGGR values (1,'1071,12,11103,123404');
insert into COMMAGGR values (2,'15,11116,1,,118');
insert into COMMAGGR values (3,'6');
commit;

we want to parse it back.

1. Using 9i pipelined function to generate a "pseudotable" -- it is much faster than just pulling 
integers from the table that is "big enough":

CREATE or replace FUNCTION UNSAFE
  RETURN  IntSet PIPELINED IS
  i INTEGER;
BEGIN
    i := 0;
    loop
       PIPE ROW(i);
       i:=i+1;
    end loop;
END;
/

1a (Optional). Now we can generate a sequence of 1M integers like this:

select rownum from TABLE(UNSAFE) where rownum < 1000000;

Be aware about stop condition: if you remove the predicate, you'll hung up the client process. Note 
that just adding a parameter to UNSAFE function is inferior to "stop count" predicate -- the code 
is much more self-documenting in the latter case.

2. The query:

select NUM, substr( -- format
             substr( aggr,
                    instr(aggr, ',', 1, i)+1,
                    instr(aggr, ',', 1, i+1)
                    -instr(aggr, ',', 1, i)-1 )
            ,1,20)  -- /format
from ( select rownum i from TABLE(UNSAFE) where rownum < 1000 ), 
     ( select NUM, ','||aggr||',' AGGR from COMMAGGR )
where instr(aggr, ',', 1, i)>0
and instr(aggr, ',', 1, i+1)>0

Alternative solution:

2. Enumerate all integer pairs:

    select t-(xplusy+1)*xplusy/2 y,
        xplusy-t+(xplusy+1)*xplusy/2 x from (
            select FLOOR((SQRT(8*(rownum-1)+1)-1)/2) xplusy,
                   rownum-1 t 
                   from TABLE(UNSAFE) where rownum <1000
    )

3. The query:

select NUM, substr(aggr, beg, len) 
from (
    select t-(xplusy+1)*xplusy/2+1 beg,
        xplusy-t+(xplusy+1)*xplusy/2+1 len from (
            select FLOOR((SQRT(8*(rownum-1)+1)-1)/2) xplusy,
                   rownum-1 t 
                   from TABLE(UNSAFE) where rownum <1000
    )
) interval, COMMAGGR
where ( beg=1 or substr(aggr,beg-1,1)=',' )       
                       -- a parsed string 
                       --usually begins with comma
and ( substr(aggr,beg+len,1)=','      -- it
or beg-1+len = length(aggr) )         -- usually ends with comma as well
                                              
and instr(substr(aggr, beg, len), ',')=0    -- it has no comma in the middle
;


Also, Tom, how can I easily cut and paste your code without line numbers?


 


Followup   December 13, 2001 - 2pm Central time zone:

I cut and paste all of my examples from SQLPlus.  It cuts way down on typos.  I manually reuse my 
own code in here by using an editor...


I'll not be changing the format -- its too nice having the code/response/code/response.  Just 
posting the script and then the response is a hassle and not as nice.

I use vi, I cut and paste the examples into vi and then a simple:

:1,$s/.*COM>/xxxx/
:1,$s/.....//

gets rid of all of the line numbers and sqlplus prompts.  I just have to delete " created", "s 
inserted." and so on after that...

 

5 stars missed definition   December 13, 2001 - 12pm Central time zone
Reviewer: Mikito harakiri 
0.

CREATE TYPE IntSet AS TABLE OF Integer;
/
 


4 stars Another Oracle 8i workaround   June 17, 2002 - 6pm Central time zone
Reviewer: msc from Estonia
Here is another 8i workaround which is of course not perfect :-) but suitable for certain cases.


1. create package which collects strings into pl/sql table for each department.

create or replace package stragg as
  function result(i in integer) return varchar2;
  function collect(i in integer, s in varchar2) return integer;
end;
/

create or replace package body stragg as
  type TStringTable is table of long index by binary_integer;
  tab TStringTable;
  iNullIndex binary_integer := -1; 
  bCanEmpty boolean := False; 
  
  function result(i in integer) return varchar2 is
    itmp binary_integer := nvl(i,iNullIndex);
  begin
    bCanEmpty := True;
    if tab.exists(itmp) then
      return tab(itmp);
    else
      return NULL;
    end if; 
  end;
  
  function collect(i in integer, s in varchar2) return integer is
    itmp binary_integer := nvl(i,iNullIndex);
    stmp long := substr(s,1,4000);
  begin
    if bCanEmpty then -- after result(..) we can clean up here 
      tab.delete;
      bCanEmpty := False;
    end if;
    if tab.exists(itmp) then
      tab(itmp) := substr( tab(itmp)|| '.' || s, 1, 4000);  
    else        
      tab(itmp) := stmp;
    end if;
    return 1; 
  end;

end;
/

2. use in SQL:

SQL> col names for a50
SQL> select deptno, stragg.result(deptno) as NAMES
  2  from emp
  3  where stragg.collect( deptno, ename) = 1
  4  group by deptno
  5  /

    DEPTNO NAMES
---------- --------------------------------------------------
        10 CLARK.KING.MILLER
        20 SMITH.JONES.SCOTT.ADAMS.FORD
        30 ALLEN.WARD.MARTIN.BLAKE.TURNER.JAMES

SQL> 

----
This solutions has one (afaik) pitfall -- one must ensure that stragg.collect is executed.

Following query does *not* work as expected because Oracle optimizes collect() function out:

SQL> select deptno, stragg.result(deptno) as NAMES
  2  from (select deptno, stragg.collect( deptno, ename) as COL from EMP)
  3  group by deptno;

    DEPTNO NAMES
---------- --------------------------------------------------
        10
        20
        30

SQL> 

(btw if this query happens to show right results then they are from *previous* execution)

This subquey can be tricked to work with where clause:

SQL> select deptno, stragg.result(deptno) as NAMES
  2  from (select deptno, stragg.collect( deptno, ename) as COL from EMP)
  3  where COL = 1
  4  group by deptno;

    DEPTNO NAMES
---------- --------------------------------------------------
        10 CLARK.KING.MILLER
        20 SMITH.JONES.SCOTT.ADAMS.FORD
        30 ALLEN.WARD.MARTIN.BLAKE.TURNER.JAMES

SQL>  

 


5 stars Can you use a dynamic separator with the 9i solution?   September 30, 2002 - 8am Central time zone
Reviewer: Daryl from Texas
Is it possible to use the 9i user aggregate solution with a dynamically specified string to use 
between the values? In the example, you used comma (","). Is there a way to specify colons (":") or 
conjunctions ("or ")  without using separate types and function for all the different 
possibilities? 


Followup   October 1, 2002 - 9am Central time zone:

You would have to sort of "prime the pump" on this one.

You could:

dbms_application_info.set_client_info( 'delim=|' )

before running the query and we can use that.  The changes would be:

create or replace type string_agg_type as object
(
   total varchar2(4000),
   delim varchar2(1),
.....



create or replace type body string_agg_type
is

static function ODCIAggregateInitialize(sctx IN OUT string_agg_type)
return number
is
    l_delim  varchar2(1) default ',';
begin
    for x in ( select sys_context('userenv','client_info') client_info from dual )
    loop
        if ( x.client_info like 'delim=%' )
        then
            l_delim := substr( x.client_info, 7, 1 );
        end if;
    end loop;
    sctx := string_agg_type( null, l_delim );
    return ODCIConst.Success;
end;

....


and then:

ops$tkyte@ORA9I.WORLD> column enames format a25
ops$tkyte@ORA9I.WORLD> select deptno, stragg(ename) enames
  2    from emp
  3   group by deptno
  4  /

    DEPTNO ENAMES
---------- -------------------------
        10 CLARK|KING|MILLER
        20 SMITH|FORD|ADAMS|SCOTT|JO
           NES

        30 ALLEN|BLAKE|MARTIN|TURNER
           |JAMES|WARD


3 rows selected.

ops$tkyte@ORA9I.WORLD> 
ops$tkyte@ORA9I.WORLD> exec dbms_application_info.set_client_info( 'delim=|' )

PL/SQL procedure successfully completed.

ops$tkyte@ORA9I.WORLD> 
ops$tkyte@ORA9I.WORLD> select deptno, stragg(ename) enames
  2    from emp
  3   group by deptno
  4  /

    DEPTNO ENAMES
---------- -------------------------
        10 CLARK|KING|MILLER
        20 SMITH|FORD|ADAMS|SCOTT|JO
           NES

        30 ALLEN|BLAKE|MARTIN|TURNER
           |JAMES|WARD


3 rows selected.



 

5 stars Exactly what I was looking for   January 7, 2003 - 11am Central time zone
Reviewer: John 
Thanks Tom!!  This is exactly what I was looking for.  

I did have one problem.  The type compiled fine but the function did not.    My version is  
8.1.7.3.4 on Windows.

SQL> CREATE or replace
  2  FUNCTION stragg(input varchar2 )
  3  RETURN varchar2
  4  PARALLEL_ENABLE AGGREGATE USING string_agg_type;
  5  /

Warning: Function created with compilation errors.

SQL> show errors
Errors for FUNCTION STRAGG:

LINE/COL ERROR
-------- -----------------------------------------------------------------
3/17     PLS-00103: Encountered the symbol "AGGREGATE" when expecting one
         of the following:
         ; is authid deterministic parallel_enable as



My guess is that it may be a parameter that I do not have set, but I have not been able to fine it 
anywhere.


 


Followup   January 8, 2003 - 2pm Central time zone:

Not in 8i, in 9i yes -- it is very very doable.

see

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:229614022562
for 8i methods (not aggregates)

in 9i,
....

in 9i..... 

4 stars Never Mind   January 7, 2003 - 12pm Central time zone
Reviewer: John 
I read you solution wrong.  I don't 9i so I will have to find another way.

Thanks.

 


5 stars Linux vi   January 18, 2003 - 7am Central time zone
Reviewer: Jerry from Washington, DC USA
"
I use vi, I cut and paste the examples into vi and then a simple:
:1,$s/.*COM>/xxxx/
:1,$s/.....//
"


I suspect that you're a regex purist, but knowing that you use Linux, the Linux vi has the ability 
to do a columnar block delete.

You just hit CTRL-V, and "paint" the area you want to delete with the cursor.

Now, if I can just get the mouse to work with it, vi would be a GUI editor...:) 


Followup   January 18, 2003 - 9am Central time zone:

well, there is gvim 

try

SQL> define editor="gvim -f"

 

5 stars Need an order by statement...   February 5, 2003 - 9am Central time zone
Reviewer: Damian Smith from UK
This is almost exactly what I want, except that I want to concatenate strings according to a third 
column called seq_num.  The order of concatenation doesn't appear to be predictable.

My source table has deptno, seq_num, ename.  I have tried an in-line view with an order by clause 
but it doesn't seem to work:

scott@ORA9I.WORLD> select deptno, stragg(ename)
  2    from (
  3    select deptno, seq_num, ename
  4    from emp
  5    order by deptno, seq_num)
  3   group by deptno
  4  /

Many thanks,
Damian
 


Followup   February 5, 2003 - 12pm Central time zone:

Just write a custom function instead of an aggregate.  Aggregates are "SET" things and "sets" 
really don't have any concept of order.


select deptno, f(deptno) from dept;


where f(deptno) runs your query against EMP with an order by and does what you want.... 

3 stars Using only SQL (9i only though : ( )   February 21, 2003 - 10am Central time zone
Reviewer: Alan Patil from Reading, UK
You can do this with 9i just using SQL without any PLSQL

Query to generate a list of values from a child table (in CSV format) for every primary key value 
in a parent table.
 
i.e.
 
if parent table has
 
id
1
2
3
 
and the child table has
 
id     value_id
1      a
1      b
1      c
2      d
3      e    
3      f
 
this query will output
 
id    list of value_ids
1     a,b,c
2     d
3     e,f
 
 
The only issue I can think of is what happens if the list is longer than the varchar2 limit of 4000 
bytes.
 
Obviously this only works with 9i due to the SYS_CONNECT_BY_PATH : (.
 
 
 
-- get the longest csv list of values for each id
select 
id, 
SUBSTR(MAX(list) KEEP (DENSE_RANK FIRST ORDER BY (lev) desc),2) as lov 
from
( 
        -- create a tree and use sys_connect_by_path to create a csv list of values
        select id , SYS_CONNECT_BY_PATH(value_id, ',') list, level lev
        from
        (
                -- create a result set with a linked list column for every id so that we can create 
a hierarchical tree with the next query
                select r.id, i.value_id, rownum prow, 
                LAG(rownum, 1) OVER (PARTITION BY i.id ORDER BY value_id) as connect_id
                from parent_table r, child_table i
                where r.id = i.id
        ) r
        START WITH connect_id is null
        CONNECT BY PRIOR prow=connect_id
)
group by id
order by id
 
 

 


Followup   February 21, 2003 - 3pm Central time zone:

...
The only issue I can think of is what happens if the list is longer than the 
varchar2 limit of 4000 bytes.
.....

use a clob then -- it works, i've done that.

don't understand the spurious reference to sys_connect_by_path???  stragg seems a bit easier?




 

3 stars A straight sql solution   February 21, 2003 - 5pm Central time zone
Reviewer: Jack Mulhollan from Little Rock, AR USA
If the max number of values within each group is finite, known, and relatively small, then straight 
sql will do.  For example, my hypothetical company has a maximum number of five employees per 
department:

SQL> ---------------------
SQL> -- View sample table.
SQL> ---------------------
SQL> 
SQL> select dept,
  2          emp_name
  3  from temp_01_tb
  4  order by 1,2
  5  ;

   DEPT  EMP_NAME
-------  --------------------
      1  Bob
      1  David
      1  Sally
      1  Sam
      2  Jane
      2  Jennifer
      2  Joe
SQL> 
SQL> -------------------------
SQL> -- Perform string concat.
SQL> -------------------------
SQL> 
SQL> select dept,
  2          max(decode(my_seq,1,emp_name)) || ' ' ||
  3          max(decode(my_seq,2,emp_name)) || ' ' ||
  4          max(decode(my_seq,3,emp_name)) || ' ' ||
  5          max(decode(my_seq,4,emp_name)) || ' ' ||
  6          max(decode(my_seq,5,emp_name)) emp_list
  7  from (
  8        select dept,
  9           emp_name,
 10           row_number() over
 11             (partition by dept order by emp_name) my_seq
 12        from temp_01_tb
 13        )
 14  group by dept
 15  order by 1
 16  ;

   DEPT  EMP_LIST
-------  --------------------------------------------
      1  Bob David Sally Sam
      2  Jane Jennifer Joe

 


Followup   February 21, 2003 - 6pm Central time zone:

Yes, that works as well -- very similar to the example I have in expert one on one Oracle to 
demonstrate pivots.

but it is funny -- once you install stragg -- it is "straight sql" as well. 

4 stars analytic sql solutions are disgusting   February 21, 2003 - 8pm Central time zone
Reviewer: Mikito Harakiri 
db1>with ordemp as (select * from emp order by deptno),
  2       seqemp as (select rownum rn, ordemp.* from ordemp),
  3       startfinish as (select min(rn) low, max(rn) high, deptno from seqemp ee group by deptno),
  4    paths as (
  5    select sys_connect_by_path(ename,',') concat, deptno, rn
  6    from seqemp o
  7    connect by rn = prior rn + 1 and deptno = prior deptno
  8    start with rn in (select low from startfinish s where s.deptno = o.deptno)
  9  ) select concat, deptno from paths p where rn in (select high from startfinish s where 
s.deptno = p.deptno);

CONCAT
----------------------------------------------------------------------------------------------------
---------------
    DEPTNO
----------
,CLARK,KING,MILLER
        10

,SMITH,ADAMS,FORD,SCOTT,JONES
        20

,ALLEN,BLAKE,MARTIN,JAMES,TURNER,WARD
        30


db1> 


Followup   February 22, 2003 - 10am Central time zone:

You are amusing sometimes with your analysis and comments you know ;)

Analytic functions ROCK AND ROLL -- coolest thing to happen to SQL since the keyword select.  I 
find AT LEAST 2 or 3 answers a day involve (gasp) analytics.

This will not be the first time I take one of your SQL only solutions and show has dastardly slow, 
hard to read, unfathomable in the real world they actually are.


I find queries that are

o really fast
o readable
o easy to maintain, develop

to be slightly less digusting then queries that

o are not any of the above ;)


Now, I cannot see how ANY index could be used with your psuedo procedural approach (it is so 
procedural as to be scary -- looks almost like a SAS program except it is using SELECT instead of 
proc print; to show the end result).

You sort emp by deptno (all of the rows).
You then assign a rownumber to those ordered rows...
you then find the mininum and maximum rownums by deptno and then procede to query...

So, lets benchmark shall we?  Well use all three approaches.  Then, you tell us which is 
"disgusting"



So, I'll start with:

drop table emp;

create table emp
as
select object_id empno, substr(object_name,1,10) ename, object_type job, data_object_id mgr,
       created hiredate, object_id sal, object_id comm, mod(rownum,500) deptno
  from all_objects
/

Just to generate some data.  TKPROF says:

select deptno,
         max(decode(my_seq, 1,ename)) || ' ' ||
         max(decode(my_seq, 2,ename)) || ' ' ||
         max(decode(my_seq, 3,ename)) || ' ' ||
         ...... 4..67 snipped
         max(decode(my_seq,68,ename)) || ' ' ||
         max(decode(my_seq,69,ename)) emp_list
 from (
       select deptno,
          ename,
          row_number() over
            (partition by deptno order by ename) my_seq
       from emp
       )
 group by deptno
 order by 1


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch       35      1.12       1.13          0        237          0         500
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       37      1.13       1.14          0        237          0         500

Rows     Row Source Operation
-------  ---------------------------------------------------
    500  SORT GROUP BY
  32498   VIEW
  32498    WINDOW SORT
  32498     TABLE ACCESS FULL OBJ#(42644)

so, analytics, while limited in this case as you have to sort of know the MAX number of elements, 
perform quite outstanding here -- don't they (especially as we move on)


********************************************************************************
select deptno, stragg(ename) from emp group by deptno order by deptno


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch       35      4.70       4.93          0        240          0         500
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       37      4.70       4.94          0        240          0         500

Rows     Row Source Operation
-------  ---------------------------------------------------
    500  SORT GROUP BY
  32498   TABLE ACCESS FULL EMP

by far the simpliest solution but a tad more cpu intensive on this set... but wait:


with ordemp as (select * from emp order by deptno),
     seqemp as (select rownum rn, ordemp.* from ordemp),
     startfinish as (select min(rn) low, max(rn) high, deptno from seqemp ee group by deptno),
 paths as (
  select sys_connect_by_path(ename,',') concat, deptno, rn
  from seqemp o
  connect by rn = prior rn + 1 and deptno = prior deptno
  start with rn in (select low from startfinish s where s.deptno = o.deptno))
select concat, deptno
  from paths p
 where rn in (select high from startfinish s where s.deptno = p.deptno)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          6           0
Fetch       35     13.97      15.69          2     259435          1         500
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total       37     13.97      15.70          2     259435          7         500

Rows     Row Source Operation
-------  ---------------------------------------------------
    500
    500  FILTER
  32498   VIEW
  32498   CONNECT BY WITHOUT FILTERING
  32498    COUNT
  32498    VIEW
  32498     TABLE ACCESS FULL SYS_TEMP_4254950912
    500    VIEW
16124250      TABLE ACCESS FULL SYS_TEMP_4254950913
    500   VIEW
16124250       TABLE ACCESS FULL SYS_TEMP_4254950913

that is a query I wouldn't like to see on my system -- slightly too many IO's for my taste and it 
really chomps up the CPU doesn't it?  


So, here is what I would say if you need to do this:


o have a known finite number of elements?  row_number() is looking pretty good
o have an unknown or just looking for the easiest query and you find the result sets you are going 
against perform more then well enought?  stragg
o your psuedo procedural approach?  I don't think so.....

But -- you are free to keep ignoring the existence of language features that can greatly increase 
the performance of your system all you want....


 

5 stars   April 7, 2003 - 10am Central time zone
Reviewer: Arun Mathur from Marietta,GA
Tom,

This is a great site. Would you mind explaining how the PARALLEL_ENABLE option works?

Regards,
Arun 


Followup   April 7, 2003 - 2pm Central time zone:

It permits the user defined aggregate to be called from Parallel query.


http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/statements_59a.htm#2075083

 

5 stars sys_connect_by_path   July 30, 2003 - 4pm Central time zone
Reviewer: A reader 
Tom Can you please explain the post by Alan Patil  where he uses sys_connect_by_path ? 


Followup   July 30, 2003 - 4pm Central time zone:

which part needs explaining?  sys_connect_by_path is documented in the 9i doc set if that is it? 

4 stars analytical sql is not intuitive   July 31, 2003 - 8pm Central time zone
Reviewer: mikito harakiri 
Why

select deptno,
   ename,
   sum(1) over
     (partition by deptno order by deptno) my_seq
from emp

returns 3,3,3,5,5,5,5,5,6,6,6,6,6,6

while

select deptno,
   ename,
   sum(1) over
     (partition by deptno order by empno) my_seq
from emp

returns

1,2,3,1,2,3,4,5,1,2,3,4,5,6

? 


Followup   July 31, 2003 - 9pm Central time zone:

because you added "order by"

the order by makes things "the same".  you order by deptno -- well, isn't everything with the same 
deptno the same? (rhetorical question -- YES)

when you order by empno -- they are NOT the same (empno happens to be unique) so the sum is a pure 
cumulative total.

using sum(1) is "not intuitive" -- i believe you truly meant to use the correct and proper 
"count(*)" syntax.  here is an example:

scott@ORA920LAP> select deptno, ename,
  2         count(*) over (partition by deptno order by deptno) cnt1,
  3         count(*) over (partition by deptno order by deptno, ROWID) cnt2,
  4         count(*) over (partition by deptno order by empno) cnt3,
  5         count(*) over (partition by deptno order by job) cnt4
  6    from emp
  7  /

    DEPTNO ENAME            CNT1       CNT2       CNT3       CNT4
---------- ---------- ---------- ---------- ---------- ----------
        10 CLARK               3          1          1          2
        10 KING                3          2          2          3
        10 MILLER              3          3          3          1

 see with order by deptno -- cnt1 -- all of the rows are "the same".  with order by deptno, rowid 
-- they are "all different" -- rowid makes them different.

order by empno - same thing since empno just happens to be unique.

order by job, boring in this case -- job was "unique"


        20 SMITH               5          1          1          4
        20 JONES               5          2          2          5
        20 SCOTT               5          3          3          2
        20 ADAMS               5          4          4          4
        20 FORD                5          5          5          2

oh, now cnt 4, order by job, is interesting.  apparently ford and scott have the same job, adams 
and smith have the same job, and jones is unique.  since the jobs were the same -- the rows in the 
window defined by the order by are the SAME

        30 ALLEN               6          1          1          6
        30 WARD                6          2          2          6
        30 MARTIN              6          3          3          6
        30 BLAKE               6          4          4          2
        30 TURNER              6          5          5          6
        30 JAMES               6          6          6          1

14 rows selected.



sorry you don't find it "intuitive" -- but it is the way it has to work. 

5 stars FUNCTION stragg in package   August 14, 2003 - 3pm Central time zone
Reviewer: fan from PA
>>scott@ORA9I.WORLD> CREATE or replace
  2  FUNCTION stragg(input varchar2 )
  3  RETURN varchar2
  4  PARALLEL_ENABLE AGGREGATE USING string_agg_type;
  5  /


Tom, how do I properly put this function in a package ?
Thanks 


Followup   August 14, 2003 - 3pm Central time zone:

don't believe you can, it is just a 'definition', no code really.  

3 stars are analytical queries powerful?   August 22, 2003 - 8pm Central time zone
Reviewer: Mikito Harakiri 
I'm still evaluating analytical functions:-)

Here is an exersise. Given
table commissions
        RN ENAME            COMM
---------- ---------- ----------
         1 ADAMS               0
         2 ALLEN             300
         3 BLAKE               0
         4 CLARK               0
         5 FORD                0
         6 JAMES               0
         7 JONES               0
         8 KING                0
         9 MARTIN           1400
        10 MILLER              0
        11 SCOTT               0
        12 SMITH               0
        13 TURNER              0
        14 WARD              500

The output should be

     begin        end       COMM
---------- ---------- ----------
         1          1          0
         2          2        300
         3          8          0
         9          9       1400
        10         13          0
        14         14        500

Clearly, commission should span continuous ranges only, but how would I specify that as a window 
condition?

I have no problems with the other SQL constructs: they scale. An subquery can be used at any place 
where expression is needed, which gives enormous expressiveness. On the other hand, window syntax 
looks like has been invented to solve one particular problem, and there little opportunity to use 
it outside this very specific context. You can refute this ramble with just one query, of course:-) 


Followup   August 23, 2003 - 10am Central time zone:

"clearly" "clearly what????  continous ranges of what?  I've no idea what your basic question is -- 
why should the output be that?  what is the question you are trying to answer -- and more 
importantly where is the subquery query you thing is "better"



i give up.  Mikito -- do what you will.  Like what you like.




I'll write fast queries that perform and scale well.  I'll be using analytics out the whazoo to do 
it.  I'll drop down to relatively inefficient subqueries (in general, not always) when I have to.


 

5 stars how bout ODCITABLE ?   November 19, 2003 - 10pm Central time zone
Reviewer: ant from ny
Tom,
 Stragg, using the ODCI, is very cool. I came across the ODCITABLE interface and thought it was 
interesting.
I noticed that stragg outperforms a "regular" plsql function that does pretty much the same 
operation
and was wondering if a function using ODCITABLE interface would outperform a pipelined function.
I did a search for ODCITABLEFETCH on metalink and it returned no results. I got all of 4 hits ( all 
to the oracle doc ) from google. Can a function using ODCITABLE be used to "stream" result sets 
like pipelined functions ?

The ODCI doc doesn't have a complete example using just pl/sql and the ODCI interface ( the 
examples use OCI and JAVA ).
Can you provide an example that uses the ODCITABLE interface and pl/sql ( no OCI or JAVA calls ) ?

thanks,
 ant
 


4 stars An exception with sys views?   November 20, 2003 - 1am Central time zone
Reviewer: Alex Taylor from Canberra, Australia
All good, however it doesn't work on my 9.2.0.4 database for the this view:
select ADDRESS,stragg(sql_text)
from v$sqltext
where address = '0000000383E4FCA8'
group by ADDRESS;

The aggregate value includes only the last sql_text value of 6 values. Is there something funny 
with sys view? 


Followup   November 21, 2003 - 3pm Central time zone:

don't know what you mean by "only the last sql_text value of 6 values"

give us the "example", show the unaggregated data and the aggregated data and point out what is 
"wrong" 

4 stars Analytic Functions are VERY powerful - get used to it.   November 20, 2003 - 8pm Central time zone
Reviewer: Glen Morris from Canberra, Australia
I tried out the 'challenge' from Mikito above and used Analytic Functions. This may not be the most 
elegant method but since the requirement wasn't stated clearly I'll forgive myself. I've assumed 
the rown wasn't actually in the source table. If it turns out to be then the query is simpler and 
just need to replace all occurences of 'row_number() over (order by ename)' with 'rown'.

SQL> create table zz_comm
  2  (ename varchar2(15),comm number);

Table created.

SQL> 
SQL> insert into zz_comm values ('ADAMS',0);

1 row created.

SQL> insert into zz_comm values ('ALLEN',300);

1 row created.

SQL> insert into zz_comm values ('BLAKE',0);

1 row created.

SQL> insert into zz_comm values ('CLARK',0);

1 row created.

SQL> insert into zz_comm values ('FORD',0);

1 row created.

SQL> insert into zz_comm values ('JAMES',0);

1 row created.

SQL> insert into zz_comm values ('JONES',0);

1 row created.

SQL> insert into zz_comm values ('KING',0);

1 row created.

SQL> insert into zz_comm values ('MARTIN',1400);

1 row created.

SQL> insert into zz_comm values ('MILLER',0);

1 row created.

SQL> insert into zz_comm values ('SCOTT',0);

1 row created.

SQL> insert into zz_comm values ('SMITH',0);

1 row created.

SQL> insert into zz_comm values ('TURNER',0);

1 row created.

SQL> insert into zz_comm values ('WARD',500);

1 row created.

SQL> commit;

Commit complete.

SQL> 
SQL> select start_range
  2        ,nvl(lead(start_range,1) over (order by rown) -1,start_range) end_range
  3        ,comm
  4  from (select row_number() over (order by ename) rown
  5              ,comm
  6              ,case when comm <> nvl(lag(comm,1) over (order by ename),-1)
  7                         then row_number() over (order by ename) else null end start_range
  8          from zz_comm
  9       )
 10  where start_range is not null;

START_RANGE  END_RANGE       COMM
----------- ---------- ----------
          1          1          0
          2          2        300
          3          8          0
          9          9       1400
         10         13          0
         14         14        500

6 rows selected.

SQL> spool off
 


4 stars Awsome STRAGG, how to explicitly order the result?   January 9, 2004 - 4pm Central time zone
Reviewer: Antonio from Houston, TX
Excellent, STRAGG simplified most everything.

Two questions though:

1.  How to specify the order of the data returned by 

select stragg(text_message) as "long_message" from messages;
    
long_message
-------------------------------------
message3,messag2,message4,message1

The table MESSAGES has columns  ID, text_message, usage.
How can I have the results ordered by the ID column (or other column) to get something like 

select stragg(text_message, "ordered by ID or group by ID or where something) from messages

long_message
------------------------------------
message1,message2,message3,message4



2.  What is the best resource(s) for me to read to get up to speed on your example to "understand" 
what the ODCIAggregate* are and to use/apply on my own?
 


Followup   January 10, 2004 - 10am Central time zone:

1) you cannot -- that is one of the "issues" with aggregates.  they work on unordered sets.  Most 
likely the reason there is no such thing as "stragg" in the "real database" since the results are 
somewhat indepterminate as far as order goes.

ROW_NUMBER() is deterministic however since you assign the numbers after ordering.

2) the data cartridge developers guide and application developers guide, both available on 
otn.oracle.com 

3 stars Handy function for sorting elements in a string   January 12, 2004 - 1am Central time zone
Reviewer: Gary from Sydney, Aus
In a pinch, the following may be useful. It splits a string by a delimiter, stores the elements in 
an associative array, then re-extracts them in a sorted order, described in the PL/SQL User's Guide 
as :

"For associative arrays with VARCHAR2 keys, these methods (NEXT and PRIOR) return the appropriate 
key value; ordering is based on the binary values of the characters in the string, unless the 
NLS_COMP initialization parameter is set to ANSI, in which case the ordering is based on the 
locale-specific sort order specified by the NLS_SORT initialization parameter."

Since Associative arrays are a 9i feature, this cannot be ported to 8i or earlier (unless you 
implement your own sort code)

create or replace function sort_string 
  (p_string in varchar2, p_delim in varchar2, 
  p_dup in varchar2 default 'I') return varchar2 is
  type t_sort_tab is table of number(4) index by varchar2(4000);
  t_sort t_sort_tab;
  v_start number := 1;
  v_end number := 0;
  v_ext varchar2(4000);
  v_out varchar2(4000);
begin
  while v_end < length(p_string) loop
    v_end := instr(p_string,p_delim,v_start);
    if v_end = 0 then
      v_end := length(p_string)+1;
    end if;
    v_ext := substr(p_string,v_start,v_end-v_start);
    if t_sort.exists(v_ext) THEN
      t_sort(v_ext) := t_sort(v_ext) + 1;
    else
      t_sort(v_ext) := 1;
    end if;
    v_start := v_end + length(p_delim);
  end loop;
  v_ext := t_sort.first;
  WHILE v_ext is not null loop
    IF p_dup = 'I' THEN
      -- Option A - Ignore duplicates (treat as single occurrence)
      v_out := v_out||v_ext||p_delim;
    ELSIF p_dup = 'N' THEN
      -- Option B - Add number of entries after value in brackates
      v_end := t_sort(v_ext);
      v_out := v_out||v_ext||'('||to_char(v_end)||')'||p_delim;
    ELSIF p_dup = 'D' THEN
      -- Option C - Repeat duplicated entries
      v_end := t_sort(v_ext);
      FOR i in 1..v_end loop
        v_out := v_out||v_ext||p_delim;
      END LOOP;
    END IF;
    v_ext := t_sort.next(v_ext);
  END LOOP;
  v_out := substr(v_out,1,instr(v_out,p_delim,-1)-1);
  return v_out;
end;
 


5 stars Sorting the results   January 13, 2004 - 9am Central time zone
Reviewer: Noam Tene from McLean, VA
This is exactly what I have been trying to do for a while.

My problem was that (as Tom says) aggregates are by definition unsorted.  It gets even worse 
because with parallel execution the order in which ODCIAggregateMerge happens is unspecified and 
may even be non-deterministic.   I am not sure the results are even reproduceable.

In cases where the results is going to be grouped by the value returned by stragg, it is often 
desireable to treat
"CLARK,KING,MILLER" and "KING,CLARK,MILLER" 
as the same combination and to do that it would be useful of the result were lexicographically 
sorted.

The sort_string function from Gary was the missing piece.
I wonder if there is some way to create the associative array in ODCIAggregateInitialize and 
generate the sorted result in ODCIAggregateTerminate. 

That would make the solution much more elegant.

 


4 stars using sragg in Crystal report   February 6, 2004 - 9pm Central time zone
Reviewer: A reader 
I use view with stragg function in Crystal report.
And I had to work with this field, but Crystal report prompted: Can't use blob in formula.
I wonder why blob if return value varchar2? 


Followup   February 7, 2004 - 2pm Central time zone:

I supposed you would have to call Crystal Reports and ask them why? 

4 stars Comment to Glenn's solution   February 9, 2004 - 7pm Central time zone
Reviewer: Mikito Harakiri 
Yes, lag analytic function is critical. Or we can join with c1.rn=c2.rn-1 predicate. Analytical 
functions are for join-challenged folks! 

More significant objection, however, is that analytic functions frequently work together with 
"group by". And in my example I intuitively expect "group by" accociated with those range buckets. 
Instead, you filtered out the rows with some predicate.


 


Followup   February 9, 2004 - 8pm Central time zone:

Analytical functions are for those who crave, desire or just sometimes NEED speed.

 

5 stars Instant Gratification   February 20, 2004 - 2pm Central time zone
Reviewer: Kevin Meade from Connecticut USA
Thanks once more Tom.  Had a guy call me minutes ago who wanted "a way in 9i to concatinate strings 
as an aggregate function, can this be done?".

Natrually being a very experienced IM guy I did what any experienced developer/dba/architect worth 
his salt would do; I stole from somebody else who is better at it than I am.

Damn if I don't look good.  If you could have heard him: "wow, I have never seen anything like this 
before!, this is amazing!, where do you get this stuff?".

I plan on spending some time with him to make sure he verses himself with the technology before he 
does anything real with it.  Can't have people using code they don't understand.

Did I tell you this is now my favorite site on the internet?

Kevin 


3 stars 10g goes one better...   May 11, 2004 - 5am Central time zone
Reviewer: Adrian Billington from UK
Tom

10g SQL now has the COLLECT function which, when combined with a table to string function, seems to 
outperform STRAGG quite significantly. See below for examples.

scott@test10g SQL> CREATE OR REPLACE TYPE ntt_varchar2 AS TABLE OF VARCHAR2(4000);
  2  /

Type created.

scott@test10g SQL> 
scott@test10g SQL> CREATE OR REPLACE FUNCTION to_string (
  2              nt_in        IN ntt_varchar2,
  3              delimiter_in IN VARCHAR2 DEFAULT ','
  4              ) RETURN VARCHAR2 IS
  5  
  6     v_idx PLS_INTEGER;
  7     v_str VARCHAR2(32767);
  8     v_dlm VARCHAR2(10);
  9  
 10  BEGIN
 11  
 12     v_idx := nt_in.FIRST;
 13     WHILE v_idx IS NOT NULL LOOP
 14        v_str := v_str || v_dlm || nt_in(v_idx);
 15        v_dlm := delimiter_in;
 16        v_idx := nt_in.NEXT(v_idx);
 17     END LOOP;
 18  
 19     RETURN v_str;
 20  
 21  END to_string;
 22  /

Function created.

scott@test10g SQL> 
scott@test10g SQL> 
scott@test10g SQL> SELECT deptno
  2  ,      CAST( COLLECT( ename ) AS ntt_varchar2 ) AS emps
  3  FROM   emp
  4  GROUP  BY
  5         deptno;

    DEPTNO EMPS
---------- ---------------------------------------------------------------------------
        10 NTT_VARCHAR2('CLARK', 'KING', 'MILLER')
        20 NTT_VARCHAR2('SMITH', 'JONES', 'SCOTT', 'ADAMS', 'FORD')
        30 NTT_VARCHAR2('ALLEN', 'WARD', 'MARTIN', 'BLAKE', 'TURNER', 'JAMES')

scott@test10g SQL> 
scott@test10g SQL> SELECT deptno
  2  ,      TO_STRING( CAST( COLLECT( ename ) AS ntt_varchar2 ) ) AS emps
  3  FROM   emp
  4  GROUP  BY
  5         deptno;

    DEPTNO EMPS
---------- ---------------------------------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,JONES,SCOTT,ADAMS,FORD
        30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

scott@test10g SQL> 
scott@test10g SQL> SELECT deptno
  2  ,      STRAGG( ename ) AS emps
  3  FROM   emp
  4  GROUP  BY
  5         deptno;

    DEPTNO EMPS
---------- ---------------------------------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,FORD,ADAMS,SCOTT,JONES
        30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

scott@test10g SQL> 
scott@test10g SQL> CREATE TABLE t
  2  AS
  3     SELECT MOD( ROWNUM, 100 ) AS id
  4     ,      CAST( 'A' AS VARCHAR2(1) ) AS val
  5     FROM   dba_objects
  6     ,      TABLE( row_generator(4) );

Table created.

scott@test10g SQL> 
scott@test10g SQL> SELECT COUNT(*) FROM t;

  COUNT(*)
----------
    194236

scott@test10g SQL> 
scott@test10g SQL> ANALYZE TABLE t COMPUTE STATISTICS;

Table analyzed.

scott@test10g SQL> set autotrace traceonly
scott@test10g SQL> exec timer.snap();

PL/SQL procedure successfully completed.

scott@test10g SQL>
scott@test10g SQL> SELECT id
  2  ,      TO_STRING( CAST( COLLECT( val ) AS ntt_varchar2 )) AS vals
  3  FROM   t
  4  GROUP  BY
  5         id;

100 rows selected.


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=88 Card=100 Bytes=300)
   1    0   SORT (GROUP BY) (Cost=88 Card=100 Bytes=300)
   2    1     TABLE ACCESS (FULL) OF 'T' (TABLE) (Cost=73 Card=194272 Bytes=582816)




Statistics
----------------------------------------------------------
        263  recursive calls
        113  db block gets
        469  consistent gets
          7  physical reads
      21084  redo size
      12975  bytes sent via SQL*Net to client
        388  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
        100  rows processed

scott@test10g SQL>
scott@test10g SQL> exec timer.show('COLLECT');
<COLLECT> 0.95 seconds

PL/SQL procedure successfully completed.

scott@test10g SQL> exec timer.snap();

PL/SQL procedure successfully completed.

scott@test10g SQL>
scott@test10g SQL> SELECT id
  2  ,      STRAGG( val ) AS vals
  3  FROM   t
  4  GROUP  BY
  5         id;

100 rows selected.


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=88 Card=100 Bytes=300)
   1    0   SORT (GROUP BY) (Cost=88 Card=100 Bytes=300)
   2    1     TABLE ACCESS (FULL) OF 'T' (TABLE) (Cost=73 Card=194272 Bytes=582816)




Statistics
----------------------------------------------------------
          5  recursive calls
          9  db block gets
        402  consistent gets
        387  physical reads
          0  redo size
      13033  bytes sent via SQL*Net to client
        388  bytes received via SQL*Net from client
          8  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
        100  rows processed

scott@test10g SQL>
scott@test10g SQL> exec timer.show('STRAGG');
<STRAGG> 7.11 seconds

PL/SQL procedure successfully completed.

So that's quite an improvement, given that the COLLECT had to do some cleanout work as well !

Regards
Adrian 


Followup   May 11, 2004 - 8am Central time zone:

Cool, nice idea there.


anytime we can do something in "more pure sql" the better.  what you've effectively done here is 
reduce the context switches from sql to plsql from 196k down to 100.

I like it. 

5 stars How to change STRAGG to use Clob   June 11, 2004 - 6am Central time zone
Reviewer: Ajeet from Bombay.India
Hi Tom,

Stragg does not work if I have large amount of data --as earlier you told that in this case I 
should use CLOB...You have refered to an example at google for this.but I want to use the STRAGG 
for 9i...How can we change it to use CLOB..I have not worked much on Clobs so want to know from 
you.

Thanks 


5 stars Is this ok   June 11, 2004 - 9am Central time zone
Reviewer: Ajeet from Bombay.india
Tom,
I change the Stagg to use clob..it is working too.but want to confirm that it is ok --please take a 
look..
have done nothing just -- renamed the type names and function as i was tesing and used Clob data 
type for all out and total varilable.



create or replace type rmd_agg_type as object
(
total clob,
static function
ODCIAggregateInitialize(sctx IN OUT rmd_agg_type )
return number,
member function
ODCIAggregateIterate(self IN OUT rmd_agg_type ,value IN varchar2 )
return number,
member function
ODCIAggregateTerminate(self IN rmd_agg_type,
                              returnValue OUT  clob,
                             flags IN number)
return number, 
member function
ODCIAggregateMerge(self IN OUT rmd_agg_type,
                        ctx2 IN rmd_agg_type)
return number
);
/

Type created.

create or replace type body rmd_agg_type
is
static function ODCIAggregateInitialize(sctx IN OUT rmd_agg_type)
return number
is
begin
sctx := rmd_agg_type( null );
return ODCIConst.Success;
end;
member function ODCIAggregateIterate(self IN OUT rmd_agg_type,
                                       value IN varchar2 )
return number
is
begin
self.total := self.total || ',' || value;
return ODCIConst.Success;
end;
member function ODCIAggregateTerminate(self IN rmd_agg_type,
                                        returnValue OUT clob,
                                        flags IN number)
return number
is
begin
returnValue := ltrim(self.total,',');
return ODCIConst.Success;
end;
member function ODCIAggregateMerge(self IN OUT rmd_agg_type,
                                    ctx2 IN rmd_agg_type)
return number
is
begin
self.total := self.total || ctx2.total;
return ODCIConst.Success;
end;
end;
/

Type body created.


CREATE or replace
FUNCTION stragg2(input varchar2 )
RETURN clob
PARALLEL_ENABLE AGGREGATE USING rmd_agg_type;
/

Function created. 

Thanks 


Followup   June 11, 2004 - 4pm Central time zone:

the caller might need to close the clob after fetching it (it'll be a temporary clob).  you'll want 
to check that out and be careful if so. 

4 stars STRAGG fails if table has a parallel > 1   June 15, 2004 - 2am Central time zone
Reviewer: Ajeet from Bombay,India
Tom - It seems Stragg function gives an error if table has a parallel value > 1.
It gives me an error --  Like this.

select  c.parameter_name,b.aircraft_id,a.fleet,d.engine_id,f.engine_position,
*
ERROR at line 1:
ORA-00600: internal error code, arguments: [QerpxObjMd2], [], [], [], [], [],
[], []
--

I ran this query 

select /*+ parallel (rdm_f_parameter ,8) */  
c.parameter_name,b.aircraft_id,a.fleet,d.engine_id,f.engine_position,
stragg2(a.parameter_value) ,stragg2(a.flight_datetime)
from 
rdm.rdm_f_parameter a,rdm.rdm_d_aircraft b,rdm.rdm_d_engine d,
rdm.rdm_d_flight_phase e,
rdm.rdm_d_eng_position f,
rdm.rdm_d_param c
where b.aircraft_seq_id = a.aircraft_seq_id
and   d.engine_seq_id   = a.engine_seq_id
and   c.param_seq_id = a.param_seq_id
and   a.engine_position_seq_id = f.engine_position_seq_id
and   a.flight_phase_seq_id =  e.flight_Phase_seq_id
and   c.parameter_name IN  ('DEGT','DEGT_SMOOTHED','DPOIL','DPOIL_SMOOTHED')
and   a.fleet  = 'AAL'
and   b.aircraft_id = 'N3ANAA'
and   e.flight_phase = 'CRUISE'
and   d.engine_id = '875767' 
and   a.flight_datetime > to_Date('01-JAN-2003','dd-MON-yyyy') 
group by c.parameter_name,b.aircraft_id,a.fleet,d.engine_id,f.engine_position
/

and before that I had

alter table rdm_f_parameter parallel 8 ;
---

then I changed 

alter table rdm_f_parameter parallel 8 ;
and remove the paralled hint from above query and then this query worked again.

--
Did not know the reason -- so thought I should post here.
I have not done any further trouble shotting so I could be wrong very much.

Thanks
Ajeet
 


Followup   June 15, 2004 - 3pm Central time zone:

please contact support (for all internal errors) 

5 stars Invokers rights   August 19, 2004 - 2pm Central time zone
Reviewer: A reader 
Should generic "utility" functions like stragg be defined as invokers rights, since they dont 
really access any data in the database? They just process whatever data the invoker throws at it.

Even from a security perspective, it would make sense to define this function either in a separate 
"utility" schema just for such stuff or just make it invokers rights so that the caller doesnt get 
to see any data that he shouldnt see.

So, if I defined the function itself as "authid current_user", am I forced to define the underlying 
object type as 'authid current_user' as well?

Comments?

Thanks 


Followup   August 19, 2004 - 7pm Central time zone:

since they do no sql, and the invoke nothing that does sql -- it matters not that they are invoker 
or definers rights.

it would make sense to place it into a utility schema, yes.  but not for security -- for 
maintenance.

I see no benefit from authid current_user here, no sql, no change. 

4 stars Different delimitd   August 27, 2004 - 10am Central time zone
Reviewer: A reader 
In your response above to having a different delimiter other than ","...

1. I dont see how your change would do it. You just changed 
ODCIAggregateInitialize. But ODCIAggregateIterate is the one where each additional "," is tacked 
on. How would that get the different delimiter?

2. Is there a way to have this defined in the 'stragg' function itself?

i.e.

CREATE or replace  FUNCTION stragg(input varchar2, 
     delimiter varchar2 default ',')
RETURN varchar2
PARALLEL_ENABLE AGGREGATE USING string_agg_type;

instead of using the client_info thing? 

That would be really cool!

Thanks 


Followup   August 27, 2004 - 10am Central time zone:

1) no i didn't, i changed the global variables in the type as well and set the value of one of them 
in the return statement.

2) not that i'm aware of.  it is why i went with the "obscure" approach. 

4 stars   August 27, 2004 - 11am Central time zone
Reviewer: A reader 
Um, still dont get it

all you did was add the delimiter to the type and use that in the Initialize function.

But

12  member function ODCIAggregateIterate(self IN OUT string_agg_type,
 13                                       value IN varchar2 )
 14  return number
 15  is
 16  begin
 17      self.total := self.total || ',' || value;
 18      return ODCIConst.Success;
 19  end;

still tacks on the ",", not l_delim

so how is it working? 


Followup   August 27, 2004 - 11am Central time zone:

it was inferred you would make that change.  sorry, could have made that more clear.  I demostrated 
how to get the global variable set. 

5 stars Straight SQL?   September 9, 2004 - 10am Central time zone
Reviewer: Kashif from Reston, VA
Hi Tom,

A few posts above you said:

<quote>
but it is funny -- once you install stragg -- it is "straight sql" as well.
</quote>

Not sure what you mean there, are you saying that there's no context switch involved in calling an 
aggregate function? Thanks.

Kashif 


Followup   September 9, 2004 - 12pm Central time zone:

no, i mean it is "straight sql" from then on in.

it would be like saying "don't use max()"

after installation, how does stragg materially differ from max? 

5 stars   September 14, 2004 - 8pm Central time zone
Reviewer: A reader 
Just curious, do other databases out there have this user-defined aggregates feature?

How unique is the feature set of Oracle 10g or even 9i as compared to its peers?

Thanks 


Followup   September 15, 2004 - 7am Central time zone:

a quick google search seems to suggest that

db2 has a "technique"
postgress has support  

4 stars Parameterized stragg...   September 15, 2004 - 8am Central time zone
Reviewer: Padders from UK
You can pass object type in place of scalar parameter to aggregate functions and bypass restriction 
on passing in multiple values. Kind of worries me why restriction was there in the first place 
though...

Oracle Database 10g Enterprise Edition Release 10.1.0.2.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options

SQL> CREATE OR REPLACE TYPE concat_expr AS OBJECT (
  2    str VARCHAR2 (4000),
  3    del VARCHAR2 (4000));
  4  /

Type created.

SQL> CREATE OR REPLACE TYPE concat_all_ot AS OBJECT (
  2    str VARCHAR2 (4000),
  3    del VARCHAR2 (4000),
  4  
  5    STATIC FUNCTION odciaggregateinitialize (
  6      sctx IN OUT concat_all_ot)
  7      RETURN NUMBER,
  8  
  9    MEMBER FUNCTION odciaggregateiterate (
 10      SELF IN OUT concat_all_ot,
 11      ctx IN concat_expr)
 12      RETURN NUMBER,
 13  
 14    MEMBER FUNCTION odciaggregateterminate (
 15      SELF IN concat_all_ot,
 16      returnvalue OUT VARCHAR2,
 17      flags IN NUMBER)
 18      RETURN NUMBER,
 19  
 20    MEMBER FUNCTION odciaggregatemerge (
 21      SELF IN OUT concat_all_ot,
 22      ctx2 concat_all_ot)
 23      RETURN NUMBER);
 24  /

Type created.

SQL> CREATE OR REPLACE TYPE BODY concat_all_ot
  2  AS
  3    STATIC FUNCTION odciaggregateinitialize (
  4      sctx IN OUT concat_all_ot)
  5      RETURN NUMBER
  6    IS
  7    BEGIN
  8      sctx := concat_all_ot (NULL, NULL);
  9      RETURN odciconst.success;
 10    END;
 11  
 12    MEMBER FUNCTION odciaggregateiterate (
 13      SELF IN OUT concat_all_ot,
 14      ctx IN concat_expr)
 15      RETURN NUMBER
 16    IS
 17    BEGIN
 18      IF SELF.str IS NOT NULL THEN
 19        SELF.str := SELF.str || ctx.del;
 20      END IF;
 21      SELF.str := SELF.str || ctx.str;
 22      RETURN odciconst.success;
 23    END;
 24  
 25    MEMBER FUNCTION odciaggregateterminate (
 26      SELF IN concat_all_ot,
 27      returnvalue OUT VARCHAR2,
 28      flags IN NUMBER)
 29      RETURN NUMBER
 30    IS
 31    BEGIN
 32      returnvalue := SELF.str;
 33      RETURN odciconst.success;
 34    END;
 35  
 36    MEMBER FUNCTION odciaggregatemerge (
 37      SELF IN OUT concat_all_ot,
 38      ctx2 IN concat_all_ot)
 39      RETURN NUMBER
 40    IS
 41    BEGIN
 42      IF SELF.str IS NOT NULL THEN
 43        SELF.str := SELF.str || SELF.del;
 44      END IF;
 45      SELF.str := SELF.str || ctx2.str;
 46      RETURN odciconst.success;
 47    END;
 48  END;
 49  /

Type body created.

SQL> CREATE OR REPLACE FUNCTION concat_all (
  2    ctx IN concat_expr)
  3    RETURN VARCHAR2 DETERMINISTIC PARALLEL_ENABLE
  4    AGGREGATE USING concat_all_ot;
  5  /

Function created.

SQL> SELECT concat_all (concat_expr (deptno, '|')) deptnos,
  2         concat_all (concat_expr (dname, ',')) dnames
  3  FROM   dept;

DEPTNOS       DNAMES
------------- ------------------------------------
10|20|30|40   ACCOUNTING,RESEARCH,SALES,OPERATIONS

SQL>  


Followup   September 15, 2004 - 9am Central time zone:

nice, sweet - i like that.


 

5 stars using distinct with parameterized stragg   September 22, 2004 - 12pm Central time zone
Reviewer: Oliver from Germany
If you change the definition of concat_expr you even can concatenate distinct strings. 
(Adopted from expert one-on-one)

CREATE OR REPLACE TYPE concat_expr AS OBJECT (
  str VARCHAR2 (4000),
  del VARCHAR2 (4000),
  MAP MEMBER FUNCTION mapping_function RETURN VARCHAR2);
/

CREATE OR REPLACE TYPE BODY concat_expr AS
  MAP MEMBER FUNCTION mapping_function
  RETURN VARCHAR2
  IS
  BEGIN
    RETURN str||del;
  END mapping_function;
END;
/
 


5 stars   October 19, 2004 - 6am Central time zone
Reviewer: A reader from Italy


5 stars Creating index DDL   October 20, 2004 - 3pm Central time zone
Reviewer: A reader 
I am trying to play around with this stragg to create DDL for index creation. Something like

select 'create '||
        case when uniqueness='UNIQUE' then 'unique' else null end||
        ' index '||index_name||' on '||table_name||
        '('||(select stragg(column_name) over (order by column_position) from user_ind_columns b 
              where b.index_name=a.index_name and a.table_owner=b.table_owner
              and a.table_name=b.table_name)
      ||')'
from user_indexes a
where table_owner=:1
and table_name=:2

Of course, this gives me "single-row subquery returns more than one row" for the stragg.

How can this be done?

Thanks 


Followup   October 20, 2004 - 5pm Central time zone:

o dbms_metadata

o exp rows=n, imp indexfile=foo

o CONFIGURATION MANAGEMENT (you know, the fine art of actually having the ddl for your system)


all of those are inifintely better (especially the last one!) than do it yourself ddl extraction.  
DIY DDL extraction is so 1994, this is 2004 - there are much bigger fish to fry.  

http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:1464804639878#16619648888465
read that entire thread, but that particular piece shows how to use max(decode... if you continue 
down this "bad idea" path. 

5 stars   October 20, 2004 - 3pm Central time zone
Reviewer: A reader 
I came up with the following

select ddl from (
select 
'create '||decode(uniqueness,'UNIQUE',uniqueness)||' index '||b.index_name||' on '||
'('||stragg(column_name) over (partition by b.index_name order by column_position)||')' ddl,
row_number() over (partition by b.index_name order by column_position desc) rn
from all_indexes a,all_ind_columns b
where a.owner=b.index_owner and a.index_name=b.index_name
and a.owner=:1
) where rn=1

Seems to work. Is it sound or was I just lucky?

Thanks 


Followup   October 20, 2004 - 5pm Central time zone:

see above. 

5 stars dbms_metadata   October 20, 2004 - 5pm Central time zone
Reviewer: A reader 
I would like to use dbms_metadata, but I dont want just the DDL, I want to add a leading column to 
all the indexes

i.e. if table T has 5 indexes, I want to get get DDL for creating those 5 indexes except that all 
the 5 indexes will have a additional leading column on them.

How can I do this with dbms_metadata?

Thanks 


Followup   October 20, 2004 - 8pm Central time zone:

seems it would be really easy to write a stored procedure that got the DDL and stuffed the column 
name in there immediately after the opening ( ? 

5 stars   October 20, 2004 - 9pm Central time zone
Reviewer: A reader 
"seems it would be really easy to write a stored procedure that got the DDL and stuffed the column 
name in there immediately after the opening ( ? "

Hm, I guess. Also, I need to suppress the "owner." before the index name. You responded on another 
thread that "no, there is not currently any such filter.  you would have to use the xml interface 
and develop a style sheet to do a custom transformation"

Developing a stylesheet for such a simple requirement seems like overkill? Or is this really easier 
than it sounds?

Or would you suggest using replace() to blank out the schema name?

 


Followup   October 20, 2004 - 9pm Central time zone:

if you go the procedure route -- getting the owner off is easy (the beginning of a create index is 
pretty predicable).

replace() would work -- if the schema name isn't used elsewhere in the create, replace('"SCHEMA".', 
'' )

would nuke them all 

5 stars dbms_metadata   October 20, 2004 - 9pm Central time zone
Reviewer: A reader 
Since dbms_metadata returns a CLOB, I suppose I would need to get this into a PL/SQL LONG variable 
and then do all the substr(), replace() stuff? If dbms_metadata returns something more than 32767 
bytes, I am out of luck, right? [DDL more than 32K is a extreme case, but just curious] 


Followup   October 21, 2004 - 6am Central time zone:

not necessarily, you have instr, substr, etc on a clob.  You could move from "clob to clob" using 
32k or less pieces. 

5 stars Excellent   October 21, 2004 - 10am Central time zone
Reviewer: Sasa from Belgrade, Serbia
This is really great about parameterized delimiter and someone provide a way to make a distinct csv 
list using MAP function.
Could you provide example for this as creating type with MAP function member didn't prevent 
appearing same values in csv list.
Thanks 


Followup   October 21, 2004 - 2pm Central time zone:

stragg does this by default? 

5 stars   October 21, 2004 - 3pm Central time zone
Reviewer: A reader 
stragg doesnt elimate dupes by default? You would have to do
stragg(distinct col), right?

Also, what did the previous reader mean by "If you change the definition of concat_expr you even 
can concatenate distinct 
strings"

He posted some code, but I couldnt figure out how to use it?

Thanks 


Followup   October 22, 2004 - 2pm Central time zone:

give a go -- there are lots of examples on this page -- please, give a "good college try" as they 
say.  

the way to understand is to do sometimes... it is how i've learned most of what I now know.. 

5 stars   October 22, 2004 - 3pm Central time zone
Reviewer: A reader 
I did

SQL> SELECT concat_all (concat_expr (rownum,'|')) from all_objects where rownum<=10;

CONCAT_ALL(CONCAT_EXPR(ROWNUM,'|'))
--------------------------------------------------------------------------------
1|2|3|4|5|6|7|8|9|10

SQL> SELECT concat_all (concat_expr (1,'|')) from all_objects where rownum<=10;

CONCAT_ALL(CONCAT_EXPR(1,'|'))
--------------------------------------------------------------------------------
1|1|1|1|1|1|1|1|1|1

SQL> SELECT concat_all (distinct concat_expr (1,'|')) from all_objects where rownum<=10;

CONCAT_ALL(DISTINCTCONCAT_EXPR(1,'|'))
--------------------------------------------------------------------------------
1

I thought his modification would make it such that I dont need the distinct in there i.e. I thought 
it would make 2 and 3 above behave the same, but it didnt.

SO what exactly did that mapping member function do?

The docs say

MAP MEMBER

This clause lets you specify a member function (MAP method) that returns the relative position of a 
given instance in the ordering of all instances of the object. A MAP method is called implicitly 
and induces an ordering of object instances by mapping them to values of a predefined scalar type. 
PL/SQL uses the ordering to evaluate Boolean expressions and to perform comparisons.

If the argument to the MAP method is null, the MAP method returns null and the method is not 
invoked.

An object specification can contain only one MAP method, which must be a function. The result type 
must be a predefined SQL scalar type, and the MAP method can have no arguments other than the 
implicit SELF argument.

But I really cant make sense of the above!

Thanks 


Followup   October 23, 2004 - 9am Central time zone:

the map method returned a string (a varchar2) which was the two elements concatented together -- 
eg: 

1|

in your case.  1| = 1| so distinct would treat them the "same". 

5 stars   October 23, 2004 - 3pm Central time zone
Reviewer: A reader 
I still dont get it. So in what case would that map method really eliminate dupes? 


Followup   October 23, 2004 - 4pm Central time zone:

the MAP doesn't eliminate anything.

the MAP permits "distinct" to "distinct", map returns a scalar (string, number, date) represenation 
of the complex object.  DISTINCT removed the duplicates (based on their MAPPED value) 

5 stars   October 24, 2004 - 12pm Central time zone
Reviewer: A reader 
Hm, so then we are back where we started. Like I said earlier, just passing distinct to your 
original stragg works fine.

select stragg(1) from all_objects where rownum<=5;
will give
1,1,1,1,1

select stragg(distinct 1) from all_objects where rownum<=5;
will give 1

so what exactly is the 'map function' doing for us?

So what e

 


Followup   October 24, 2004 - 1pm Central time zone:

letting you pass in the delimiter quite simply.

to pass in the delimiter, you use a user defined type.
to distinct a user defined time, you need a map. 

5 stars   October 24, 2004 - 1pm Central time zone
Reviewer: A reader 
"letting you pass in the delimiter quite simply.
to pass in the delimiter, you use a user defined type"

Well, the userdefined scalar type let you pass the delimiter, but that was the original 
'concat_expr/concat_all' posted by 'Padders fro UK'.

"to distinct a user defined time, you need a map"

To distinct, all one needs to do is
select concat_all(distinct concat_expr(str,delim)) from ...
This part is no different than using distinct with your original stragg.

Again, so why do we need the map function posted by 'oliver from germany'?

sorry for belaboring this, but I dont see why the map member function is required. 

The map member function is not what lets you pass in the delimiter, the user-defined type does 
that. 

The map member function is not what does the distinct, the DISTINCT operator does that.

So what does the 'map member function' do exactly? 


Followup   October 24, 2004 - 2pm Central time zone:

ops$tkyte@ORA9IR2> SELECT concat_all ( DISTINCT concat_expr (deptno, '|')) deptnos,
  2         concat_all ( DISTINCT concat_expr (dname, ',')) dnames
  3  FROM   scott.dept;
       concat_all ( DISTINCT concat_expr (dname, ',')) dnames
                             *
ERROR at line 2:
ORA-22950: cannot ORDER objects without MAP or ORDER method


to distinct these things, one needs a MAP or order method.


That is what this MAP method is doing...  As stated:

"to distinct a user defined time, you need a map"

it cannot distinct these user defined types without it.

The DISTINCT cannot function without the MAP (or order) 

5 stars odciaggregatemerge   November 24, 2004 - 9am Central time zone
Reviewer: A reader 
I thought the odciaggregatemerge function was only required if I create the function as 
parallel_enable? 

I created a type without the odciaggregatemerge function and a function without specifying the 
parallel_enable and when I ran it, it complained about the type not having odciaggregatemerge.

Isnt odciaggregatemerge required only when multiple parallel threads work on the aggregate and need 
to "sync" their work once in a while during the process? Stricly speaking, only the initialize, 
iterate and terminated are required?

Thanks 


Followup   November 24, 2004 - 9am Central time zone:

seems you have proven your own premise "incorrect" no?

see the data cartridge developers guide on otn.oracle.com, it describes this entire process in 
great detail 

3 stars distinct keyword within aggregate function parameter   November 30, 2004 - 6pm Central time zone
Reviewer: Mikito harakiri 
What possible benefit the keyword "distinct" allowed within aggregate function parameter gives 
besides creating more confusion? Just do distinct in the inner query, and apply user-defined 
aggregate on top of it.  


Followup   November 30, 2004 - 8pm Central time zone:

Let's see:


you hate aggregates and the syntax (either that or you refuse to read the docs and actually 
understand how they are designed.  We are back to looking at the screen and just thinking at it and 
having it come up with 42 for us all by itself)


you hate analytics

geez.....  Ok, I want to know the number of distinct enames and the count of records.

Let's see:

select count(distinct ename), count(*) from emp;


yeah, that is much less clear than:

select count(*), sum(cnt)
  from (select ename, count(*) cnt from emp group by emp);


I almost didn't understand that first query but the second -- a work of art, so much more 
understandable.


NOT

Let's see your totally intuitive approach to this please:

scott@ORA9IR2> select count(distinct job), count(distinct ename), count(distinct mgr) from emp;
 
COUNT(DISTINCTJOB) COUNT(DISTINCTENAME) COUNT(DISTINCTMGR)
------------------ -------------------- ------------------
                 5                   14                  6
 


I want it in one row, just like that.  Please, share with us the intuitive answer you have without 
distinct inside the aggregate.


 

5 stars good example   November 30, 2004 - 9pm Central time zone
Reviewer: mikito 
I'm really surprised that among the zillion way of writing this query there seems nothing compared 
to straightforward

select count(distinct sal), count(*) from emp

which is just one FTS and SORT on top of it. How does it happen that ad-hock solution (distinct 
keyword inside an aggr function) work better?
 


Followup   December 1, 2004 - 8am Central time zone:

what do you mean by "How does it happen that ad-hock 
solution (distinct keyword inside an aggr function) work better?"


what is ad-hoc about using SQL as documented? 

5 stars why count distinct is a hack   December 1, 2004 - 6pm Central time zone
Reviewer: mikito 
Because aggregate-group-by is an extended projection operator. You project away all the columns 
that you no longer need and add aggregate expression instead. For example, in

select deptno, sum(sal) from emp
group by deptno

you eleiminate all the columns except deptno (projection) but add column expression sum(sal) 
instead. Count distinct doesn't fit into this idea. 


Followup   December 1, 2004 - 8pm Central time zone:

so, please (begging here), show me a more intuitive, clear, performant way to do it.

you know, i read the documentation.
I understand what it says.
I use the tools.

I don't make the standards up.  I don't even really care frankly.  I myself want to know what I can 
and cannot do and how to do it and I do it.  Period.

You can make up all kinds of fancy terms -- whatever..

count(distinct ... ) sure is nifty when you need it ain't it.  SQL would be worse off without it.  

tell you what -- ignore its existence (i won't but you can).  I'll have faster code, with more 
answers -- and the "purity" thing doesn't bother me.  I know what it does. I understand what it 
does.  I respect what it does.  Thats all folks.
 

5 stars Clearing the Temporaty CLOB   December 2, 2004 - 2am Central time zone
Reviewer: A reader from Australia from Sydney
I found a use for Ajeet from Bombay.india code (June 11,2004) using a clob and noted your response 
:
<quote>
the caller might need to close the clob after fetching it (it'll be a temporary 
clob).  you'll want to check that out and be careful if so. 
<quote>

When I run the code and check v$temporary_lobs I see a large number of cache_lobs.  (which grow 
with each subsequent execution of the code !).

For example :
SQL> select * from v$temporary_lobs;

       SID CACHE_LOBS NOCACHE_LOBS
---------- ---------- ------------
        92       1224            0

but I have had no luck clearing them programically.  (They go when I exit the session).

I would like to clean up after I am finished with them.  Can you please show me how to do this in 
the context of Ajeet's code as it uses the clob.   Many Thanks !  


Followup   December 2, 2004 - 7am Central time zone:

what is your client code written in.  dbms_lob has an api to close/free open temporary clobs -- 
freetemporary. 

5 stars Clearing the Temporaty CLOB   December 2, 2004 - 6pm Central time zone
Reviewer: A reader from Sydney
Yes, I studied the references to dbms_lob.  
I basically want to output the data from stragg2 in a query that forms a ref cursor in a PL/sql 
package in 9iR2.   I would appreciate some guidance and an example to be sure. 


Followup   December 2, 2004 - 7pm Central time zone:

call dbms_lob.freetemporary in your code.

verify as you are now that they are freed (you have verfied now that they are not freed, make the 
change and check again) 

4 stars Carefull w   December 7, 2004 - 10am Central time zone
Reviewer: Eugen 
If you're using PL/SQL you might experience, what is described in the Note 228479.1 (TEMPORARY LOBS 
are not freed up automatically after PL/SQL block execution).

Fix (as stated in the Note):
>>To workaround the problem, disconnect the database user session.   
>>This behaviour does not reproduce in Oracle10, but is not a bug.   

HtH 


5 stars Dynamic concatenation string   December 7, 2004 - 9pm Central time zone
Reviewer: A reader from Sydney
Thanks Tom and Eugen.

I do not think this qualifies as a new question and Apoligise if I am mistaken.

I need to use CLOBs because of the amount of data being returned.  I would also like to use a soft 
parameter as sometimes we want to use ',' and sometimes chr(10).

I note Padders from UK did this (15 Sep 2004) but with varchar2.  Everything I have tried returns 
ORA-00932 inconsistent datatypes.   I'd appreciate some assistance with this please. 


Followup   December 8, 2004 - 10am Central time zone:

you need to bind a clob to a clob in general.  sounds like you are binding a string to a clob.

in old releases -- you really had to do clob to clob, there was no implicit conversion as there is 
in current releases.

but you really don't give an example of how you were using it. 

5 stars Dynamic concatenation string   December 9, 2004 - 2am Central time zone
Reviewer: A reader from Sydney
I had another look at my code and found the proverbial typo.

I am coding against 9iR1.

This works but I would appreciate any feedback.  Again it follows Padders from UK (15 Sep 2004) 
example.  The trick is to output a CLOB rather than VARCHAR2 in concat_all_ot
and the Member Function ODCIAggregateTerminate.  The Concat_all Function must also return a CLOB.  
But of course you-all knew that already !

So here is a Clobberised version of Padders Code :

SQL> CREATE OR REPLACE TYPE concat_expr AS OBJECT (
  2      str VARCHAR2 (4000),
  3      del VARCHAR2 (4000));
  4  /

Type created.

Elapsed: 00:00:00.00
SQL> 
SQL> CREATE OR REPLACE TYPE concat_all_ot AS OBJECT (
  2      str CLOB,
  3      del VARCHAR2 (4000),
  4    
  5      STATIC FUNCTION odciaggregateinitialize (
  6        sctx IN OUT concat_all_ot)
  7        RETURN NUMBER,
  8    
  9      MEMBER FUNCTION odciaggregateiterate (
 10        SELF IN OUT concat_all_ot,
 11        ctx IN concat_expr)
 12        RETURN NUMBER,
 13    
 14      MEMBER FUNCTION odciaggregateterminate (
 15        SELF IN concat_all_ot,
 16        returnvalue OUT CLOB,
 17        flags IN NUMBER)
 18        RETURN NUMBER,
 19    
 20      MEMBER FUNCTION odciaggregatemerge (
 21        SELF IN OUT concat_all_ot,
 22        ctx2 concat_all_ot)
 23        RETURN NUMBER);
 24  /

Type created.

Elapsed: 00:00:00.00
SQL> 
SQL> CREATE OR REPLACE TYPE BODY concat_all_ot
  2    AS
  3      STATIC FUNCTION odciaggregateinitialize (
  4        sctx IN OUT concat_all_ot)
  5        RETURN NUMBER
  6      IS
  7      BEGIN
  8        sctx := concat_all_ot (NULL, NULL);
  9        RETURN odciconst.success;
 10      END;
 11    
 12      MEMBER FUNCTION odciaggregateiterate (
 13        SELF IN OUT concat_all_ot,
 14        ctx IN concat_expr)
 15        RETURN NUMBER
 16      IS
 17      BEGIN
 18        IF SELF.str IS NOT NULL THEN
 19          SELF.str := SELF.str || ctx.del;
 20        END IF;
 21        SELF.str := SELF.str || ctx.str;
 22        RETURN odciconst.success;
 23      END;
 24    
 25      MEMBER FUNCTION odciaggregateterminate (
 26        SELF IN concat_all_ot,
 27        returnvalue OUT CLOB,
 28        flags IN NUMBER)
 29        RETURN NUMBER
 30      IS
 31      BEGIN
 32        returnvalue := SELF.str;
 33        RETURN odciconst.success;
 34      END;
 35    
 36      MEMBER FUNCTION odciaggregatemerge (
 37        SELF IN OUT concat_all_ot,
 38        ctx2 IN concat_all_ot)
 39        RETURN NUMBER
 40      IS
 41      BEGIN
 42        IF SELF.str IS NOT NULL THEN
 43          SELF.str := SELF.str || SELF.del;
 44        END IF;
 45        SELF.str := SELF.str || ctx2.str;
 46        RETURN odciconst.success;
 47      END;
 48    END;
 49  /

Type body created.

Elapsed: 00:00:00.00
SQL> 
SQL> CREATE OR REPLACE FUNCTION concat_all (
  2      ctx IN concat_expr)
  3      RETURN CLOB DETERMINISTIC PARALLEL_ENABLE
  4      AGGREGATE USING concat_all_ot;
  5  /

Function created.

Elapsed: 00:00:00.00
SQL> 

 


5 stars Thank you   December 9, 2004 - 11am Central time zone
Reviewer: Lam from Germany
the page ist very good!!! 


5 stars Another approach - Using Different Delimiters   January 5, 2005 - 2pm Central time zone
Reviewer: JMattinson from CANADA
  Changing the body of the string_agg_type, you could change these two lines: 
      self.total := self.total || ',' || value;
      returnValue := ltrim(self.total,',');

   To be:

     self.total := SUBSTR(self.total || value, 1, 4000);  -- Fix the error that occurs when too 
many values appended.
     returnValue := self.total;

 Now you can delimit by anything:
 =================================

  select deptno, LTRIM(stragg('|' || ename), '|') enames
    from emp
   group by deptno;
  

    DEPTNO ENAMES
---------- -----------------------------------------
        10 CLARK|KING|MILLER
        20 SMITH|JONES|SCOTT|FORD|ADAMS
        30 ALLEN|MARTIN|JAMES|WARD|TURNER|BLAKE


  select deptno, LTRIM(stragg(' + ' || ename), ' + ') enames
    from emp
   group by deptno;

    DEPTNO ENAMES
---------- -----------------------------------------
        10 CLARK + KING + MILLER
        20 SMITH + JONES + SCOTT + FORD + ADAMS
        30 ALLEN + MARTIN + JAMES + WARD + TURNER + BLAKE
 


Followup   January 5, 2005 - 7pm Central time zone:

I think that silently truncating data, rathering that raising an error is more along the lines of 
introducing a bug rather than fixing an error. 

5 stars How Best to get the Manager Name for an Employee?   February 17, 2005 - 10am Central time zone
Reviewer: Mac 
Using the standard EMP table, what is the best way to get the MGR name for each EMP? 


Followup   February 17, 2005 - 1pm Central time zone:

join
5 stars Lookup Function? Join? Something Else?   February 17, 2005 - 11am Central time zone
Reviewer: A reader 
To be more specific, which? 


Followup   February 17, 2005 - 1pm Central time zone:

just join. 

3 stars Want to make order Ename group by deptno   May 3, 2005 - 3am Central time zone
Reviewer: andrea from Kor
that result is like that 
deptno stragg(ename)
10     CLARK,KING,MILLER
20     SMITH,FORD,ADAMS,SCOTT,JONES
30     ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

select deptno, stragg(ename)
from emp
group by deptno;

but that Stragg(ename) is not ordered 

even though
SQL like that 

select deptno, stragg(ename) from 
  (select deptno, ename from emp order by deptno, ename)
group by deptno;

i Can't expect ordered name like under result. 

deptno stragg(ename)
10     CLARK,KING,MILLER
20     ADAMS,FORD,JONES,SCOTT,SMITH
30     ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

how can i make this??








 


3 stars String Functions   July 19, 2005 - 9am Central time zone
Reviewer: sat from US
Is there any function we can use to find the 
number of occurances of particular word in a string 

  Say 
    STring is ->> David is elder than gary.
    Serach word is  'David'  , the function should return 1
    David is elder than gary, david younger than smith.
    Serach word is  'David'  , the function should return 2

is there any way to find this ?
  


Followup   July 19, 2005 - 10am Central time zone:

ops$tkyte@ORA10G> variable x varchar2(20)
ops$tkyte@ORA10G> exec :x := 'david';
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> select str,
  2         (length(str)-length(replace(upper(str),upper(:x),'')))/length(:x) cnt
  3    from t;
 
STR                                                            CNT
------------------------------------------------------- ----------
david is older than gary                                         1
david is older than gary, david is younger than smith            2
bob is older than gary, bob is younger than smith                0
 
 

4 stars nvl   July 19, 2005 - 10am Central time zone
Reviewer: Gabe 
Need to nvl the length(replace()) call. 


Followup   July 19, 2005 - 10am Central time zone:

true, if the string is just daviddaviddavid ....

nvl( length(), 0 ) 

4 stars One more ..   July 19, 2005 - 10am Central time zone
Reviewer: A reader 
Could you please explain  me this logic. I didn't followed
Why did you use division,length and replace 

  


4 stars   July 19, 2005 - 10am Central time zone
Reviewer: A reader 
Is there any function we can use to find the 
number of occurances of particular word in a string 

  Say 
    STring is ->> David is elder than gary.
    Serach word is  'David'  , the function should return 1
    David is elder than gary, david younger than smith.
    Serach word is  'David'  , the function should return 2

is there any way to find this ?
  


Followup:  
ops$tkyte@ORA10G> variable x varchar2(20)
ops$tkyte@ORA10G> exec :x := 'david';
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA10G>
ops$tkyte@ORA10G> select str,
  2         (length(str)-length(replace(upper(str),upper(:x),'')))/length(:x) 
cnt
  3    from t;
 
STR                                                            CNT
------------------------------------------------------- ----------
david is older than gary                                         1
david is older than gary, david is younger than smith            2
bob is older than gary, bob is younger than smith                0

 


Could you please explain  me this logic. I didn't followed
Why did you use division,length and replace  


Followup   July 19, 2005 - 12pm Central time zone:

see above 

5 stars The question of ordering the values we are concatenating.   August 25, 2005 - 10am Central time zone
Reviewer: Jack Douglas from England
Some time ago, in response to a question about how to the order of the data returned by stragg, you 
said:
  "you cannot -- that is one of the "issues" with aggregates.  they work on unordered sets.  Most 
likely the reason there is no such thing as "stragg" in the 'real database' since the results are 
somewhat indepterminate as far as order goes."

If you are prepared to take the additional performance hit, you can build up the array inside the 
type and calculate and return the ordered concatenation in ODCIAggregateTerminate.

SQL> create or replace type table_of_varchar as table of varchar(4000);
  2  /

Type created.

Elapsed: 00:00:00.00
SQL>
SQL> create or replace type agg_concat_ord as object
  2  (
  3    vals table_of_varchar,
  4    static function ODCIAggregateInitialize (init_context IN OUT agg_concat_ord) return number,
  5    member function ODCIAggregateIterate (self IN OUT agg_concat_ord, this_value IN varchar) 
return number,
  6    member function ODCIAggregateTerminate (self IN agg_concat_ord, result OUT varchar, flags IN 
number) return number,
  7    member function ODCIAggregateMerge (self IN OUT agg_concat_ord, merge_context IN 
agg_concat_ord) return number
  8  );
  9  /

Type created.

Elapsed: 00:00:00.01
SQL>
SQL> create or replace type body agg_concat_ord is
  2    static function ODCIAggregateInitialize (init_context in out agg_concat_ord) return number 
is
  3    begin
  4      init_context := agg_concat_ord (null);
  5      init_context.vals := table_of_varchar();
  6      return ODCIConst.Success;
  7    end;
  8    member function ODCIAggregateIterate (self in out agg_concat_ord, this_value in varchar) 
return number is
  9      begin
 10        self.vals.extend;
 11        self.vals (self.vals.last) := this_value;
 12        return ODCIConst.Success;
 13      end;
 14    member function ODCIAggregateTerminate (self IN agg_concat_ord, result OUT varchar, flags IN 
number ) return number is
 15      begin
 16        for r in (select column_value from table (self.vals) order by 1) loop
 17          result := result || r.column_value;
 18        end loop;
 19        return ODCIConst.Success;
 20      end;
 21    member function ODCIAggregateMerge (self IN OUT agg_concat_ord, merge_context IN 
agg_concat_ord) return number is
 22      i integer;
 23      begin
 24        i := merge_context.vals.first;
 25        while i is not null loop
 26          self.vals.extend;
 27          self.vals (self.vals.last) := merge_context.vals (i);
 28          i := merge_context.vals.next (i);
 29        end loop;
 30        return ODCIConst.Success;
 31      end;
 32  end;
 33  /

Type body created.

Elapsed: 00:00:00.00
SQL>
SQL> create or replace function aggregate_concat_ord (input varchar) return varchar parallel_enable 
aggregate using agg_concat_ord;
  2  /

Function created.

Elapsed: 00:00:00.00
SQL>
SQL> create table temp as select 'KING' as name from dual union select 'CLARK' from dual union 
select 'MILLER' from dual;

Table created.

Elapsed: 00:00:00.00
SQL>
SQL> select substr (aggregate_concat_ord (',' || name), 2) from temp;


SUBSTR(AGGREGATE_CONCAT_ORD(','||NAME),2)
----------------------------------------------------------------------------------------------------
----------------------------------
CLARK,KING,MILLER 


Followup   August 25, 2005 - 2pm Central time zone:

<quote>
If you are prepared to take the additional performance hit, you can build up the 
array inside the type and calculate and return the ordered concatenation in 
ODCIAggregateTerminate.
</quote>
 
I've got that somewhere here too ;)

Maybe not on this page, but somewhere - I remember writing it once upon a time ago .

but yes, you are very much correct. 

5 stars Built in functionality   August 25, 2005 - 5pm Central time zone
Reviewer: Bob B from Albany, NY
Ordered aggregation is also available through analytics, but you have to apply across the entire 
window and then aggregate/distinct it.

SELECT PART, NUMS
FROM (
SELECT MOD( ROWNUM, 2 ) PART, STRAGG( ROWNUM ) OVER ( PARTITION BY MOD( ROWNUM, 2 ) ORDER BY ROWNUM 
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) NUMS
FROM DUAL
CONNECT BY LEVEL <= 100
)
GROUP BY PART, NUMS

Top-N and first rows type queries may suffer on large data sets as this method may require 
materializing of the entire data set before it returns the first row.   


Followup   August 25, 2005 - 6pm Central time zone:

yup, we've done that too :)

(with the same caveats.... precisely the same caveats) 

5 stars as always, your help is most useful   December 5, 2005 - 7pm Central time zone
Reviewer: jeff from Portland, OR
Thanks Tom! This has given us the blueprint to help solve a data warehouse denormalization 'sticky 
wicket'. Not only that, we can apply the solution to a number of different scenarios - can you say 
"Reusable Objects"? 


4 stars How to make COLLECT( ename ) run parallel?   December 19, 2005 - 5pm Central time zone
Reviewer: Charlie Zhu from Victoria, BC Canada
Please help.

It looks like the COLLECT function prevent PARALLEL SQL exec plan.

alter table scott.emp parallel;

SELECT /*+ parallel(e) */ deptno, COLLECT( ename ) emps
FROM   scott.emp e
GROUP  BY deptno;

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     3 |    27 |     2  (50)| 00:00:01 |
|   1 |  SORT GROUP BY     |      |     3 |    27 |     2  (50)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| EMP  |    14 |   126 |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------
 


Followup   December 19, 2005 - 5pm Central time zone:

It does look like a limitation of collect - not sure you can change that behaviour without an 
enhancement request.


we can write a parallel enabled version, would that work for you? 

4 stars Please! make COLLECT( ename ) run parallel.   December 19, 2005 - 6pm Central time zone
Reviewer: Charlie Zhu from Victoria, BC Canada
Thanks, Please.

The FUNCTION stringAgg support PARALLEL SQL by default
PARALLEL_ENABLE AGGREGATE.

But StrAgg even runs parallel, can not beat the COLLECT().

05.73  vs. 4.51 Seconds

(1) StrAgg, noparallel
select listingsid,stringAgg(bsacode) bsacode
from scott.lstrsrch group by listingsid;

86078 rows selected.
Elapsed: 00:00:12.06

(2) collect, noparallel
select listingsid,TO_STRING( CAST( COLLECT( to_char(bsacode) ) AS ntt_varchar2 ) ) AS bsacode
from scott.lstrsrch
group by listingsid;

86078 rows selected.

Elapsed: 00:00:04.51

(3) StrAgg, Parallel
select /*+ parallel(lstrsrch) */ listingsid,stringAgg(bsacode) bsacode from scott.lstrsrch group by 
listingsid;

86078 rows selected.

Elapsed: 00:00:05.73

Execution Plan
----------------------------------------------------------
Plan hash value: 2469243229

----------------------------------------------------------------------------------------------------
---------------------
| Id  | Operation               | Name     | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |    TQ  
|IN-OUT| PQ Distrib |
----------------------------------------------------------------------------------------------------
---------------------
|   0 | SELECT STATEMENT        |          | 86078 |   840K|       |    22  (14)| 00:00:01 |        
|      |            |
|   1 |  PX COORDINATOR         |          |       |       |       |            |          |        
|      |            |
|   2 |   PX SEND QC (RANDOM)   | :TQ10001 | 86078 |   840K|       |    22  (14)| 00:00:01 |  Q1,01 
| P->S | QC (RAND)  |
|   3 |    SORT GROUP BY        |          | 86078 |   840K|  5456K|    22  (14)| 00:00:01 |  Q1,01 
| PCWP |            |
|   4 |     PX RECEIVE          |          |   190K|  1857K|       |    19   (0)| 00:00:01 |  Q1,01 
| PCWP |            |
|   5 |      PX SEND HASH       | :TQ10000 |   190K|  1857K|       |    19   (0)| 00:00:01 |  Q1,00 
| P->P | HASH       |
|   6 |       PX BLOCK ITERATOR |          |   190K|  1857K|       |    19   (0)| 00:00:01 |  Q1,00 
| PCWC |            |
|   7 |        TABLE ACCESS FULL| LSTRSRCH |   190K|  1857K|       |    19   (0)| 00:00:01 |  Q1,00 
| PCWP |            |
----------------------------------------------------------------------------------------------------
---------------------
 


Followup   December 20, 2005 - 8am Central time zone:

with such a small set, I would not even be considering parallel - likely you are spending more time 
setting up parallel execution than you are actually running the query.

Parallel is good for things that take minutes, anything running as fast as a few seconds, I would 
not even *consider* it.

So, unless you have millions of big records - parallel isn't something you want to consider in this 
case. 

4 stars 90 Millions rows master table Join 5 Child tables   December 20, 2005 - 1pm Central time zone
Reviewer: Charlie Zhu from Victoria, BC Canada
<code>Hi Tom,

I guess you will reject my last question,
I have to test it with small portion of real data.

Let me show you the context and background.

M = Millions

We have a books inventory(Listings) table, 91 M (Million) rows, 14GB
and 5 Child table, 1 Fat, 4 Thin

Fat table (ListDocs) got a VARCHAR2(4000) column, avg_row_len is 700 Bytes,110GB table size;
About(99.99%) 1:1 relationship to Master books table.

Thin tables get 50M to 200M rows, avg_row_len is 30 Bytes.
3GB to 7GB table size.

2 of them need to do StringAgg to pivot.
1 of them just need to get Min(Child table PK id).

Here is my SQL,

with e as (select listingsid, Min(VENDCATSID) VENDCATSID from abelisting.Listsvendcats group by listingsid ),
  c as (select listingsid,stringAgg( to_char(bsacode) ) AS bsacode
      from abelisting.Listsbyrsrchattrs group by listingsid ),
  d as (select listingsid,stringAgg( to_char(ABECATEGORIESGROUPID) ) AS ABECATEGORIESGROUPID
      from abelisting.Listingsabecats group by listingsid )
SELECT /*+ parallel(c) parallel(d) parallel(e) use_hash */ l.clientid||chr(9)||l.listingsid||chr(9)||
l.picturecount||chr(9)||l.price||chr(9)||l.quantity||chr(9)||l.quantitylimit||chr(9)||
l.rowdf||chr(9)||l.languageisocode3||chr(9)||
l.rowadddt||chr(9)||l.vendorlistingid||chr(9)||
Substr(ld.xmldocument,1,2000)||chr(9)||
c.bsacode||chr(9)||
d.ABECATEGORIESGROUPID||chr(9)||
e.VENDCATSID||chr(9)||
f.XBNIDENTIFIER||chr(9)
FROM abelisting.listings l, abelisting.listdocs ld , c, d, e, abelisting.lstisbn f
WHERE l.listingsid = ld.listingsid
and l.listingsid = c.listingsid (+)
and l.listingsid = d.listingsid (+)
and l.listingsid = e.listingsid (+)
and l.listingsid = f.listingsid (+);

Exec plan:
----------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation              | Name        | Rows | Bytes |TempSpc| Cost (%CPU)| Time  |  TQ |IN-OUT| PQ Distrib
----------------------------------------------------------------------------------------------------------------------------------------
|  0 | SELECT STATEMENT          |            |  89M|  402G|    | 4839K (1)| 14:47:18 |    |    |     
|  1 | PX COORDINATOR          |            |    |    |    |        |      |    |    |     
|  2 |  PX SEND QC (RANDOM)      | :TQ10007      |  89M|  402G|    | 4839K (1)| 14:47:18 | Q1,07 | P->S | QC (RAND)
|* 3 |  HASH JOIN RIGHT OUTER    |            |  89M|  402G| 6946M| 4839K (1)| 14:47:18 | Q1,07 | PCWP |     
|  4 |  VIEW                |            |  28M|  53G|    | 41715  (2)| 00:07:39 | Q1,07 | PCWP |     
|  5 |    SORT GROUP BY        |            |  28M|  301M| 1370M| 41715  (2)| 00:07:39 | Q1,07 | PCWP |     
|  6 |    PX RECEIVE          |            |  42M|  445M|    | 8909  (1)| 00:01:38 | Q1,07 | PCWP |     
|  7 |    PX SEND HASH        | :TQ10001      |  42M|  445M|    | 8909  (1)| 00:01:38 | Q1,01 | P->P | HASH   
|  8 |      PX BLOCK ITERATOR    |            |  42M|  445M|    | 8909  (1)| 00:01:38 | Q1,01 | PCWC |     
|  9 |      TABLE ACCESS FULL    | LISTINGSABECATS  |  42M|  445M|    | 8909  (1)| 00:01:38 | Q1,01 | PCWP |     
|* 10 |  HASH JOIN OUTER        |            |  89M|  234G| 8645M| 2797K (1)| 08:32:50 | Q1,07 | PCWP |     
|* 11 |    HASH JOIN            |            |  89M|  66G| 1321M| 1102K (1)| 03:22:05 | Q1,07 | PCWP |     
|* 12 |    HASH JOIN RIGHT OUTER  |            |  89M| 9551M|  233M|  215K (1)| 00:39:26 | Q1,07 | PCWP |     
| 13 |    PX RECEIVE          |            |  45M| 1348M|    | 6945  (1)| 00:01:17 | Q1,07 | PCWP |     
| 14 |      PX SEND HASH        | :TQ10002      |  45M| 1348M|    | 6945  (1)| 00:01:17 | Q1,02 | P->P | HASH   
| 15 |      PX BLOCK ITERATOR    |            |  45M| 1348M|    | 6945  (1)| 00:01:17 | Q1,02 | PCWC |     
| 16 |      TABLE ACCESS FULL  | LSTISBN      |  45M| 1348M|    | 6945  (1)| 00:01:17 | Q1,02 | PCWP |     
|* 17 |    HASH JOIN RIGHT OUTER  |            |  89M| 6907M|  158M|  141K (1)| 00:26:00 | Q1,07 | PCWP |     
| 18 |      PX RECEIVE        |            |  34M|  865M|    | 43854  (1)| 00:08:03 | Q1,07 | PCWP |     
| 19 |      PX SEND HASH      | :TQ10003      |  34M|  865M|    | 43854  (1)| 00:08:03 | Q1,03 | P->P | HASH   
| 20 |      VIEW            |            |  34M|  865M|    | 43854  (1)| 00:08:03 | Q1,03 | PCWP |     
| 21 |        SORT GROUP BY    |            |  34M|  432M| 1624M| 43854  (1)| 00:08:03 | Q1,03 | PCWP |     
| 22 |        PX RECEIVE      |            |  35M|  438M|    | 4823  (1)| 00:00:54 | Q1,03 | PCWP |     
| 23 |        PX SEND HASH    | :TQ10000      |  35M|  438M|    | 4823  (1)| 00:00:54 | Q1,00 | P->P | HASH   
| 24 |          PX BLOCK ITERATOR |            |  35M|  438M|    | 4823  (1)| 00:00:54 | Q1,00 | PCWC |     
| 25 |          TABLE ACCESS FULL| LISTSVENDCATS  |  35M|  438M|    | 4823  (1)| 00:00:54 | Q1,00 | PCWP |     
| 26 |      PX RECEIVE        |            |  89M| 4690M|    | 50618  (1)| 00:09:17 | Q1,07 | PCWP |     
| 27 |      PX SEND HASH      | :TQ10004      |  89M| 4690M|    | 50618  (1)| 00:09:17 | Q1,04 | P->P | HASH   
| 28 |      PX BLOCK ITERATOR  |            |  89M| 4690M|    | 50618  (1)| 00:09:17 | Q1,04 | PCWC |     
| 29 |        TABLE ACCESS FULL  | LISTINGS      |  89M| 4690M|    | 50618  (1)| 00:09:17 | Q1,04 | PCWP |     
| 30 |    PX RECEIVE          |            |  89M|  57G|    |  414K (1)| 01:15:59 | Q1,07 | PCWP |     
| 31 |    PX SEND HASH        | :TQ10005      |  89M|  57G|    |  414K (1)| 01:15:59 | Q1,05 | P->P | HASH   
| 32 |      PX BLOCK ITERATOR    |            |  89M|  57G|    |  414K (1)| 01:15:59 | Q1,05 | PCWC |     
| 33 |      TABLE ACCESS FULL    | LISTDOCS      |  89M|  57G|    |  414K (1)| 01:15:59 | Q1,05 | PCWP |     
| 34 |    VIEW              |            |  85M|  160G|    |  114K (2)| 00:21:02 | Q1,07 | PCWP |     
| 35 |    SORT GROUP BY        |            |  85M|  814M| 4320M|  114K (2)| 00:21:02 | Q1,07 | PCWP |     
| 36 |    PX RECEIVE          |            |  139M| 1326M|    | 22642  (1)| 00:04:10 | Q1,07 | PCWP |     
| 37 |      PX SEND HASH        | :TQ10006      |  139M| 1326M|    | 22642  (1)| 00:04:10 | Q1,06 | P->P | HASH   
| 38 |      PX BLOCK ITERATOR    |            |  139M| 1326M|    | 22642  (1)| 00:04:10 | Q1,06 | PCWC |     
| 39 |      TABLE ACCESS FULL  | LISTSBYRSRCHATTRS |  139M| 1326M|    | 22642  (1)| 00:04:10 | Q1,06 | PCWP |     
----------------------------------------------------------------------------------------------------------------------------------------

My questions are:
1) WITH, (Subquery) or Temporary table to hold interim results for StringAgg pre process.

You mentioned in your 3rd book(Expert Oracle Database Architecture), Let Oracle SQL handle the big query, no need to use Temp table to hold the interim result. I don't know if it apply to my situation, I did not test the (SubQ

Followup   December 20, 2005 - 2pm Central time zone:

2) won't happen.  we can possibly push the pga workarea to 4gig (maybe) but that's it.



3) we would have to *write it*, it would be variation on the existing stragg function.


 

2 stars The table join order, make small or big table as driven table?   December 20, 2005 - 2pm Central time zone
Reviewer: Charlie Zhu from Victoria, BC Canada
Hi Tom,

You answer is too fast and simple this time. :)

About the Table read sequence,
should I put the big table ListDocs to the end and hash join and read it at last ? or is OK read it 
in the middle?

I guess the Temporary tablespace used effected by this.

I use SubQuery to put it to the last read:
( test it now, it'll take 4 to 10 hours in the test server, 12G memory, but disk I/O band is 1/10 
of PROD server disk array)

select --+ ordered 
 qin.*, Substr(ld.xmldocument,1,2000) 
from (
with e as (select listingsid, Min(VENDCATSID) VENDCATSID from scott.lstvendcat group by listingsid 
),
     ...
SELECT ...
FROM  scott.lst l, c, d, e, scott.lstisbn f
WHERE l.listingsid = c.listingsid (+)
...) 
     qin,   scott.lstdoc ld 
where qin.listingsid = ld.listingsid ;
 


Followup   December 20, 2005 - 2pm Central time zone:

well the question (in a review/followup section) was big and long - so it made up for it :)

I read these things *fast* (have to, well over 1,000 a month....)  

I'd rather be getting some new questions...


put the tables in any order, it is the job of the CBO to reorder them (in my opinion) 

3 stars COLLECT( ename ) and read-only physical standby database   December 21, 2005 - 12pm Central time zone
Reviewer: Charlie Zhu from Victoria, BC Canada
When I execute SQL,

SELECT deptno, COLLECT( ename ) AS emps
FROM   scott.emp
GROUP  BY deptno;

I got ERROR at line 1:
ORA-16000: database open for read-only access

stringAgg(ename) and concat_all (concat_expr (ename, ',')) works.

Is it possible to fix it?

Where is your lastest version of StrAgg?

By the way, if you don't have time to go throught the question, just leave it.
I can wait 1 week, 2 weeks, 1 month ...,
it's better than this kind of anwswer, I almost got nothing,
Just a suggestion. ;)

Thanks 10k.
 


Followup   December 21, 2005 - 7pm Central time zone:

that is interesting, if you enable sql_trace first - what do you see in the trace file?   

3 stars COLLECT( ename ) and read-only physical standby database   December 22, 2005 - 1pm Central time zone
Reviewer: Charlie Zhu from Victoria, BC Canada
How to upload the trace file?
I only can read the tkprof report, don't know how to read the raw trace file.

Do I need to contact Oracle Support?

Our whole system department are going to see movie this afternoon, so wonderful here!

Happy new year. 


Followup   December 22, 2005 - 5pm Central time zone:

just edit the trace file (if you have expert one on one Oracle - I describe the contents in depth). 
 We'd be looking for the statement that "failed" - search for "err" 

3 stars The err in trace file   December 23, 2005 - 2pm Central time zone
Reviewer: Charlie Zhu from Victoria, BC Canada
=====================
PARSING IN CURSOR #18 len=331 dep=2 uid=0 oct=6 lid=0 tim=1108668952834875 hv=2997034431 
ad='dad810d0'
update obj$ set 
obj#=:6,type#=:7,ctime=:8,mtime=:9,stime=:10,status=:11,dataobj#=:13,flags=:14,oid$=:15,spare1=:16, 
spare2=:17 where owner#=:1 and name=:2 and namespace=:3 and(remoteowner=:4 or remoteowner is null 
and :4 is null)and(linkname=:5 or linkname is null and :5 is null)and(subname=:12 or subname is 
null and :12 is null)
END OF STMT
PARSE #18:c=30000,e=21941,p=3,cr=57,cu=0,mis=1,r=0,dep=2,og=4,tim=1108668952834867
EXEC #18:c=0,e=8941,p=1,cr=4,cu=2,mis=1,r=0,dep=2,og=4,tim=1108668952844030
ERROR #18:err=16000 tim=447072081
EXEC #17:c=70000,e=143569,p=17,cr=253,cu=3,mis=0,r=0,dep=1,og=4,tim=1108668952844384
ERROR #17:err=604 tim=447072082
STAT #18 id=1 cnt=0 pid=0 pos=1 obj=0 op='UPDATE  (cr=0 pr=0 pw=0 time=25 us)'
STAT #18 id=2 cnt=1 pid=1 pos=1 obj=37 op='INDEX RANGE SCAN I_OBJ2 (cr=3 pr=0 pw=0 time=55 us)'
=====================
Sorry, I did not read Expert one on one yet.
I'm a little addict to "new" Tech.
 


Followup   December 23, 2005 - 2pm Central time zone:

ok, easy to see now - it is recursive sql to create a type to collect into - a "fake" type.

You will solve this by creating a REAL type to be used:

create or replace type ename_type as table of varchar2(30)
/

select deptno, cast( collect(ename) as ename_type ) enames
  from emp
 group by deptno
/


You'll create the real type in production - wait for dataguard (assuming that is what you are 
using) to propagate it over to the standby and when you open it read only - that'll not attempt to 
do any recursive sql to create a new type. 

3 stars Error message   December 23, 2005 - 2pm Central time zone
Reviewer: Charlie Zhu from Victoria, BC Canada
oerr ora 604
00604, 00000, "error occurred at recursive SQL level %s"
// *Cause:  An error occurred while processing a recursive SQL statement
//         (a statement applying to internal dictionary tables).
// *Action: If the situation described in the next error on the stack
//         can be corrected, do so; otherwise contact Oracle Support.


@abelabs-em64t-32bit /home/oracle/admin/lab/maint>oerr ora 

16000
16000, 00000, "database open for read-only access"
// *Cause:  The database has been opened for read-only access.  Attempts to
//          modify the database using inappropriate DML or DDL statements
//          generate this error.
// *Action: In order to modify the database, it must first be shut down and
//          re-opened for read-write access.
 


3 stars CAST COLLECT( )   December 23, 2005 - 3pm Central time zone
Reviewer: Charlie Zhu from Victoria, BC Canada
Hi Tom,

I got same err again.

Sorry to bother you before Christmas,
I can run it in PROD too if not possible in Standby db.

abelisting@vicrpt_vicdb02> SELECT deptno, CAST( COLLECT( ename ) AS ntt_varchar2) AS emps
  2  FROM   scott.emp
  3  GROUP  BY deptno;
SELECT deptno, CAST( COLLECT( ename ) AS ntt_varchar2) AS emps
*
ERROR at line 1:
ORA-16000: database open for read-only access

SQL> desc abelisting.ntt_varchar2
 abelisting.ntt_varchar2 TABLE OF VARCHAR2(4000)

=====================
PARSING IN CURSOR #7 len=68 dep=1 uid=0 oct=77 lid=0 tim=1108760204948184 hv=0 ad='f6409e08'
CREATE TYPE "SYSTPCJ4IWk5JOVzgQAoKKQgB5A==" AS TABLE OF VARCHAR2(10)
END OF STMT
PARSE #7:c=0,e=217,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=4,tim=1108760204948173
=====================
PARSING IN CURSOR #8 len=198 dep=2 uid=0 oct=3 lid=0 tim=1108760204949632 hv=4125641360 
ad='db236204'
select obj#,type#,ctime,mtime,stime,status,dataobj#,flags,oid$, spare1, spare2 from obj$ where 
owner#=:1 and name=:2 and namespace=:3 and remoteowner is null and linkname is null and subname is 
null
END OF STMT
PARSE #8:c=10000,e=593,p=0,cr=0,cu=0,mis=1,r=0,dep=2,og=4,tim=1108760204949621
EXEC #8:c=0,e=1543,p=0,cr=0,cu=0,mis=1,r=0,dep=2,og=4,tim=1108760204951416
FETCH #8:c=0,e=68,p=0,cr=4,cu=0,mis=0,r=0,dep=2,og=4,tim=1108760204951571
EXEC #8:c=0,e=55,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=1108760204952304
FETCH #8:c=0,e=245,p=0,cr=5,cu=0,mis=0,r=1,dep=2,og=4,tim=1108760204952606
=====================
PARSING IN CURSOR #9 len=331 dep=2 uid=0 oct=6 lid=0 tim=1108760204953429 hv=2997034431 
ad='db395e4c'
update obj$ set 
obj#=:6,type#=:7,ctime=:8,mtime=:9,stime=:10,status=:11,dataobj#=:13,flags=:14,oid$=:15,spare1=:16, 
spare2=:17 where owner#=:1 and name=:2 and namespace=:3 and(remoteowner=:4 or remoteowner is null 
and :4 is null)and(linkname=:5 or linkname is null and :5 is null)and(subname=:12 or subname is 
null and :12 is null)
END OF STMT
PARSE #9:c=0,e=692,p=0,cr=0,cu=0,mis=1,r=0,dep=2,og=4,tim=1108760204953418
EXEC #9:c=10000,e=5665,p=1,cr=4,cu=2,mis=1,r=0,dep=2,og=4,tim=1108760204959336
ERROR #9:err=16000 tim=456415970
EXEC #7:c=20000,e=11387,p=1,cr=15,cu=3,mis=0,r=0,dep=1,og=4,tim=1108760204959742
ERROR #7:err=604 tim=456415970
STAT #9 id=1 cnt=0 pid=0 pos=1 obj=0 op='UPDATE  (cr=0 pr=0 pw=0 time=24 us)'
STAT #9 id=2 cnt=1 pid=1 pos=1 obj=37 op='INDEX RANGE SCAN I_OBJ2 (cr=3 pr=0 pw=0 time=54 us)'
=====================

Here is wishing you all a Merry Christmas and a New Year bright with joy and success. 


Followup   December 23, 2005 - 4pm Central time zone:

guess I should have tested that in a read only database - i was relying on the trace only.  It is 
trying to create the "hidden" type after the database startup.

 

3 stars COLLECT( ) and PGA limit   December 28, 2005 - 2pm Central time zone
Reviewer: Charlie Zhu from Victoria, BC Canada
Where I set rownum < 500,000, it's OK.
I enlarged to rownum < 1,000,000, err jump out.

Oracle PGA: 1G

I need to process 100 million rows, it's a thin table.

Here is the SQL and err message:

select listingsid,TO_STRING( CAST( COLLECT( to_char(bsacode) ) AS ntt_varchar2 ) ) AS bsacode
from int_tmp_sa
where rownum < 1000000
group by listingsid;
*
ERROR at line 1:
ORA-29400: data cartridge error
ORA-04030: out of process memory when trying to allocate 1036 bytes (koh dur heap d,kghsseg : 
kokbCollInitialize)

or 

*
ERROR at line 1:
ORA-29400: data cartridge error
ORA-04030: out of process memory when trying to allocate 65548 bytes (koh dur heap 
d,kokbCollMergebuf1)

Is there any workaround?
For small scall data, COLLECT() is 10 times faster than StrAgg().
 


Followup   December 28, 2005 - 6pm Central time zone:

the pga_aggregate_target won't come into play here at all, that is used for sort/hash/bitmap 
workareas, it cannot control the overall heap

and that is what you ran out of here.  

have you monitored the pga memory usage of the process?  what is it using

what is to_string?

you can skip the cast and your own type (which is likely adding overhead) if you want. 

3 stars COLLECT( ), StrAgg( ) and PGA limit   December 30, 2005 - 3pm Central time zone
Reviewer: Charlie Zhu from Victoria, BC Canada
The StringAgg is OK, no err for 100M rows table.
After I compiled StringAgg PL/SQL Code for Native Execution,
a little (0.5%) performance improved.

As you mentioned for performance tuning,
the best way to do it, is not to do it at all.

I'll create a staging table and maintain the data in data loading process,
(less than 0.3% daily data changed for the Invertory table and it's child tables)
and direct get the prepared concatenated string data,
this step will save 50% (3 hours) time of the whole daily data extract process.


For COLLECT and PGA,

alter system set pga_aggregate_target=10M;

The PGA increased to 950M, the err pop up. I monitor the PGA by Quest Spot*Light
and
select 'MB_PGA: '||Round(value/1048576) MB_PGA from v$pgastat
where name = 'total PGA inuse';

select 'MB_Temp TBS: '||sum(Round(blocks/256)) MB from V$TEMPSEG_USAGE;


abelisting@lab> select listingsid, COLLECT( to_char(bsacode) ) AS bsacode
  from int_tmp_sa
 where rownum < 1000000
 group by listingsid;
ERROR:
ORA-29400: data cartridge error
ORA-04030: out of process memory when trying to allocate 65548 bytes (koh dur heap 
d,kokbCollMergebuf1)


alter system set pga_aggregate_target=500M;

Exectue the SQL again, no err this time.

When PGA used increased to 400M, start returning data, 
the PGA used keep increasing to 500M, 600M, 700M...
then I cancelled the execution in SQL*Plus.

But the Min() will not ask for more PGA, about 10M it used.

select listingsid, Min( to_char(bsacode) ) AS bsacode
  from int_tmp_sa
 where rownum < 2000000
 group by listingsid;
.

If someday in the future, the COLLECT() can compete with Min() and parallel run,
I'll trun back to COLLECT.

set arraysize 2000
86078  rows processed

3.43 Seconds
SELECT listingsid, COLLECT(c.bsacode)
FROM scott.lstrsrch c group by listingsid;

0.27 Seconds
SELECT listingsid, Min(c.bsacode)
FROM scott.lstrsrch c group by listingsid;

Happy new year! half work today and going home.
 


3 stars   January 3, 2006 - 3pm Central time zone
Reviewer: A reader 
<code>hi tom

I want to extract firstname , middlename , last name , suffix from column oldname of a table and insert into another table.


i have written script like
--------------------------------------------------------

declare
      ls_old varchar2(36);
      ls_new varchar2(39);
      ls_middle varchar2(30);
      ls_first varchar2(60);
      ls_last varchar2(60);
      ls_suffix varchar2(99);
      ls_instby varchar2(60);
      i number (9);
     
      ll_len number(9);
      ll_pos1 number (9);
      ll_pos2 number (9); 
      ll_pos3 number (9);
      ll_pos4 number (9);
      ll_pos5 number (9); 
      ll_pos number (9);
      ld_instdt date;
     
  BEGIN
 
 
 
    for cur_indiv in (select individualid, ssn from lwgnindividual
        )
    loop
        for cur_indiv_namchg in (select RECKEY, SSNUM, OLDNAME, NEWNAME, INSERTBY, INSERTDT
                        from tlog_ChangeIndivName  where ssnum = cur_indiv.ssn
                      order by nvl(to_date(substr(insertdt,1,11),'yyyy/mon/dd'), to_date('19010101','yyyymmdd')) desc
        )
      loop
 
          -- first run following on source data
            --update tlog_changeindivname set oldname=trim(oldname);
            --update tlog_changeindivname set oldname=replace(oldname,' ',' ');
            --update tlog_changeindivname set oldname=replace(oldname,' ',' ');
            --update tlog_changeindivname set oldname=replace(oldname,' ',' ');
            --update tlog_changeindivname set oldname=replace(oldname,' ',' ');
         
         
          select nvl(to_date(substr(cur_indiv_namchg.insertdt,1,11),'yyyy/mon/dd'), to_date('19010101','yyyymmdd'))
            into ld_instdt from dual;
          --select cur_indiv_namchg.insertby into ls_instby from dual;
   
          select replace(cur_indiv_namchg.oldname,' ',' ') into ls_old from dual;
         
          ls_suffix := null;
         
         
         
          if ls_old like '% AIC' then  ls_suffix := 'AIC';  end if;
          if ls_old like '% CLU' then  ls_suffix := 'CLU';  end if;
          if ls_old like '% ESQ' then  ls_suffix := 'ESQ';  end if;
          if ls_old like '% GUY' then  ls_suffix := 'GUY';  end if;
          if ls_old like '% H' then  ls_suffix := 'H';  end if;
          if ls_old like '% IV' then  ls_suffix := 'IV';  end if;
          if ls_old like '% J' then  ls_suffix := 'J';  end if;
          if ls_old like '% JR' then  ls_suffix := 'JR';  end if;
          if ls_old like '% JR.' then  ls_suffix := 'JR.';  end if;
          if ls_old like '% M.' then  ls_suffix := 'M.';  end if;
          if ls_old like '% RI' then  ls_suffix := 'RI';  end if;
          if ls_old like '% SR' then  ls_suffix := 'SR';  end if;
          if ls_old like '% SR.' then  ls_suffix := 'SR.';  end if;
          if ls_old like '% V' then  ls_suffix := 'V';  end if;
          if ls_old like '% VII' then  ls_suffix := 'VII';  end if;
          if ls_old like '% ST' then  ls_suffix := 'ST';  end if;
          if ls_old like '% ST.' then  ls_suffix := 'ST.';  end if;
         
          if length(ls_suffix) > 0 then
              ls_old := rtrim(ls_old, ' ' || ls_suffix);
          end if;
         
        /*  ll_pos1:=instr(ls_old,' ',1,1);
          ll_pos2:=instr(ls_old,' ',1,2);
          ll_pos3:=instr(ls_old,' ',1,3);
          ll_pos4:=instr(ls_old,' ',1,4);
          ll_pos5:=instr(ls_old,' ',1,5);
       
       
 
            if ll_pos5 > 0 then
              ls_first:= substr(ls_old,1,ll_pos1-1);
            ls_last:= substr(ls_old,ll_pos2+1,ll_pos4-ll_pos2-1);
            ls_middle:= substr(ls_old,ll_pos1+1);
          elsif ll_pos4 > 0 then
              ls_first:= substr(ls_old,1,ll_pos1-1);
            ls_last:= substr(ls_old,ll_pos2+1,ll_pos3-ll_pos2-1);
            ls_middle:= substr(ls_old,ll_pos3+1);
          elsif ll_pos3 > 0 then
            ls_first:= substr(ls_old,1,ll_pos1-1);
            ls_last:= substr(ls_old,ll_pos2+1,ll_pos3-ll_pos2-1);
            ls_middle:= substr(ls_old,ll_pos3+1);
          elsif ll_pos2 > 0 then
              ls_first:= substr(ls_old,1,ll_pos1-1);
            ls_last:= substr(ls_old,ll_pos1+1,ll_pos2-ll_pos1-1);
            ls_middle:= substr(ls_old,ll_pos2+1);
          elsif ll_pos1 > 0 then
              ls_first:= substr(ls_old,1,ll_pos1-1);
            ls_middle:= '';
            ls_last:= substr(ls_old,ll_pos1+1);
          else
            ls_first:= ls_old;
            ls_middle:= '';
            ls_first:= '';
          end if;
 
        */
        ll_len:=length(ls_old);
        ll_pos:=instr(ls_old,' ',1,1);
          ls_first:=substr(ls_old,1,ll_pos);
         
          ll_pos1:=instr(ls_old,' ',-1,2);
          ls_last:=substr(ls_old,ll_pos1,ll_len);
         
          IF INSTR ( LS_LAST,' ',2)=0 THEN
          LS_MIDDLE:=SUBSTR(LS_LAST,1,2);
          LS_LAST:=LTRIM(LS_middle||LS_last);
        end if;
       
          ll_pos2:=(ll_len-ll_pos)-(ll_len-ll_pos1);
          ls_middle:=substr(ls_old,ll_pos,ll_pos2);
          if length(ls_middle)>2 then
          ls_first:=ls_first||ls_middle;
          ls_middle:=NULL;
          end if;
 
 
  --dbms_output.put_line(ls_old||'--'||ls_first||'--'||ls_middle||'--'||ls_last);
 
 
          insert into lwgnindividualhistory_t
                (historyid,          individualid,          ssn,                lastname,
                firstname,            middlename,            TITLE,                SUFFIX,
                DOB,              GENDER,            USERID,                PASSWORD,
                FPRCNO,          TAXPAYERID,          PHONETICCODE,          CRDNUMBER,
                AFFILIATION,        ISCITIZEN,            TRADENAME,            BUSINESSNAME,
                BCARDCOUNT,          VETDISABLEDFLAG,        FLAG,                performeddate,           
                performedby,        indexno, oldnamename)
               
          select sq_lwgnindividualhistory.nextval, individualid,    ssn,                ls_last,
                ls_first,            ls_middle,              title,                  ls_suffix,   
                dob,              gender,              USERID,                  PASSWORD,
                FPRCNO,            TAXPAYERID,          PHONETICCODE,          CRDNUMBER,
                  AFFILIATION,          ISCITIZEN,            TRADENAME,              BUSINESSNAME,
                BCARDCOUNT,          VETDISABLEDFLAG,      FLAG,                ld_instdt,           
                ls_instby,            indexno,ls_old
                from lwgnindividual
                where individualid=cur_indiv.individualid
                and ssn=cu

3 stars Help   March 27, 2006 - 9pm Central time zone
Reviewer: A reader 
Hi Tom,
I want to split a sentence with each word being printed in
each line.
How to give the exit condition with in the 
LOOP
...
end loop;
Please do reply. 


Followup   March 28, 2006 - 7am Central time zone:

just type:

exit;


or 

exit when (condition);



but it is unlikely you want to use "exit" when a better way exists:

ops$tkyte@ORA9IR2> declare
  2          l_string varchar2(4000) := 'how   now brown      cow';
  3  begin
  4          while (l_string is NOT null)
  5          loop
  6                  dbms_output.put_line
                     ( substr( l_string, 1, instr(l_string||' ',' ') ) );
  7                  l_string := substr( l_string, instr(l_string||' ',' ')+1 );
  8          end loop;
  9  end;
 10  /
how
now
brown
cow
 
PL/SQL procedure successfully completed.

 

3 stars ok   March 28, 2006 - 7am Central time zone
Reviewer: A reader 
Hi Tom,
Excellent and Thanks.
Previously you used a 
LOOP
..
END LOOP;

with ' ' appended to the actual string passed.

condition checking was in the loop itself and no while was
there.

That was nice to see and I don't remember where it stays
in this huge log.
Any way thanks for your time.
Bye! 


5 stars   April 20, 2006 - 1pm Central time zone
Reviewer: A reader 


2 stars   April 21, 2006 - 1am Central time zone
Reviewer: stupid 
-- 1
Insert into test (ID, STATUS, GRP) Values (1, 'B', 'x');
Insert into test (ID, STATUS, GRP) Values (2, 'B', 'x');
Insert into test (ID, STATUS, GRP) Values (3, 'A', 'x');
Insert into test (ID, STATUS, GRP) Values (4, 'A', 'y');
COMMIT;

-- 2
select distinct z.grp,last_value (z.xconcat) over (partition by grp) zconcat
from  
(
  select x.grp ,sys_connect_by_path(status,'.') xconcat
  from 
  (select id,lag(id,1) over (partition by grp order by grp,id) parent_id, status, grp 
    from (select grp, status, min(id) id from test group by grp, status) a
  ) x
  connect by prior id=parent_id
  start with parent_id is null
) z

-- 3
GRP    ZCONCAT
----------------
x    .B.A
y    .A



 


5 stars A reader   May 25, 2006 - 5pm Central time zone
Reviewer: Azeem from USA
Hi,

It was the exact procedure what I was looking for.I spent around 3 days to solve this problem but 
using your solution I could do it in 2 min.

Thanks to you. 


5 stars Suppressing buffer Errors -- StringAggType   May 29, 2006 - 9am Central time zone
Reviewer: Anindya Mitra from India
I used stringagg for one implementation where the user wanted the function not to "break" with 
string buffer exception when the list is very long, but should some way indicate the list overflow 
in output.
I modified StringAggType to result in 'List,...' for a longer list that could not be accomodated; 
and also a space between two list entries.

"CREATE OR REPLACE
type body StringAggType
   is

   static function ODCIAggregateInitialize(sctx IN OUT nocopy StringAggType)
   return number
   is
   begin
       sctx := StringAggType( null );
       return ODCIConst.Success;
   end;

   member function ODCIAggregateIterate(self IN OUT nocopy StringAggType,
                                        value IN varchar2 )
   return number
   is
   begin   
     if length (self.theString || ', ' || value) <=4000 and 
substr(nvl(rpad(self.theString,4,'#'),'####'),-4) != ',...' then
       self.theString := self.theString || ', ' || value;
       return ODCIConst.Success;
     elsif substr(self.theString,-4) != ',...' then
       self.theString := substr(self.theString,1,3996) || ',...';
       return ODCIConst.Success;
     else
       return ODCIConst.Success;
     end if;
   end;

   member function ODCIAggregateTerminate(self IN StringAggType,
                                          returnValue OUT nocopy varchar2,
                                          flags IN number)
   return number
   is
   begin
       returnValue := rtrim( ltrim( self.theString, ', ' ), ', ' );
       return ODCIConst.Success;
   end;

   member function ODCIAggregateMerge(self IN OUT nocopy StringAggType,
                                      ctx2 IN StringAggType)
   return number
   is
   begin
       self.theString := self.theString || ', ' || ctx2.theString;
       return ODCIConst.Success;
   end;

   end;
   /"

Also the 10g compiler issued a few warnings (suggestions basically) to use "nocopy" which I used in 
type and type body after that.

1) Tom, is this fine to use nocopy here? Any caveats?
2) My sqlplus did not show any warning. I only got to know when I used Oracle Sql Developer to 
compile the same.
Is there any simple sqlplus command for warnings just like "show errors'? Also sqlplus does that 
job automatically after any compilation call. Is there any similar facility available for warnings 
in sqlplus?

Thanks 


Followup   May 30, 2006 - 8am Central time zone:

1) probably is OK, do you know what it does, what it implies?  (those are the caveats, if you 
answer "no", that is the caveat - you don't understand what the implications are :)  - search for 
nocopy on this site, we've talked about it before.

2) 
http://download-east.oracle.com/docs/cd/B19306_01/appdev.102/b14258/d_warn.htm#sthref9481
 

5 stars nocopy in aggregate function   May 31, 2006 - 9am Central time zone
Reviewer: Anindya from India
Thanks for your reply Tom.
I could set the warning on for sqlplus; though sqlplus still represents a warning as error. Also I 
found the documentation link for DBMS_WARNING gives "DBMS_WARNING.GET_WARNING_SETTING_NUM (
   warning_number      IN    NUMBER)
RETURN warning_value;" ... I did not find the declaration of the return type "warning_value" here.
Regarding "Nocopy" hint - I am not fully sure whether there is any anomaly impact for aggregate 
functions; which are definitely called from SQLs only and the input parameter is normally table 
column, not any pl/sql variable.
Assuming I have not done any exception handling within function code that suppresses any raised 
exception, is there really any risk? Do I corrupt any SQL return value? Only the statement fails I 
believe. 


Followup   May 31, 2006 - 10am Central time zone:

As long as you don't care that the OUT values of a procedure will be "messed up upon hitting an 
unhandled exception" you may use it.

You own the code - you make that call.  Seems pretty "safe" in this case since we would discard the 
result set upon hitting some error anyway it would seem. 

2 stars String Concatenation   June 22, 2006 - 10am Central time zone
Reviewer: Purav Shah from Texas
Can you do the same thing in Microsoft SQL server 2000.... 


Followup   June 22, 2006 - 2pm Central time zone:

that would be a good question for askbill@ms.com.... 

5 stars string   June 28, 2006 - 5pm Central time zone
Reviewer: mal 
Tom,

I just need file name without location then How can I do this?
select file_name   from dba_data_files;
e,g 
I just need daqdata_04.dbf
/export/u02/oradata/A/daqdata_04.dbf 


Followup   June 28, 2006 - 6pm Central time zone:

ops$tkyte@ORA10GR2> select substr( file_name, instr( file_name, '/', -1 )+1 ) from dba_data_files;

SUBSTR(FILE_NAME,INSTR(FILE_NAME,'/',-1)+1)
-------------------------------------------------------------------------------
users01.dbf
sysaux01.dbf
undotbs01.dbf
system01.dbf
 

5 stars You are the man...   June 28, 2006 - 7pm Central time zone
Reviewer: mal 


5 stars For windows   June 28, 2006 - 7pm Central time zone
Reviewer: Mahmood Lebbai from Bothell,WA USA
And for windows it is forward slah(/) to be replaced with backward slash(\)

The query...
select substr( file_name, instr( file_name, '\', -1 )+1 )
from dba_data_files

 


4 stars Nice use of COLLECT but can we prevent hidden type creation?   June 29, 2006 - 5pm Central time zone
Reviewer: Aaron Valdes from San Diego, Ca
Its seems to create a type for every table when the length gets bigger:


SQL> create or replace type ntt_varchar2 as table of varchar2(4000);
  2  /

Type created.

SQL> 
SQL> CREATE TABLE names (NAME VARCHAR2(40));

Table created.

SQL> CREATE TABLE names2 (NAME VARCHAR2(60));

Table created.

SQL> 
SQL> INSERT INTO names VALUES ('rick');

1 row created.

SQL> INSERT INTO names VALUES ('bob');

1 row created.

SQL> INSERT INTO names VALUES ('tom');

1 row created.

SQL> 
SQL> INSERT INTO names2 VALUES ('rick');

1 row created.

SQL> INSERT INTO names2 VALUES ('bob');

1 row created.

SQL> INSERT INTO names2 VALUES ('tom');

1 row created.

SQL> 
SQL> commit;

Commit complete.

SQL> 
SQL> SELECT object_name FROM user_objects WHERE object_type = 'TYPE';

OBJECT_NAME
--------------------------------------------------------------------------------
NTT_VARCHAR2

1 row selected.

SQL> 
SQL> SELECT CAST( COLLECT (NAME) AS ntt_varchar2  ) 
  2  FROM names;

CAST(COLLECT(NAME)ASNTT_VARCHAR2)
--------------------------------------------------------------------------------
NTT_VARCHAR2('rick', 'bob', 'tom')

SQL> 
SQL> SELECT object_name FROM user_objects WHERE object_type = 'TYPE';

OBJECT_NAME
--------------------------------------------------------------------------------
NTT_VARCHAR2
SYSTPF2Ofw9UPTkbgQCUK7ZV+YQ==

2 rows selected.

SQL> 
SQL> SELECT CAST( COLLECT (NAME) AS ntt_varchar2  ) 
  2  FROM names2;

CAST(COLLECT(NAME)ASNTT_VARCHAR2)
--------------------------------------------------------------------------------
NTT_VARCHAR2('rick', 'bob', 'tom')

SQL> 
SQL> SELECT object_name FROM user_objects WHERE object_type = 'TYPE';

OBJECT_NAME
--------------------------------------------------------------------------------
NTT_VARCHAR2
SYSTPF2Ofw9UPTkbgQCUK7ZV+YQ==
SYSTPF2Ofw9UUTkbgQCUK7ZV+YQ==

3 rows selected.

SQL> 


Kinda annoying since these objects show up in schema compares, etc.

thanks 


5 stars number of occurances   July 12, 2006 - 6pm Central time zone
Reviewer: A reader 
Hi Tom

You posted a query to find the number of occurances of a string

select str,       (length(str)-length(replace(upper(str),upper(:x),'')))/length(:x) 
cnt
from t;

Not sure how this works, may you give a little explanation?

 


Followup   July 12, 2006 - 6pm Central time zone:

a = length(str) = length of the original string
b = length(replace(upper(str),upper(:x),'')) = length of string WITHOUT :x in it
c = length(:x) = length of the :x string, the thing you want to count


so....

a-b = how many characters were removed from STR by the replace()

a-b
--- = how many times we removed :x from A
 c



eg:

str = 'hello mellow yellow'
:x = ll


  1  select length(:str),
  2         length(replace(upper(:str),upper(:x),'')),
  3         length(:x)
  4*   from dual
ops$tkyte@ORA10GR2> /

LENGTH(:STR) LENGTH(REPLACE(UPPER(:STR),UPPER(:X),'')) LENGTH(:X)
------------ ----------------------------------------- ----------
          19                                        13          2


So, str-replace() = 19-13 = 6.  We removed six characters by replacing 'll' with nothing.

6/2 = 3, we must have removed 3 occurences of 'll' from :str...



 

3 stars allowing only charcters   August 4, 2006 - 3pm Central time zone
Reviewer: ram from usa
is there a way we can allow only charcters while storing in table for example  first name and last 
name can contain the Alhpa charcters ( no numneric or special charcters) 


Thanks for the help 


Followup   August 4, 2006 - 4pm Central time zone:

ops$tkyte%ORA10GR2> create table t
  2  ( x varchar2(30)
  3      constraint alpha_check
  4      check ( replace(
  5                translate(
  6                    upper(x),' ABCDEFGHIJKLMNOPQRSTUVWXYZ',rpad('X',27,'X')),
  7                'X','') is null )
  8  )
  9  /

Table created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> insert into t values ( 'Tom Kyte' );

1 row created.

ops$tkyte%ORA10GR2> insert into t values ( 'tom_kyte' );
insert into t values ( 'tom_kyte' )
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.ALPHA_CHECK) violated



the translate turns all valid characters (space plus alphabet) into 'X'
the replace turns all 'X's into nothing
if the replaced, translated, upper case string is now NULL - it is OK.

 

5 stars ORA-600   August 9, 2006 - 1pm Central time zone
Reviewer: Dierk from Germany
<code>Hi Tom,

I read the article "SQL Build Custom Aggregate Functions" of the oracle magazine. Therefore I merge that with the concat_all_ot example. By using the object concat_expr with the MAP MEMBER FUNCTION I get an ORA-600. Can you help me?
DROP TABLE car_rental;
CREATE TABLE car_rental (
  car_id  NUMBER,
  cust_id    NUMBER,
  rental_state VARCHAR2(2),
  rental_out  TIMESTAMP,
  rental_in  TIMESTAMP
);
INSERT INTO car_rental VALUES (1, 101, 'MI', TIMESTAMP '2006-03-05 08:00:00.00', TIMESTAMP '2006-03-11 14:00:00.00');
INSERT INTO car_rental VALUES (1, 102, 'MI', TIMESTAMP '2006-03-12 08:34:24.00', TIMESTAMP '2006-03-17 19:23:19.00');
INSERT INTO car_rental VALUES (1, 103, 'MI', TIMESTAMP '2006-03-17 23:19:00.00', TIMESTAMP '2006-03-26 06:00:00.00');
INSERT INTO car_rental VALUES (1, 104, 'MI', TIMESTAMP '2006-03-28 09:45:15.00', TIMESTAMP '2006-03-30 15:27:13.00');
INSERT INTO car_rental VALUES (4, 105, 'MI', TIMESTAMP '2006-03-05 15:23:00.00', TIMESTAMP '2006-03-11 06:00:00.00');
INSERT INTO car_rental VALUES (4, 106, 'MI', TIMESTAMP '2006-03-12 17:34:24.00', TIMESTAMP '2006-03-17 18:23:19.00');
INSERT INTO car_rental VALUES (4, 107, 'MI', TIMESTAMP '2006-03-17 05:19:00.00', TIMESTAMP '2006-03-26 18:00:00.00');
INSERT INTO car_rental VALUES (4, 108, 'MI', TIMESTAMP '2006-03-28 12:45:15.00', TIMESTAMP '2006-03-30 03:27:13.00');
INSERT INTO car_rental VALUES (7, 109, 'MI', TIMESTAMP '2006-03-05 08:27:00.00', TIMESTAMP '2006-03-11 14:23:00.00');
INSERT INTO car_rental VALUES (7, 110, 'MI', TIMESTAMP '2006-03-12 08:19:24.00', TIMESTAMP '2006-03-17 19:56:19.00');
INSERT INTO car_rental VALUES (7, 111, 'MI', TIMESTAMP '2006-03-17 23:58:00.00', TIMESTAMP '2006-03-26 06:39:00.00');
INSERT INTO car_rental VALUES (7, 112, 'MI', TIMESTAMP '2006-03-28 09:02:15.00', TIMESTAMP '2006-03-30 15:01:13.00');
INSERT INTO car_rental VALUES (2, 113, 'WI', TIMESTAMP '2006-03-06 08:00:00.00', TIMESTAMP '2006-03-11 14:00:00.00');
INSERT INTO car_rental VALUES (2, 114, 'WI', TIMESTAMP '2006-03-13 08:34:24.00', TIMESTAMP '2006-03-17 19:23:19.00');
INSERT INTO car_rental VALUES (2, 115, 'WI', TIMESTAMP '2006-03-18 23:19:00.00', TIMESTAMP '2006-03-26 06:00:00.00');
INSERT INTO car_rental VALUES (2, 116, 'WI', TIMESTAMP '2006-03-29 09:45:15.00', TIMESTAMP '2006-03-30 15:27:13.00');
INSERT INTO car_rental VALUES (5, 117, 'WI', TIMESTAMP '2006-03-06 15:23:00.00', TIMESTAMP '2006-03-11 06:00:00.00');
INSERT INTO car_rental VALUES (5, 118, 'WI', TIMESTAMP '2006-03-13 17:34:24.00', TIMESTAMP '2006-03-17 18:23:19.00');
INSERT INTO car_rental VALUES (5, 119, 'WI', TIMESTAMP '2006-03-18 05:19:00.00', TIMESTAMP '2006-03-26 18:00:00.00');
INSERT INTO car_rental VALUES (5, 120, 'WI', TIMESTAMP '2006-03-29 12:45:15.00', TIMESTAMP '2006-03-30 03:27:13.00');
INSERT INTO car_rental VALUES (8, 121, 'WI', TIMESTAMP '2006-03-06 08:27:00.00', TIMESTAMP '2006-03-11 14:23:00.00');
INSERT INTO car_rental VALUES (8, 122, 'WI', TIMESTAMP '2006-03-13 08:19:24.00', TIMESTAMP '2006-03-17 19:56:19.00');
INSERT INTO car_rental VALUES (8, 123, 'WI', TIMESTAMP '2006-03-18 23:58:00.00', TIMESTAMP '2006-03-26 06:39:00.00');
INSERT INTO car_rental VALUES (8, 124, 'WI', TIMESTAMP '2006-03-29 09:02:15.00', TIMESTAMP '2006-03-30 15:01:13.00');
INSERT INTO car_rental VALUES (3, 125, 'MN', TIMESTAMP '2006-03-06 15:23:00.00', TIMESTAMP '2006-03-10 06:00:00.00');
INSERT INTO car_rental VALUES (3, 126, 'MN', TIMESTAMP '2006-03-13 17:34:24.00', TIMESTAMP '2006-03-16 18:23:19.00');
INSERT INTO car_rental VALUES (3, 127, 'MN', TIMESTAMP '2006-03-18 05:19:00.00', TIMESTAMP '2006-03-25 18:00:00.00');
INSERT INTO car_rental VALUES (3, 128, 'MN', TIMESTAMP '2006-03-29 12:45:15.00', TIMESTAMP '2006-03-29 03:27:13.00');
INSERT INTO car_rental VALUES (6, 129, 'MN', TIMESTAMP '2006-03-06 08:27:00.00', TIMESTAMP '2006-03-10 14:23:00.00');
INSERT INTO car_rental VALUES (6, 130, 'MN', TIMESTAMP '2006-03-13 08:19:24.00', TIMESTAMP '2006-03-16 19:56:19.00');
INSERT INTO car_rental VALUES (6, 131, 'MN', TIMESTAMP '2006-03-18 23:58:00.00', TIMESTAMP '2006-03-25 06:39:00.00');
INSERT INTO car_rental VALUES (6, 132, 'MN', TIMESTAMP '2006-03-29 09:02:15.00', TIMESTAMP '2006-03-29 15:01:13.00');
COMMIT;
CREATE OR REPLACE TYPE concat_expr AS OBJECT
(
str VARCHAR2 (4000)
, del VARCHAR2 (4000)

, MAP MEMBER FUNCTION mapping_function RETURN VARCHAR2

);
/

CREATE OR REPLACE TYPE BODY concat_expr
AS
MAP MEMBER FUNCTION mapping_function
RETURN VARCHAR2
IS
BEGIN
  RETURN str || del;
 
END mapping_function;

END;
/

-- ########################################################################## --

CREATE OR REPLACE TYPE concat_all_ot AS OBJECT
(
str VARCHAR2 (4000)
, del VARCHAR2 (4000)

, STATIC FUNCTION odciaggregateinitialize( sctx IN OUT concat_all_ot)
RETURN NUMBER

, MEMBER FUNCTION odciaggregateiterate( SELF IN OUT concat_all_ot
                        , ctx IN  concat_expr
                        )
RETURN NUMBER

, MEMBER FUNCTION odciaggregateterminate( SELF    IN concat_all_ot
                          , returnvalue OUT VARCHAR2
                          , flags    IN NUMBER
                          )
RETURN NUMBER

, MEMBER FUNCTION odciaggregatemerge( SELF IN OUT concat_all_ot
                        , ctx2 in  concat_all_ot
                        )
RETURN NUMBER

, MEMBER FUNCTION ODCIAggregateDelete( self IN OUT concat_all_ot
                        , val  IN  concat_expr
                        )
RETURN NUMBER

);
/

-- ########################################################################## --

CREATE OR REPLACE TYPE BODY concat_all_ot
AS

--------------------------------------------------------------------------------

STATIC FUNCTION odciaggregateinitialize( sctx IN OUT concat_all_ot)
RETURN NUMBER
IS
BEGIN
    if sctx is null
    then
    sctx := concat_all_ot( NULL, NULL);
   
  else
    sctx.str := null;
    sctx.del := null;
   
  end if;
 
  RETURN odciconst.success;
 
END odciaggregateinitialize;

--------------------------------------------------------------------------------

MEMBER FUNCTION odciaggregateiterate( SELF IN OUT concat_all_ot
                        , ctx IN  concat_expr
                        )
RETURN NUMBER
IS
BEGIN
  IF SELF.str IS NOT NULL
  THEN
    SELF.str := SELF.str || ctx.del;
   
  END IF;
 
  SELF.str := SELF.str || ctx.str;
 
  RETURN odciconst.success;
 
END odciaggregateiterate;

--------------------------------------------------------------------------------

MEMBER FUNCTION odciaggregateterminate( SELF    IN  concat_all_ot
                          , returnvalue OUT VARCHAR2
                          , flags    IN  NUMBER
                          )
RETURN NUMBER
IS
BEGIN
  returnvalue := SELF.str;
 
  RETURN odciconst.success;
 
END odciaggregateterminate;

--------------------------------------------------------------------------------

MEMBER FUNCTION odciaggregatemerge( SELF IN OUT concat_all_ot
                        , ctx2 IN  concat_all_ot
                        )
RETURN NUMBER
IS
BEGIN
  IF SELF.str IS NOT NULL
  THEN
    SELF.str := SELF.str || SELF.del;
   
  END IF;
 
  SELF.str := SELF.str || ctx2.str;
 
  RETURN odciconst.success;
 
END odciaggregatemerge;

--------------------------------------------------------------------------------

MEMBER FUNCTION ODCIAggregateDelete( self IN OUT concat_all_ot
                        , val  IN  concat_expr
                        )
RETURN NUMBER
IS
    v_pos_val_suf number;
    v_pos_val_pref number;
    v_pos_del_from

Followup   August 9, 2006 - 4pm Central time zone:

ora-600 = please utilize support.  You have metalink access, that is the place for this. 

5 stars Getting ORA-03113 error: Am I missing something ?   August 11, 2006 - 4pm Central time zone
Reviewer: Santosh Kompella from Philly, NJ USA
SQL*Plus: Release 10.2.0.1.0 - Production on Fri Aug 11 16:01:14 2006

Copyright (c) 1982, 2005, Oracle.  All rights reserved.


Connected to:
Oracle9i Enterprise Edition Release 9.2.0.6.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.6.0 - Production

SQL> create or replace type string_agg_type as object
  2  (
  3       total varchar2(4000),
  4    
  5       static function
  6            ODCIAggregateInitialize(sctx IN OUT string_agg_type )
  7             return number,
  8     
  9        member function
 10             ODCIAggregateIterate(self IN OUT string_agg_type ,
 11                                  value IN varchar2 )
 12             return number,
 13     
 14        member function
 15             ODCIAggregateMerge(self IN OUT string_agg_type,
 16                                ctx2 IN string_agg_type)
 17             return number,
 18     
 19        member function
 20             ODCIAggregateTerminate(self IN string_agg_type,
 21                                    returnValue OUT  varchar2,
 22                                    flags IN number)
 23             return number
 24   );
 25  /
create or replace type string_agg_type as object
*
ERROR at line 1:
ORA-03113: end-of-file on communication channel 


Followup   August 11, 2006 - 6pm Central time zone:

please contact support. 

3 stars check constraint   August 23, 2006 - 1pm Central time zone
Reviewer: sat 
How do we handle check constraint for the phone number

 Say  format should be XXX-XXXX otherwise it should raise an error.

sorry for the posting here 


Followup   August 27, 2006 - 5pm Central time zone:

one way is:

check ( substr( col, 4,1 ) = '-' and translate(col,'0123456789','----------') = '--------' )

ops$tkyte%ORA10GR2> create table t
  2  (
  3  col varchar2(8)
  4  constraint check_phone check
  5     ( substr( col, 4,1 ) = '-'
  6       and
  7       translate(col,'0123456789','----------') = '--------'
  8     )
  9  )
 10  /

Table created.

ops$tkyte%ORA10GR2> insert into t values ( '123-4567' );

1 row created.

ops$tkyte%ORA10GR2> insert into t values ( '12304567' );
insert into t values ( '12304567' )
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.CHECK_PHONE) violated


ops$tkyte%ORA10GR2> insert into t values ( '123-456' );
insert into t values ( '123-456' )
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.CHECK_PHONE) violated


ops$tkyte%ORA10GR2> insert into t values ( 'x23-4567' );
insert into t values ( 'x23-4567' )
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.CHECK_PHONE) violated


ops$tkyte%ORA10GR2> insert into t values ( ' 23-4567' );
insert into t values ( ' 23-4567' )
*
ERROR at line 1:
ORA-02290: check constraint (OPS$TKYTE.CHECK_PHONE) violated

 

5 stars problem in 10g   January 8, 2007 - 3pm Central time zone
Reviewer: Rahul from columbus
nor i can use sys_connect_by_path
or stragg in the function

CREATE OR REPLACE FUNCTION APPS.PEL_NUM_CHARS(INSTRING VARCHAR2)
RETURN VARCHAR2
IS
l_output VARCHAR2(100);
BEGIN
select apps.stragg(ch)
--LTRIM(MAX(SYS_CONNECT_BY_PATH (ch , ',')),',')
    into l_output
    FROM (
          SELECT SUBSTR(IN_STR, LEVEL , 1) ch , ROWNUM rn
          FROM (SELECT INSTRING AS IN_STR FROM dual)
        CONNECT BY LEVEL <= LENGTH(IN_STR)
      )
--    START WITH rn = 1
--  CONNECT BY PRIOR rn = rn -1
    ;
    return l_output;
   
    exception when others then
    return null;
END;
/

if the string is '11011' i want the output to be '1,1,0,1,1'

3 stars Multiple columns   February 22, 2007 - 3pm Central time zone
Reviewer: Tom Fox from Cincinnati, OH
Tom,

Say I have a table:

ID  TYPE  NAME
1   AA1   Bob
1   AA2   Henry
1   AA3   Jack
1   AA4   Paul
2   AA1   Yo Yo Ma
2   AA5   Pete
2   AA9   Derek


I need to group by ID, and aggregate the type and name columns (unknown number per ID) into the following format:

1|AA1|Bob|AA2|Henry|AA3|Jack|AA4|Paul
2|AA1|Yo Yo Ma|AA5|Pete|AA9|Derek

I've been up and down and all around this answer, but not getting what I need. I know I could group by several other columns to get what I need then run Perl or sed and chop it up, but I'm trying to keep this contained to the database (while picking up some education). :)

Do you know a slick way to do this?

Followup   February 22, 2007 - 7pm Central time zone:

search this site for stragg

then you can

select id, stragg( type || '|' || name ) from t group by id
5 stars   February 22, 2007 - 7pm Central time zone
Reviewer: Tom Fox from Cincinnati, OH
Thanks! I saw stragg, but obviously I overlooked something. Have a great evening.

4 stars Concatenate Varchar2 in a single SQL without using Functions   February 25, 2007 - 11pm Central time zone
Reviewer: Jaidev Thakur from Singapore
SQL> select * from temp_temp

KEY            ERRORS
--------- ----------
S01992            631
S01992            632
S01992            634
S01994            701
S01994            705



select key,max(substr(sys_connect_by_path(errors,','),2)) errors
from   (select key, errors,  row_number() over ( partition by key order by
errors ) curr,
row_number() over ( partition by key order by errors ) - 1 prev  from
temp_temp )
start  with curr = 1
connect by prior curr = prev
and prior key= key
group by key
/

 

KEY    ERRORS
-----  -------

S01992 631,632,634
S01994 701,705



2 stars STRAGG - Reverse   July 9, 2007 - 3pm Central time zone
Reviewer: patty from NC
DEPTNO NAMES
---------- -------------------------------------------------
    10 CLARK.KING.MILLER
    20 SMITH.JONES.SCOTT.ADAMS.FORD
    30 ALLEN.WARD.MARTIN.BLAKE.TURNER.JAMES

How to create this result:

10 CLARK
10 KING
10 MILLER
20 SMITH
20 JONES
20 SCOTT
20 ADAMS
20 FORD
30 ALLEN
30 WARD
.
.
.



Followup   July 9, 2007 - 8pm Central time zone:

ops$tkyte%ORA10GR2> create table your_data
  2  as
  3  select deptno, stragg( ename ) names
  4    from scott.emp
  5  group by deptno
  6  /

Table created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> with
  2  data
  3  as
  4  (
  5  select level r
  6    from dual
  7  connect by level <= 4000
  8  )
  9  select deptno, r, names,
 10         substr( names,
 11                 instr( names, ',', 1, r )+1,
 12                 instr( names, ',', 1, r+1 ) - instr( names, ',', 1, r ) - 1
 13                 ) nm
 14    from (select deptno, ','||names||',' names,
 15                 length(names)-length(replace(names,',',''))+1 elements
 16            from your_data
 17         ) your_data, data
 18   where data.r <= your_data.elements
 19   order by deptno, r
 20  /

    DEPTNO          R NAMES                                    NM
---------- ---------- ---------------------------------------- 
----------------------------------------
        10          1 ,clark,king,miller,                      clark
        10          2 ,clark,king,miller,                      king
        10          3 ,clark,king,miller,                      miller
        20          1 ,smith,ford,adams,scott,jones,           smith
        20          2 ,smith,ford,adams,scott,jones,           ford
        20          3 ,smith,ford,adams,scott,jones,           adams
        20          4 ,smith,ford,adams,scott,jones,           scott
        20          5 ,smith,ford,adams,scott,jones,           jones
        30          1 ,allen,blake,martin,turner,james,ward,   allen
        30          2 ,allen,blake,martin,turner,james,ward,   blake
        30          3 ,allen,blake,martin,turner,james,ward,   martin
        30          4 ,allen,blake,martin,turner,james,ward,   turner
        30          5 ,allen,blake,martin,turner,james,ward,   james
        30          6 ,allen,blake,martin,turner,james,ward,   ward

14 rows selected.


5 stars RE: STRAGG - Reverse   July 10, 2007 - 10am Central time zone
Reviewer: Frank Zhou from Braintree, MA

Patty,

    Here are two more alternative SQL solutions for " Reverse the STRAGG"

Frank


http://www.jlcomp.demon.co.uk/faq/Split_Strings.html



SQL> set serveroutput on
SQL> Create table ts as
2  select 10 as id , 'CLARK.KING.MILLER' as str from dual
3  union all
4  select 20 as id , 'SMITH.JONES.SCOTT.ADAMS.FORD' as str from dual
5  union all
6  select 30 as id , 'ALLEN.WARD.MARTIN.BLAKE.TURNER.JAMES ' as str from dual
7 ;

Table created.

SQL> select * from ts;

    ID STR                                           
---------- -------------------------------------                   
    10 CLARK.KING.MILLER                                 
    20 SMITH.JONES.SCOTT.ADAMS.FORD                         
    30 ALLEN.WARD.MARTIN.BLAKE.TURNER.JAMES                     


SQL> COLUMN name FORMAT A12

SQL> select id as deptno, name_str.extract('/X/text()').getstringVal() name
2 from ts, table(xmlSequence(
3 extract(XMLType('<DOC><X>'||replace(str,'.','</X><X>')||'</X></DOC>'),'/DOC/X'))) name_str;

  DEPTNO NAME                                         
---------- ------------                                     
    10 CLARK                                         
    10 KING                                         
    10 MILLER                                         
    20 SMITH                                         
    20 JONES                                         
    20 SCOTT                                         
    20 ADAMS                                         
    20 FORD                                         
    30 ALLEN                                         
    30 WARD                                         
    30 MARTIN                                         

  DEPTNO NAME                                         
---------- ------------                                     
    30 BLAKE                                         
    30 TURNER                                         
    30 JAMES                                         

14 rows selected.

SQL> spool off;


5 stars Reverse STRAGG   July 10, 2007 - 11am Central time zone
Reviewer: patty from NC
Much appreciated! This works beautifully. My data has an element of complexity in that it looks like this:

ID  DATA (3 fields separated by columns, and pipes to separate each group of fields)
--  -----
10  3,,|5,x,|
20  5,,|
30  7,a,c|2,x,|10,,|
.
.

I'll work on splitting out the other two fields as well, but you've enabled me to get such a head start with this code you provided.

Many thanks.

5 stars Reverse Stragg   July 10, 2007 - 11am Central time zone
Reviewer: Patty from NC
Thank you to both of you! 

5 stars RE: STRAGG - Reverse   July 10, 2007 - 12pm Central time zone
Reviewer: Frank Zhou from Braintree, MA
Hi Patty,

    How about the following query?

Is that answered your real question?

Frank

SQL> select * from ts2;

    ID STR
---------- ----------------
    10 3,,|5,x,|
    20 5,,|
    30 7,a,c|2,x,|10,,|

SQL> select id as deptno, name_str.extract('/X/text()').getstringVal() name
2 from
3 (select id, replace(rtrim(str, '|'), '|', ',') str from ts2),
4  table(xmlSequence(
5 extract(XMLType('<DOC><X>'||replace(str,',','</X><X>')||'</X></DOC>'),'/DOC/X'))) name_str
6 ;

  DEPTNO NAME
---------- ------------
    10 3
    10
    10
    10 5
    10 x
    10
    20 5
    20
    20
    30 7
    30 a
    30 c
    30 2
    30 x
    30
    30 10
    30
    30

18 rows selected.

SQL>

2 stars reverse stragg   July 10, 2007 - 12pm Central time zone
Reviewer: Patty from NC
I should make myself clear.
Input:
ID STR
-------------------
10 3,,|5,x,|
20 5,,|
30 7,a,c|2,x,|10,,|

The output will end up like this:

ID STR CD1 CD2
-- --- --- ---
10 3
10 5  x
20 5
30 7  a  c
30 2  x
30 10

4 stars RE: STRAGG - Reverse   July 10, 2007 - 2pm Central time zone
Reviewer: Frank Zhou from Braintree, MA



SQL> select deptno, extractvalue(data, '//Z[1]') col_1,
2      extractvalue(data, '//Z[2]') col_2, extractvalue(data, '//Z[3]') col_3
3 from
4 (select deptno, XMLType('<DOC1><Z>'|| replace(name,',', '</Z><Z>')||'</Z></DOC1>') data
5  from
6    (select id as deptno, name_str.extract('/X/text()').getstringVal() name
7      from
8        (select id, rtrim(str, '|') str from ts2),
9              table(xmlSequence(
10          extract(XMLType('<DOC><X>'|| replace(str,'|', '</X><X>')||'</X></DOC>'),'/DOC/X'))
) name_str
11      )
12 );

  DEPTNO COL COL COL
---------- --- --- ---
    10 3
    10 5  x
    20 5
    30 7  a  c
    30 2  x
    30 10

6 rows selected.

5 stars Reverse STRAGG   July 10, 2007 - 4pm Central time zone
Reviewer: Patty from NC
Thank you, Frank.

I don't understand the code completely, but I was able to modify it to use my table and column names and got the results I need. Now - I need to attempt to figure out how it works!

Many thanks.

5 stars String concatenation   October 12, 2007 - 1am Central time zone
Reviewer: Tarun Sehgal from INDIA
Hi Tom,

I try to use your user defined aggregate function but while trying to use them in my query it gives 
me "end of file communication channel" error.

Plz suggest


Followup   October 14, 2007 - 9pm Central time zone:

if you can provide a test case from start to finish - create table, insert into table, run stragg, I'll take a look

short of that, please utilize support.
5 stars MODEL   November 21, 2007 - 7am Central time zone
Reviewer: Laurent Schneider from Switzerland
I am aggreing with your remark about WM_CONCAT. I thought it was fun to post it on otn forums, but I should have specified more explicitely that it is undocumented. My bad.

Ok, I tried something else with model
select deptno, enames       
from emp   
model    
return updated rows     
partition by (deptno)         
dimension by (
   row_number() over (
      partition by deptno 
      order by ename) 
   rn)
measures (
   cast(ename as varchar2(4000)) enames)         
rules iterate (1000) until presentv(enames[ITERATION_NUMBER+3],1,2)=2 (
   enames[1]  = enames[1] || presentv(enames[2],',','') || enames[ITERATION_NUMBER+2]          
);
DEPTNO ENAMES
------ ------------------------------------
    20 ADAMS,FORD,JONES,SCOTT,SMITH
    10 CLARK,KING,MILLER
    30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

it is then quite straightforward to change the order in the ORDER BY clause.


5 stars wm_concat   November 21, 2007 - 12pm Central time zone
Reviewer: Barbara Boehmer from Riverside County, CA USA
Hi Tom,

I saw wm_concat on a couple of forums (otn and orafaq), apparently a new, undocumented function as 
shown below.  Since it is undocumented, is it safe to use?

Regards,
Barbara

SCOTT@orcl_11g> select * from v$version
  2  /

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 - Production
PL/SQL Release 11.1.0.6.0 - Production
CORE    11.1.0.6.0    Production
TNS for 32-bit Windows: Version 11.1.0.6.0 - Production
NLSRTL Version 11.1.0.6.0 - Production

SCOTT@orcl_11g> describe wm_concat
FUNCTION wm_concat RETURNS VARCHAR2
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 P1                             VARCHAR2                IN

SCOTT@orcl_11g> column enames format a45 word_wrapped
SCOTT@orcl_11g> select deptno, wm_concat (ename) as enames from emp group by deptno
  2  /

    DEPTNO ENAMES
---------- ---------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,FORD,ADAMS,SCOTT,JONES
        30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

SCOTT@orcl_11g> 


   


Followup   November 20, 2007 - 2pm Central time zone:

my suggestion is going to be consistent....

Never use undocumented stuff, it is subject to change, removal, broken-ness without recourse.

either

a) use stragg
b) write your own
c) use the connect by trick.
4 stars stragg has sorting built in   January 2, 2008 - 3pm Central time zone
Reviewer: Sean D. Stuber from Columbus, OH USA
I've seen several references to stragg not being able to sort.  But I've been using it for a long 
time to do just that by using it as an analytic instead of an simple aggregate.

Is this unreliable?


SELECT DISTINCT deptno,
                stragg(ename) OVER(PARTITION BY deptno ORDER BY ename DESC ROWS BETWEEN UNBOUNDED 
PRECEDING AND UNBOUNDED FOLLOWING)
           FROM emp
       ORDER BY deptno



Followup   January 2, 2008 - 3pm Central time zone:

that is using analytics and that has been covered first time about four or five years ago :)

http://asktom.oracle.com/pls/asktom/f?p=100:232:207529176231702::::P232_QUESTIONID:2196162600402#258
79699852042

http://asktom.oracle.com/pls/asktom/f?p=100:232:207529176231702::::P232_QUESTIONID:2196162600402#797
1282174613


Yes, that works, but isn't necessarily optimal - as it has to analytic - sort - then aggregate out the duplicates.

rather than aggregate+sort in one step.
5 stars Correcting above links   January 2, 2008 - 7pm Central time zone
Reviewer: Balaji from Stamford,CT
http://asktom.oracle.com/pls/asktom/f?p=100:11:207529176231702::::P11_QUESTION_ID:2196162600402#2587
9699852042

http://asktom.oracle.com/pls/asktom/f?p=100:11:207529176231702::::P11_QUESTIOID:2196162600402#797128
2174613



5 stars Correction of Link   January 2, 2008 - 7pm Central time zone
Reviewer: Balaji from Stamford,CT
http://asktom.oracle.com/pls/asktom/f?p=100:11:207529176231702::::P11_QUESTION_ID:2196162600402#2587
9699852042

http://asktom.oracle.com/pls/asktom/f?p=100:11:207529176231702::::P11_QUESTION_ID:2196162600402#7971
282174613



2 stars An extension of concatenation   March 29, 2008 - 1pm Central time zone
Reviewer: A reader 
Am having a value like "40+50+60" for one of the columns, can i make that as 150 (i.e. total of 
40,50 and 60) in sql itself.

For e.g

Table has

x  y
-  -------
A  40+50+60
B  239+34
C  12+22+45+11

I want the report to return as

A  150
B  273
C   90

Is it possible to get in sql itself, I can get that by creating a function which can accept "y" as 
parameter and can split em and sum it. Any better way?

Thank you
Mani


Followup   March 31, 2008 - 8am Central time zone:

ugh, that is not an extension of concatenation.

that is turning a database into a calculator.


You have to write a function
2 stars Summing concatenated numbers   March 30, 2008 - 9am Central time zone
Reviewer: A reader 
The follwing code was simpler i found to achive the total,

create or replace function calc_tot(x in varchar2) return number
as
str varchar2(4000):= 'Select '||trim(x)||' from dual';
temp number;

begin
execute immediate str into temp;
return temp;
exception
when others then 
return -999999999;
end calc_tot;
/

function calc_tot(x Compiled.

select calc_tot('+50+30+343') from dual;

CALC_TOT(+50+30+343)   
---------------------- 
423                    

1 rows selected

I know you will always give a simpler one, so waiting for that.

Thank you
Mani


Followup   March 31, 2008 - 9am Central time zone:

can you spell:

SQL INJECTION


google it, you are very much subject to it.

ouch:

when others then
return -999999999;
end calc_tot;
/

I'll never ever understand code like that... :( if you have an error, LET IT BE AN ERROR, don't hide it!

I can give you much more complex way using cast, multiset, lots of substrings - but I won't (you are doing this all wrong, this data should be evaluated AND THEN stored, not stored and evaluated, over and over and over and over and over again)

2 stars SQL Injection   April 1, 2008 - 12am Central time zone
Reviewer: A reader 
Its actually not my data, was just working on one of the questions in oracle forum 
http://forums.oracle.com/forums/message.jspa?messageID=2426622#2426622 . I understand the when 
others part mistake, could have simply raised it..just wanted to return some big -ve number to 
understand its an error. 

Thanks
mani


Followup   April 1, 2008 - 9am Central time zone:

to understand it is an error - LET IT BE AN ERROR FOR GOODNESS SAKE.

What is more understandable:

return code = ORA-12345 Error happened!!!!!

or

return code = 0, everything is OK.


and that forum posting seems to have, well, absolutely nothing to do with this?
4 stars ref cursor   May 23, 2008 - 11am Central time zone
Reviewer: A reader from poland
should it work in procedure like this:
CREATE OR REPLACE procedure SOWK.sp_str(out_cursor out sys_refcursor) is
begin
open out_cursor for select stragg(distinct object_name) from all_objects;
end;
/

for me it returns error ORA-30482 "DISTINCT option not allowed for this function" during 
compilation on 9i and 10g. Is it my error or "natural" behavior of that, while 
select stragg(distinct object_name) from all_objects
works nice.
Or maybe it's the same situation as the one in thread 
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1058232381458#11384357177468

regards


Followup   May 23, 2008 - 6pm Central time zone:

it would be the same thing as the link
2 stars   May 29, 2008 - 11am Central time zone
Reviewer: Karteek from India
Tom,

Is there any constraint on sys_connect_by_path() like functions that they should used alone, but not together with another analytic function?

Below sql is not fetching any value in "str", but when I remove max() function it does.Can you help me why sys_connect_by_path is not working here?

select rn, max(rn) over(order by rn) dummy, sys_connect_by_path(rn,',') str 
from
(
select rownum rn from dual  connect by rownum < 5
)
start with rn = 1
connect by 
rn = prior rn+1



Thanks!

Followup   May 29, 2008 - 1pm Central time zone:

that would be a bug...
5 stars String Concatenation & storing the data into VARCHAR2(4000)   June 27, 2008 - 1pm Central time zone
Reviewer: Hemal Deshmukh from Pune,India
Hello Tom,
I am having a PL/SQL nested having following type:-
-----
type char_table   is table of varchar2(2000) index by binary_integer;

tbl_char_data  char_table;
-----
tbl_char_data is having 3000 elements.
I want to concatenate the elements of this tbl_char_data as following 
tbl_char_data(1)||'|'||tbl_char_data(2)||'|'||tbl_char_data(3)...

and store it in a database table having a column size as VARCHAR2(4000).

Concatenated string comes to be more than 4000 characters.
Therefore I am not able to store it.
I am also not allowed to increase the column size or use any other data type.

Can you please advice how should we store this data.
Thanks and Best Regards
-Hemal 


Followup   June 27, 2008 - 3pm Central time zone:

magic?

seriously - look at what you wrote, look at what you ask...


you will either

a) do something entirely different (i would suggest - umm - DO NOT STORE A CONCATENATED STRING - i hate that)

b) change your list of "we cannot do this" in order to make it possible to do
5 stars String Concatenation & storing the data into VARCHAR2(4000)   June 28, 2008 - 4am Central time zone
Reviewer: Hemal Deshmukh from Pune,India
Hello Tom,
I am referring to the above thread "String Concatenation & storing the data into VARCHAR2(4000)" dated June 27, 2008 - 1pm US/Eastern

Client requires the concatenated string to be stored in VARCHAR2(4000) column.As mentioned in the above thread we are getting data through a PL/SQL table. We need to concatenate the elements and store the concatenated string in to the database table LONG_DATA_TABLE.

Following is the table structure

SQL> DESC  LONG_DATA_TABLE;
Name                          Null?  Type
-------------------- -------- ----------------------------
SEQ_GENERATED_NUM                  NUMBER
TEXT                          VARCHAR2(4000)



Client told me that suppose the concatenated data string is 8000 characters then store first 1 to 4000 characters
in one row and then store the remaining 4001 to 8000 characters in next row.

Client told me to write a stored procedure to concatenate the elements of the PL/SQL table and store the concatenated string into the LONG_DATA_TABLE.Incoming parameters will be the PL/SQL table.I need to loop through this PL/SQL table ,concatenate elements and store the data into the LONG_DATA_TABLE;

Can you please advice me as how should I write such a generic procedure.

Please help me.
Thanks and Best Regards
-Hemal Deshmukh


Followup   June 28, 2008 - 1pm Central time zone:

clients have software requirements

clients do not describe physical storage.


it would be silly to store this concatenated string in multiple rows to put together in client - when it would be the SAME CODE TO DO IT TO THE EXISTING DATA

do you get the point here - they either take some number of rows and in a client concatenate them (that varchar2(4000) approach) or they take some number of rows and in a client concatenate them (the leave the data as it is approach)

Now, which makes more sense.


(and if a developer could not do what was asked above - which I firmly believe should not be done - which is "build a string from an array - until adding to it would exceed 4000 characters, then reset string and start over - until it exceeds 4000 and so on".........................)
3 stars Parsing a string data into multiple columns data   September 19, 2008 - 11pm Central time zone
Reviewer: Anan from AZ USA
Tom,

I have an external table data in Oracle 10g and needs help in parsing the data based on the 
following:

From FULLPATH data to break into ABC1 after /C-                                 ABC2 after /C-O_ 
                                 ABC3 after /A-A_     
                                 ABC4 after /R-R_ 
                 ABC5 after /R-M_


FULLPATH := 
'/C-TAG-MF-AVAIL/C-O_APP_JA/A-A_PROD-TAG-POOL/R-R_AU00052355-PEOPLE/R-M_AU00052355-SAL';

ABC1 = 'TAG-MF-AVAIL'  
ABC2 = 'APP_JA'
ABC3 = 'PROD-TAG-POOL'
ABC4 = 'AU00052355-PEOPLE'
ABC4 = 'AU00052355-SAL'

select 
SUBSTR('/C-TAG-MF-AVAIL/C-O_APP_JA/A-A_PROD-TAG-POOL/R-R_AU00052355-PEOPLE/R-M_AU00052355-SAL',
 
INSTR('/C-TAG-MF-AVAIL/C-O_APP_JA/A-A_PROD-TAG-POOL/R-R_AU00052355-PEOPLE/R-M_AU00052355-SAL','/',1,
2-1)+3,
 
(INSTR('/C-TAG-MF-AVAIL/C-O_APP_JA/A-A_PROD-TAG-POOL/R-R_AU00052355-PEOPLE/R-M_AU00052355-SAL','/',1
,2)-3)-
  
INSTR('/C-TAG-MF-AVAIL/C-O_APP_JA/A-A_PROD-TAG-POOL/R-R_AU00052355-PEOPLE/R-M_AU00052355-SAL','/',1,
2-1)) from dual;

Thanks,


Followup   September 21, 2008 - 1pm Central time zone:

correct me if I'm wrong, but is the logic you tried to describe for parsing:

FULLPATH :=
'/C-TAG-MF-AVAIL/C-O_APP_JA/A-A_PROD-TAG-POOL/R-R_AU00052355-PEOPLE/R-M_AU000523
55-SAL';

ABC1 = 'TAG-MF-AVAIL'
ABC2 = 'APP_JA'
ABC3 = 'PROD-TAG-POOL'
ABC4 = 'AU00052355-PEOPLE'
ABC4 = 'AU00052355-SAL'


really:

a) look for /C- and take everything from there upto the next /
b) look for /C-O_ and take everything from there upto the next /

and so on? are the /<tags> FIXED, always c, c-o_ and so on.
3 stars Parsing a string data into multiple columns data   September 22, 2008 - 2pm Central time zone
Reviewer: Anan from AZ USA
I am having problem in using REGEXP_SUBSTR to get

a) look for /C- and take everything from there upto the next / 
b) look for /C-O_ and take everything from there upto the next / 

and so on? are the /<tags> FIXED, always c, c-o_ and so on. 


SELECT
  
REGEXP_SUBSTR('/C-TAG-MF-AVAIL/C-O_APP_JA/A-A_PROD-TAG-POOL/R-R_AU00052355-PEOPLE/R-M_AU00052355-SAL
',
                '/C-([[:alnum:]]+\.?){3,4}/?') "REGEXP_SUBSTR"  FROM DUAL;

REGEXP_SUBSTR
-------------
/C-TAG

Thanks


Followup   September 23, 2008 - 12pm Central time zone:

so, I would not use regexp - you need a simple substr.

ops$tkyte%ORA10GR2> create table t ( x varchar2(100) );

Table created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> INSERT INTO T VALUES ( 
'/C-TAG-MF-AVAIL/C-O_APP_JA/A-A_PROD-TAG-POOL/R-R_AU00052355-PEOPLE/R-M_AU00052355-SAL' );

1 row created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> select substr( c1, 1, instr(c1,'/')-1 ) c1,
  2         substr( c2, 1, instr(c2,'/')-1 ) c2,
  3         substr( c3, 1, instr(c3,'/')-1 ) c3,
  4         substr( c4, 1, instr(c4,'/')-1 ) c4,
  5         substr( c5, 1, instr(c5,'/')-1 ) c5
  6    from (
  7  select substr( x, instr( x, '/C-' )+4 ) c1,
  8         substr( x, instr( x, '/C-O_' ) +5 ) c2,
  9         substr( x, instr( x, '/A-A_' ) +5 ) c3,
 10         substr( x, instr( x, '/R-R_' ) +5 ) c4,
 11         substr( x, instr( x, '/R-M_' ) +5 ) ||'/' c5
 12    from t
 13         );

C1                   C2         C3                   C4                   C5
-------------------- ---------- -------------------- -------------------- --------------------
AG-MF-AVAIL          APP_JA     PROD-TAG-POOL        AU00052355-PEOPLE    AU00052355-SAL


5 stars Parameter as a column   October 30, 2008 - 6am Central time zone
Reviewer: A Reader 
Hi Tom,
My table is:

create table tablename(SNO NUMBER(38),NAME VARCHAR2(40),CMV BLOB,WVW BLOB,OLR BLOB,QPE    BLOB,CTTI 
   BLOB,GPS    BLOB,SYS_DATE    DATE);

My Procedure is this: But this procedure is working fine.

PROCEDURE SAT (
 P_NAME    VARCHAR2,
 P_TYPE        VARCHAR2,
 P_RET_VAL     OUT BLOB
)
AS

V_SYS_DATE1    DATE;
V_SYS_DATE2    DATE;

BEGIN

  SELECT  MAX(SYS_DATE)  INTO  V_SYS_DATE1  FROM  INSAT_SAT_DET_MSTR WHERE TRIM(SAT_NAME) = 
'INSAT-3A';
  SELECT  MAX(SYS_DATE)  INTO  V_SYS_DATE2  FROM  INSAT_SAT_DET_MSTR WHERE TRIM(SAT_NAME) = 
'INSAT-3B';

 
  
    IF  P_TYPE  = 'CMV'  THEN
      SELECT  CMV  INTO P_RET_VAL  FROM INSAT_SAT_DET_MSTR 
         WHERE SYS_DATE  =  V_SYS_DATE1 AND NAME = P_NAME;
         
    ELSIF   P_TYPE  = 'WVW'  THEN
      SELECT  WVW  INTO P_RET_VAL  FROM INSAT_SAT_DET_MSTR 
         WHERE SYS_DATE  =  V_SYS_DATE1 AND NAME = P_NAME;
         
    ELSIF   P_TYPE  = 'OLR'  THEN
      SELECT  OLR  INTO P_RET_VAL  FROM INSAT_SAT_DET_MSTR 
         WHERE SYS_DATE  =  V_SYS_DATE1 AND NAME = P_NAME;
         
    ELSIF   P_TYPE  = 'QPE'  THEN
      SELECT  QPE  INTO P_RET_VAL  FROM INSAT_SAT_DET_MSTR 
         WHERE SYS_DATE  =  V_SYS_DATE1 ANDNAME = P_NAME;
      
    ELSIF   P_TYPE  = 'CTTI'  THEN
      SELECT  CTTI  INTO P_RET_VAL  FROM INSAT_SAT_DET_MSTR 
         WHERE SYS_DATE  =  V_SYS_DATE1 AND NAME = P_NAME;
         
     ELSIF   P_TYPE  = 'GPS'  THEN
      SELECT  GPS  INTO P_RET_VAL  FROM INSAT_SAT_DET_MSTR 
         WHERE SYS_DATE  =  V_SYS_DATE1 AND NAME = P_NAME;
         
     END IF;  
 
END;





My requirement is this can we write a procedure like this if possible give me sample code.







PROCEDURE SAT (
 P_NAME    VARCHAR2,
 P_TYPE        VARCHAR2,
 P_RET_VAL     OUT BLOB
)
AS

V_SYS_DATE1    DATE;
V_SYS_DATE2    DATE;

BEGIN

  SELECT  MAX(SYS_DATE)  INTO  V_SYS_DATE1  FROM  INSAT_SAT_DET_MSTR WHERE TRIM(SAT_NAME) = 
'INSAT-3A';
  SELECT  MAX(SYS_DATE)  INTO  V_SYS_DATE2  FROM  INSAT_SAT_DET_MSTR WHERE TRIM(SAT_NAME) = 
'INSAT-3B';

 
  
   
      SELECT  P_TYPE  INTO P_RET_VAL  FROM INSAT_SAT_DET_MSTR 
         WHERE SYS_DATE  =  V_SYS_DATE1 AND NAME = P_NAME;




Thanks in Advance


Followup   October 30, 2008 - 8am Central time zone:

the thing I hate more than IM speak is when people post the same exact thing over and over and over - like I'm going to pay more attention to it

I'm almost tempted to erase the answer at
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:3696816290928#128100650034682944
8

5 stars How to get the concatenated string SORTED ?   January 12, 2009 - 9am Central time zone
Reviewer: Philippe Gaudin from Belgium
Hi Tom,

I used the CAST(COLLECT()) as in the comment :
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:2196162600402#18313422264397

...but I need the concatenated string to be sorted.

I tried :

  SQL>   SELECT deptno,
  2           to_string (CAST (COLLECT (ename) AS ntt_varchar2)) AS emps
  3      FROM (  SELECT deptno,
  4                     ename
  5                FROM emp
  6            ORDER BY deptno,
  7                     ename)
  8  GROUP BY deptno;

    DEPTNO EMPS
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 ADAMS,SMITH,SCOTT,FORD,JONES
        30 ALLEN,JAMES,TURNER,WARD,MARTIN,BLAKE


...but, as you can see in DEPTNO 30 the list of employees is not ordered.

My question : how to get the list of employees sorted ?

Followup   January 12, 2009 - 9pm Central time zone:

did you ctl-f for ordered on this page and read the previous numerous conversations on this?
1 stars How to get the concatenated string SORTED ?   January 13, 2009 - 3am Central time zone
Reviewer: Philippe Gaudin from Belgium
I did CTRL-F ordered on the page... and could'nt find any solution to get the contatenated string sorted using the COLLECT technique.

There are solutions using the STRAGG aggregate function... but I wanted to know if it was possible using COLLECT (or something like that).

I wanted to know if it was possible to sort a NESTED TABLE.

What I could do is :
SQL> SELECT deptno,
       to_string(CAST (MULTISET (SELECT ename
                         FROM emp e2
                        WHERE e1.deptno = e2.deptno order by ename) as ntt_varchar2))
         AS tst
  FROM (SELECT DISTINCT deptno FROM emp) e1

    DEPTNO TST                                               
---------- --------------------------------------------------
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD              
        20 ADAMS,FORD,JONES,SCOTT,SMITH                      
        10 CLARK,KING,MILLER   

... but it has to go twice in the EMP table.

I think I will have to use the STRAGG function.                 


Followup   January 16, 2009 - 3pm Central time zone:

ops$tkyte%ORA9IR2> select deptno, substr( max( sys_connect_by_path(ename,',')), 2) enames
  2    from (
  3  select deptno, ename, row_number() over (partition by deptno order by ename) rn
  4    from emp
  5         )
  6   start with rn = 1
  7  connect by prior deptno = deptno and prior rn+1 = rn
  8  group by deptno
  9  order by deptno
 10  /

    DEPTNO ENAMES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 ADAMS,FORD,JONES,SCOTT,SMITH
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD


4 stars Concatinating a string with a complex parameters   February 24, 2009 - 4am Central time zone
Reviewer: Andrey from Russia, Moscow
Hi, Tom. I have a question.
In an implementing interface ODCIAggregate in
STATIC FUNCTION ODCIAggregateInitialize(ctx IN OUT str_concat_t)
we have a type context parameter. How can we pass it's value from outside? From an aggregate function, for example.
Or we can't.

Followup   February 24, 2009 - 6am Central time zone:

give a bit more context here, sketch out your requirement a bit better. the context parameter is a type in all of the examples here already?


4 stars More explanation about the problem   February 24, 2009 - 7am Central time zone
Reviewer: Andrey from Russia, Moscow
Ok. I have an object type like this:
CREATE OR REPLACE TYPE str_concat_t AS OBJECT
(
   /* Result string */
  ls_sum VARCHAR2(32767)
   /* Delimiter string */
, delim_f char(5) 
   /* Maximum length of a result string */
, maxStringLength_f number 
   /* Logical value, indicates should we add '...' to the
    *  end of the result string when the length of the 
    *  result is bigger then maxStringLength_f 
    *  1 - we should
    *  0 - we should not
    */
, dots_f number(1) 
   /* Logical value, which indicates should we throw an 
    *  Exception when the length of the result is bigger 
    *  then maxStringLength_f  */
, makeError_f number(1) 

, CONSTRUCTOR FUNCTION str_concat_t(delim char, max_length NUMBER, dots number, make_error number)
  RETURN SELF AS RESULT


, MEMBER FUNCTION get_separator RETURN VARCHAR2,
  STATIC FUNCTION ODCIAggregateInitialize(ctx IN OUT str_concat_t)
   RETURN NUMBER

, MEMBER FUNCTION ODCIAggregateIterate(self  IN OUT str_concat_t
                                      ,value IN VARCHAR2)
   RETURN NUMBER

, MEMBER FUNCTION ODCIAggregateMerge(self IN OUT str_concat_t
                                    ,ctx  IN strsum_t)
   RETURN NUMBER

, MEMBER FUNCTION ODCIAggregateTerminate(self  IN str_concat_t
                                        ,value OUT VARCHAR2
                                        ,flags IN NUMBER)
   RETURN NUMBER
)


Then we add a body. I think there's no need to put it here.
And make a aggregate function definition:
CREATE OR REPLACE FUNCTION str_concat (
   as_str_i      VARCHAR2
 )
 RETURN VARCHAR2
 PARALLEL_ENABLE  
  AGGREGATE USING 
  str_concat_t;


In the constructor we'll initialize the object's fields.
But, as i see we have no place to call this constructor in order to it'll be passed into an ODCIAggregateInitialize... Am i right?

If i am, then could we write some code into the aggregate function itself? For example code which will put necessary values into the sys_context.

Followup   February 24, 2009 - 4pm Central time zone:

... But, as i see we have no place to call this constructor in order to it'll be passed into an ODCIAggregateInitialize... Am i right? ...


it is called by us, we call it, you do what you want in it. Not sure what the issue is yet?

...
If i am, then could we write some code into the aggregate function itself? ...

you write all of the code, yes.

If the sys_context is to be used in the query itself, no, this will not be safe, if you need some "state", you can add more attributes to your object type - not sure what you would use sys context for - the query that invokes your code would already be "started", it would be too late to set context values for it.


Or do you mean "i'd like to write an aggregate that works like this:

select my_aggregate( a, b, c, d ) from t;

with many inputs"
4 stars More explanation about the problem 2   February 25, 2009 - 5am Central time zone
Reviewer: Andrey from Russia, Moscow
...it is called by us, we call it, you do what you want in it. Not sure what the issue is yet?...

I mean that we don't call ODCIAggregateInitialize explicitly, so we can't pass an object type parameter into it.

...you write all of the code, yes. ...

Nope. Oracle points an error here. We only can declare an aggregate function, but we can't implement it's body. Or just i don't know how.

...Or do you mean "i'd like to write an aggregate that works like this:

select my_aggregate( a, b, c, d ) from t;

with many inputs"...

Yep, that's a bit closer... Is it possible?

Followup   February 25, 2009 - 7am Central time zone:

I'm not following you at all here -
...
Nope. Oracle points an error here. We only can declare an aggregate function, but we can't implement it's body. Or just i don't know how.

..

you write the type body, we call it, you WRITE the aggregate.


If your goal is to be able to have more than one parameter to the aggregate, the only way I know is the approach already shown on this page.

ctl-f for:

"You would have to sort of "prime the pump" on this one."

to see it, it does involve a "context", you would set the parameters in the session context before invoking the aggregate, yes.




4 stars Converting multiple rows into single row   March 25, 2009 - 4am Central time zone
Reviewer: SuryaKiran from India
create table test_asg(
Assignment_id number(15),
Title varchar2(50),
Emp_cat varchar2(2)
)    

insert into test_asg values(100,'Comp info','AP')    
insert into test_asg values(100,'Technician','CS')    
insert into test_asg values(101,'Faculty','AP')    
insert into test_asg values(102,'Teaching','CS')    
insert into test_asg values(102,'Grad Assist','CS')    
insert into test_asg values(102,'Secretary','AP')    
insert into test_asg values(103,'Director','AP')

select assignment_id, substr(asg_title,2) JOB_TITLE, substr(asg_empcat,2) EMP_CATEGORY
from (
select assignment_id, max(sys_connect_by_path(title,';')) asg_title, 
max(sys_connect_by_path(emp_cat,';')) asg_empcat
from (
select assignment_id, title, emp_cat, row_number() over (partition by assignment_id order by 
assignment_id) rnum
from test_asg where assignment_id = 100
) 
start with rnum = 1
connect by prior rnum = rnum-1 and prior assignment_id = assignment_id
group by assignment_id
order by assignment_id
)
order by assignment_id

When we run the above query the result is :
assignment_id    job_title          emp_category  
100          Comp info;Comp info    AP;AP

It looks like the Job_title and emp_category was repeated which shouldn't be the case. They have to 
be different as seen below. 


I just change the where clause from the inner select to the outer select. 

select assignment_id, substr(asg_title,2) JOB_TITLE, substr(asg_empcat,2) EMP_CATEGORY
from (
select assignment_id, max(sys_connect_by_path(title,';')) asg_title, 
max(sys_connect_by_path(emp_cat,';')) asg_empcat
from (
select assignment_id, title, emp_cat, row_number() over (partition by assignment_id order by 
assignment_id) rnum
from test_asg 
) where assignment_id = 100
start with rnum = 1
connect by prior rnum = rnum-1 and prior assignment_id = assignment_id
group by assignment_id
order by assignment_id
)
order by assignment_id

Now by running the above query the result is :
assignment_id    job_title          emp_category  
100          Comp info;Technician    AP;CS

The second query result is correct one according to my requirement.Why is there a difference 
between these two queries and why in the first query did the titles repeat. 
Can you please help me to resolve the issue ?


Followup   March 29, 2009 - 7pm Central time zone:

This is sufficient to demonstrate you have found a bug (I filed bug 8391928 for this)

ops$tkyte%ORA11GR1> create table test_asg(
  2  Assignment_id number(15),
  3  Title varchar2(50),
  4  Emp_cat varchar2(2)
  5  )    ;

Table created.

ops$tkyte%ORA11GR1>
ops$tkyte%ORA11GR1> insert into test_asg values(100,'Comp info','AP')    ;

1 row created.

ops$tkyte%ORA11GR1> insert into test_asg values(100,'Technician','CS')    ;

1 row created.

ops$tkyte%ORA11GR1>
ops$tkyte%ORA11GR1> select assignment_id, title, emp_cat,
  2         row_number() over (partition by assignment_id order by assignment_id) rnum
  3    from test_asg
  4   where assignment_id = 100
  5  /

ASSIGNMENT_ID TITLE                EMP_CAT                    RNUM
------------- -------------------- -------------------- ----------
          100 Comp info            AP                            1
          100 Technician           CS                            2

ops$tkyte%ORA11GR1> select assignment_id,
  2         sys_connect_by_path(title,';') asg_title,
  3         sys_connect_by_path(emp_cat,';') asg_empcat
  4    from (
  5  select assignment_id, title, emp_cat,
  6         row_number() over (partition by assignment_id order by assignment_id) rnum
  7    from test_asg
  8   where assignment_id = 100
  9         )
 10   start with rnum = 1
 11  connect by prior rnum = rnum-1 and prior assignment_id = assignment_id
 12  /

ASSIGNMENT_ID ASG_TITLE             ASG_EMPCAT
------------- --------------------- --------------------
          100 ;Comp info            ;AP
          100 ;Comp info;Technician ;AP;CS

ops$tkyte%ORA11GR1>
ops$tkyte%ORA11GR1> select assignment_id,
  2         max( asg_title ) job_title,
  3         max( asg_empcat ) emp_category
  4    from (
  5  select assignment_id,
  6         sys_connect_by_path(title,';') asg_title,
  7         sys_connect_by_path(emp_cat,';') asg_empcat
  8    from (
  9  select assignment_id, title, emp_cat,
 10         row_number() over (partition by assignment_id order by assignment_id) rnum
 11    from test_asg
 12   where assignment_id = 100
 13         )
 14   start with rnum = 1
 15  connect by prior rnum = rnum-1 and prior assignment_id = assignment_id
 16         )
 17   group by assignment_id
 18   order by assignment_id
 19  /

ASSIGNMENT_ID JOB_TITLE            EMP_CATEGORY
------------- -------------------- --------------------
          100 ;Comp info;Comp info ;AP;AP




I took query 1 and placed into query 2, query 2 shows the differing strings - we take query 2 and place it into query 3 with a MAX() and - well, data appears that doesn't exist.
5 stars Not repeat result   April 14, 2009 - 11am Central time zone
Reviewer: Serpini from Spain
If you want not repeat result, like 'pepe,juan,pepe' you can rewrite this function

member function ODCIAggregateIterate(self IN OUT string_agg_type,
                                        value IN varchar2 )
   return number
   is
    position  number;
   begin
      position:=INSTR(self.total,value);
      DBMS_OUTPUT.PUT_LINE('Position:'|| position|| ' '||value);
      IF(position is null) THEN
        self.total := self.total || ',' || value;
      elsif(position=0) then
        self.total := self.total || ',' || value;
      END IF;
       return ODCIConst.Success;
   end;


Followup   April 14, 2009 - 12pm Central time zone:

what happens if the strings are Pepe and Pepe Le Pew....


watch out for strings contained in other strings.

5 stars Calculating the value of a string with numbers and arithmetic operators   April 27, 2009 - 1am Central time zone
Reviewer: Logan Palanisamy from Santa Clara, CA, USA
Tom,

Is there way to calculate the value of an arithmetic expression stored as a string. I tried CAST, but it didn't work.


create table ts (f1 varchar2(100));
insert into ts values ('2+2+2+2');
insert into ts values ('1+100');
insert into ts values ('+1+10+2+30+5+7');
select * from ts;
select to_number(f1) from ts;
select to_number(2+2+2+2) from dual;
select to_number('2+2+2+2') from dual;





SQL>create table ts (f1 varchar2(100));

Table created.

SQL>insert into ts values ('2+2+2+2');

1 row created.

SQL>insert into ts values ('1+100');

1 row created.

SQL>insert into ts values ('+1+10+2+30+5+7');

1 row created.

SQL>select * from ts;

F1
-----------------------------------------------
2+2+2+2
1+100
+1+10+2+30+5+7

SQL>select to_number(f1) from ts;
select to_number(f1) from ts
       *
ERROR at line 1:
ORA-01722: invalid number


SQL>select to_number(2+2+2+2) from dual;

TO_NUMBER(2+2+2+2)
------------------
                 8

SQL>select to_number('2+2+2+2') from dual;
select to_number('2+2+2+2') from dual
                 *
ERROR at line 1:
ORA-01722: invalid number


Here is the output I want

select to_number(f1) value from ts;

Value
----------
8
101
55



Followup   April 27, 2009 - 2pm Central time zone:

think about it, how would '1+1' be 'castable' (change datatype) to a number?

It would have to be evaluated.



will it always be plus (addition) or in real is this a string of any degree of complexity (+, -, (), *, /, etc)
5 stars Evaluating an arithmetic string   April 27, 2009 - 3pm Central time zone
Reviewer: Logan Palanisamy from Santa Clara, CA, USA
Tom,

The strings contain either addition or mulitiplication, but not both. It can be of any lenght. No 
subtraction, division or parantheis or any other arithmetic operator. 

e.g.

'+1+15+25+45'
'18*27*32'

The following is not a valid string
'8*10+15'



Followup   April 27, 2009 - 3pm Central time zone:

ops$tkyte%ORA10GR2> create table t ( x int, y varchar2(30) );

Table created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> insert into t values ( 1, '+1+15+25+45' );

1 row created.

ops$tkyte%ORA10GR2> insert into t values ( 2, '18*27*32' );

1 row created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> select x, y, case when oper = '+' then sum(column_value)
  2                    else exp(sum(ln(case when oper='+' then 1 else column_value end)))
  3                            end result
  4    from (select x, y,
  5                 ','||translate( ltrim(y,'+'), '+*', ',,') ||',' newy,
  6                             case when instr( y, '*' ) > 0 then '*' else '+' end oper
  7                    from t ) t,
  8   TABLE(
  9   cast (
 10   multiset(
 11  select trim( substr(newy, instr (newy, ',', 1, level  ) + 1,
 12                  instr (newy, ',', 1, level+1) - instr (newy, ',', 1, level) -1 ) )
 13             from dual
 14           connect by level <= length(newy)-length(replace(newy,',',''))-1
 15  )
 16  as sys.odcinumberlist )
 17  )
 18  group by x, y
 19  /

         X Y                                  RESULT
---------- ------------------------------ ----------
         1 +1+15+25+45                            86
         2 18*27*32                            15552


5 stars Superb!!!   April 27, 2009 - 5pm Central time zone
Reviewer: Logan Palanisamy from Santa Clara, CA, USA


5 stars Not repeat result version 2   April 28, 2009 - 7am Central time zone
Reviewer: Serpini from Spain
The solution of problem with Pepe and Pepe Le Pew... is split, I create a function for split like 
this:

----------
create or replace function fn_split (
    p_cadena       in   varchar2,
    p_simbolo      in   char,
    p_ocurrencia   in   number default 1
  ) return varchar2 is
    v_txt       varchar2(200);
    v_pos_ini   integer := 0;
    v_pos_fi    integer;
  begin
    -- Función que separa cadenas de texto (p_cadena) por el símbolo p_simbolo
    -- Devuelve la ocurrencia p_ocurrencia
    -- Devuelve null si la ocurrencia no existe
    -- Devuelve el símbolo p_simbolo si no hay ningún carácter entre dos
    --ocurrencias de p_simbolo
    if p_ocurrencia > 1 then
      v_pos_ini := instr (
        str1   =>   p_cadena,
        str2   =>   p_simbolo,
        pos    =>   1,
        nth    =>   p_ocurrencia - 1
      );
    end if;
    v_pos_fi := instr (
      str1   =>   p_cadena,       -- test string
      str2   =>   p_simbolo,      -- string to locate
      pos    =>   1,              -- position
      nth    =>   p_ocurrencia    -- occurrence number
    );
    if v_pos_ini = v_pos_fi then
      if p_ocurrencia = 1 then
        v_txt := substr (
          str1   =>   p_cadena,
          pos    =>   1
        );
      end if;
    elsif v_pos_ini > v_pos_fi then
      v_txt := substr (
        str1   =>   p_cadena,
        pos    =>   v_pos_ini + 1
      );
    elsif v_pos_fi = v_pos_ini + 1 then
      v_txt := p_simbolo;
    else
      v_txt := substr (
        str1   =>   p_cadena,
        pos    =>   v_pos_ini + 1,
        len    =>   (v_pos_fi - v_pos_ini) - 1
      );
    end if;
    --
    return v_txt;
  end fn_split;
-----------

And now member function ODCIAggregateIterate is:

-------------
member function ODCIAggregateIterate(self IN OUT string_agg_type,
                                        value IN varchar2 )
   return number
   is
    position  number;
    puntero   number;
    anterior  varchar2(255);
   begin
      position:=INSTR(self.total,value);
      IF(position is null) THEN
        self.total := self.total || ',' || value;
      elsif(position=0) then
        self.total := self.total || ',' || value;
      ELSE
          puntero:=2;
          anterior:=fn_split(self.total,',',puntero);
          while((anterior is not null) and (anterior!=value)) loop
            puntero:=puntero+1;
            anterior:=fn_split(self.total,',',puntero);
          end loop;
          if((anterior!=value) OR (anterior is null)) then 
            self.total := self.total || ',' || value; 
          end if;
      END IF;
       return ODCIConst.Success;
   end;
----------


Followup   April 28, 2009 - 10am Central time zone:

or, you could leave code "as is" and use distinct

ops$tkyte%ORA11GR1> select deptno, stragg(distinct job) a, stragg(job) b from emp group by deptno;

    DEPTNO A                              B
---------- ------------------------------ ------------------------------
        10 CLERK,MANAGER,PRESIDENT        MANAGER,CLERK,PRESIDENT
        20 ANALYST,CLERK,MANAGER          CLERK,CLERK,ANALYST,ANALYST,MA
                                          NAGER

        30 CLERK,MANAGER,SALESMAN         SALESMAN,SALESMAN,CLERK,SALESM
                                          AN,MANAGER,SALESMAN


5 stars varchar2 max size reduced to 8192   May 13, 2009 - 4am Central time zone
Reviewer: Michal Pravda from Prague
Good morning Tom,

I encountered something I don't understand with (implicit) conversion from Clob to varchar2. There 
is some kind of "strange" size limitation of 8192 characters.

Piece of code is worth 1000 words, so here is an example of this behavior. Could you please tell me 
what am i doing wrong? I haven't found anything related in the documentation.

declare 
cl clob;
str varchar2(15000 char);
begin
  for idx in 1..1000 loop   -- works fine
    str := str || '0123456789';
    dbms_output.put_line(idx);
  end loop;  

  for idx in 1..1000 loop   -- ends with "numeric or value error" when trying to 
                            -- assign clob longer then 8192 to varchar2 variable
    cl := cl || '0123456789';
    dbms_output.put_line(idx);
    str := cl;
  end loop;  

end;
/

Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bi
PL/SQL Release 10.2.0.4.0 - Production
"CORE    10.2.0.4.0    Production"
TNS for IBM/AIX RISC System/6000: Version 10.2.0.4.0 - Productio
NLSRTL Version 10.2.0.4.0 - Production


Followup   May 13, 2009 - 1pm Central time zone:

what is your character set - any change the resulting STRING is using multibyte data (and your char(15000) is 15000 BYTES not characters...)


ops$tkyte%ORA10GR2> declare
  2  cl clob;
  3  str varchar2(15000 char);
  4  begin
  5    for idx in 1..1000 loop   -- works fine
  6      str := str || '0123456789';
  7      dbms_output.put_line(idx);
  8    end loop;
  9
 10    for idx in 1..1000 loop   -- ends with "numeric or value error" when trying to
 11                              -- assign clob longer then 8192 to varchar2 variable
 12      cl := cl || '0123456789';
 13      dbms_output.put_line(idx);
 14      str := cl;
 15    end loop;
 16
 17  end;
 18  /
1
2
3
4
5
6
7
...
992
993
994
995
996
997
998
999
1000

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2>



ops$tkyte%ORA10GR2> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - Prod
PL/SQL Release 10.2.0.4.0 - Production
CORE    10.2.0.4.0      Production
TNS for Linux: Version 10.2.0.4.0 - Production
NLSRTL Version 10.2.0.4.0 - Production

ops$tkyte%ORA10GR2> @test
ops$tkyte%ORA10GR2> declare
  2  cl clob;
  3  str varchar2(15000 char);
  4  begin
  5    for idx in 1..1000 loop   -- works fine
  6      str := str || '0123456789';
  7    end loop;
  8    dbms_output.put_line( 'length = ' || length(str) );
  9
 10    for idx in 1..1000 loop   -- ends with "numeric or value error" when trying to
 11                              -- assign clob longer then 8192 to varchar2 variable
 12      cl := cl || '0123456789';
 13      str := cl;
 14    end loop;
 15    dbms_output.put_line( 'length = ' || length(str) );
 16  end;
 17  /
length = 10000
length = 10000

PL/SQL procedure successfully completed.



5 stars reduced string length   June 12, 2009 - 8am Central time zone
Reviewer: Michal Pravda from Prague
Hello,
it does have something in common with charsets. At first I'd like to address your comment in 
parentheses.
[cite from Ora 9 plsql guide]
If you specify the maximum size in bytes rather than characters, a VARCHAR2(n)
variable might be too small to hold n multibyte characters. To avoid this possibility,
use the notation VARCHAR2(n CHAR)so that the variable can hold n characters in
the database character set, even if some of those characters contain multiple bytes.
When you specify the length in characters, the upper limit is still 32767 bytes. So for
double-byte and multibyte character sets, you can only specify 1/2 or 1/3 as many
characters as with a single-byte character set.
[/cite]
So I believed that varchar2(15000 char) would be sufficient.

The DB is utf8 but it shouldn't matter. I mean not at this boundary. I changed my example a bit to 
32767 char variable (which equals 32767 byte variable, I believe). It should be sufficient even if 
every utf8 character was 3byte long. And I use ascii compatible digits for padding which are 
probably encoded using one byte per char

First DB - utf8, 10G
SQL> select * from nls_database_parameters;

PARAMETER                      VALUE
------------------------------ ------------------------------------
NLS_LANGUAGE                   CZECH
NLS_TERRITORY                  CZECH REPUBLIC
NLS_CURRENCY                   Kc
NLS_ISO_CURRENCY               CZECH REPUBLIC
NLS_NUMERIC_CHARACTERS         ,.
NLS_CHARACTERSET               UTF8
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                DD.MM.RR
NLS_DATE_LANGUAGE              CZECH
NLS_SORT                       CZECH
NLS_TIME_FORMAT                HH24:MI:SSXFF

PARAMETER                      VALUE
------------------------------ ------------------------------------
NLS_TIMESTAMP_FORMAT           DD.MM.RR HH24:MI:SSXFF
NLS_TIME_TZ_FORMAT             HH24:MI:SSXFF TZR
NLS_TIMESTAMP_TZ_FORMAT        DD.MM.RR HH24:MI:SSXFF TZR
NLS_DUAL_CURRENCY              Kc
NLS_COMP                       BINARY
NLS_LENGTH_SEMANTICS           CHAR
NLS_NCHAR_CONV_EXCP            FALSE
NLS_NCHAR_CHARACTERSET         AL16UTF16
NLS_RDBMS_VERSION              10.2.0.4.0

20 rows selected.

--test
DECLARE
  cl CLOB;
  str VARCHAR2(32767 CHAR);
  tmp VARCHAR2(10);
BEGIN
  tmp := '0123456789';
  FOR idx IN 1 .. 1000 LOOP
    -- works fine
    str := str || tmp;
  END LOOP;
  dbms_output.put_line('length = ' || length(str));
  str := NULL;
  BEGIN
    FOR idx IN 1 .. 1000 LOOP
      -- ends with "numeric or value error" when trying to
      -- assign clob longer then 8192 to varchar2 variable
      cl  := cl || tmp;
      str := cl;
    END LOOP;
  EXCEPTION
    WHEN OTHERS THEN
      dbms_output.put_line(SQLERRM);
  END;
  dbms_output.put_line('clob2varchar length = ' || length(str));
  str := NULL;
  cl  := NULL;
  FOR idx IN 1 .. 1000 LOOP
    --works fine
    str := str || tmp;
    cl  := str;
  END LOOP;

  dbms_output.put_line('varchar2clob length = ' || length(str));
END;
/

SQL> /
length = 10000
ORA-06502: PL/SQL: numeric or value error
clob2varchar length = 8190
varchar2clob length = 10000


Second DB - Iso88592, 9i - works OK.

SQL> select * from nls_database_parameters;

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_CSMIG_SCHEMA_VERSION       2
NLS_NCHAR_CHARACTERSET         AL16UTF16
NLS_LANGUAGE                   AMERICAN
NLS_TERRITORY                  AMERICA
NLS_CURRENCY                   $
NLS_ISO_CURRENCY               AMERICA
NLS_NUMERIC_CHARACTERS         .,
NLS_CHARACTERSET               EE8ISO8859P2
NLS_CALENDAR                   GREGORIAN
NLS_DATE_FORMAT                DD-MON-RR
NLS_DATE_LANGUAGE              AMERICAN

PARAMETER                      VALUE
------------------------------ ----------------------------------------
NLS_SORT                       BINARY
NLS_TIME_FORMAT                HH.MI.SSXFF AM
NLS_TIMESTAMP_FORMAT           DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT             HH.MI.SSXFF AM TZR
NLS_TIMESTAMP_TZ_FORMAT        DD-MON-RR HH.MI.SSXFF AM TZR
NLS_DUAL_CURRENCY              $
NLS_COMP                       BINARY
NLS_LENGTH_SEMANTICS           BYTE
NLS_NCHAR_CONV_EXCP            FALSE
NLS_RDBMS_VERSION              9.2.0.8.0

21 rows selected.

DECLARE
  cl CLOB;
  str VARCHAR2(32767 CHAR);
  tmp VARCHAR2(10);
BEGIN
  tmp := '0123456789';
  FOR idx IN 1 .. 1000 LOOP
    -- works fine
    str := str || tmp;
  END LOOP;
  dbms_output.put_line('length = ' || length(str));
  str := NULL;
  BEGIN
    FOR idx IN 1 .. 1000 LOOP
      --works fine
      cl  := cl || tmp;
      str := cl;
    END LOOP;
  EXCEPTION
    WHEN OTHERS THEN
      dbms_output.put_line(SQLERRM);
  END;
  dbms_output.put_line('clob2varchar length = ' || length(str));
  str := NULL;
  cl  := NULL;
  FOR idx IN 1 .. 1000 LOOP
    --works fine
    str := str || tmp;
    cl  := str;
  END LOOP;

  dbms_output.put_line('varchar2clob length = ' || length(str));
END;
/

length = 10000
clob2varchar length = 10000
varchar2clob length = 10000




Followup   June 12, 2009 - 11am Central time zone:

... So I believed that varchar2(15000 char) would be sufficient. ...

multibyte data is MULTI-byte (more than one, more than two even, or three), the longest string in plsql is 32k - no matter what.

so, that might not be sufficient.


in any case, why wouldn't you do a little debugging to see how big the string can actually get - don't just print out the error message, print out the length in bytes (lengthb) of the string as it was when it was last succcessfully appended to
5 stars reduced varchar2 size   June 15, 2009 - 5am Central time zone
Reviewer: Michal Pravda from Prague
Hi,
I haven't done the "debugging" before because of something between 
a) not very used to lengthB (and similar byte) function 
and belief that
b) utf-8 uses up to 3 bytes so 8192 boundary has nothing in common with the problem. (which is 
wrong(originally utf-8 was up to 6 bytes, norm from 2003 reduced that to 4 (according to 
wikipaedia))) 

Here's my revised script, it's results and my interpretation (ran on utf-8 10.2.04 DB with 
(NLS_NCHAR_CHARACTERSET         AL16UTF16).

DECLARE
  ncl NCLOB;
  cl CLOB;
  str VARCHAR2(32767);
  tmp VARCHAR2(10);
BEGIN
--  tmp := chr(14989980) || chr(14989980) || chr(14989980) || chr(14989980) || chr(14989980) || 
chr(14989980) || chr(14989980) || chr(14989980) || chr(14989980) || chr(14989980);
--  tmp := '0123456789';
  tmp := '¿¿¿¿¿¿¿¿¿¿';

  dbms_output.put_line('CLOB CLOB CLOB CLOB CLOB CLOB');
  str := NULL;
  BEGIN
    FOR idx IN 1 .. 819 LOOP
      -- ends with "numeric or value error" when trying to
      -- assign clob longer then 8191 to varchar2 variable
      cl  := cl || tmp;
      str := cl;
    END LOOP;
    cl  := cl || '¿';
    str := cl;
    dbms_output.put_line('ok so far');
    dbms_output.put_line('clob2varchar length = ' || length(str) || ', clob length ' || 
length(cl));
    dbms_output.put_line('clob2varchar lengthb= ' || lengthb(str) || ', dbms_lob.getlength ' ||
                         dbms_lob.getlength(cl));
    cl  := cl || 'a';
    str := cl;
    dbms_output.put_line('ok so far');
    dbms_output.put_line('clob2varchar length = ' || length(str) || ', clob length ' || 
length(cl));
    dbms_output.put_line('clob2varchar lengthb= ' || lengthb(str) || ', dbms_lob.getlength ' ||
                         dbms_lob.getlength(cl));
  
  EXCEPTION
    WHEN OTHERS THEN
      dbms_output.put_line(SQLERRM);
      dbms_output.put_line('clob2varchar length = ' || length(str) || ', clob length ' || 
length(cl));
      dbms_output.put_line('clob2varchar lengthb= ' || lengthb(str) || ', dbms_lob.getlength ' ||
                           dbms_lob.getlength(cl));
  END;
  
  dbms_output.put_line('NCLOB NCLOB NCLOB NCLOB NCLOB NCLOB');
  str := NULL;
  BEGIN
    FOR idx IN 1 .. 1638 LOOP
      -- ends with "numeric or value error" when trying to
      -- assign nclob longer then 16383 to varchar2 variable
      ncl  := ncl || tmp;
      str := ncl;
    END LOOP;
    ncl  := ncl || '¿¿¿';
    str := ncl;
    dbms_output.put_line('ok so far');
    dbms_output.put_line('nclob2varchar length = ' || length(str) || ', nclob length ' || 
length(ncl));
    dbms_output.put_line('nclob2varchar lengthb= ' || lengthb(str) || ', dbms_lob.getlength ' ||
                         dbms_lob.getlength(ncl));
    ncl  := ncl || 'a';
    str := ncl;
    dbms_output.put_line('ok so far');
    dbms_output.put_line('nclob2varchar length = ' || length(str) || ', nclob length ' || 
length(ncl));
    dbms_output.put_line('nclob2varchar lengthb= ' || lengthb(str) || ', dbms_lob.getlength ' ||
                         dbms_lob.getlength(ncl));
  
  EXCEPTION
    WHEN OTHERS THEN
      dbms_output.put_line(SQLERRM);
      dbms_output.put_line('nclob2varchar length = ' || length(str) || ', nclob length ' || 
length(ncl));
      dbms_output.put_line('nclob2varchar lengthb= ' || lengthb(str) || ', dbms_lob.getlength ' ||
                           dbms_lob.getlength(ncl));
  END;

  dbms_output.put_line('NCLOB with kanji NCLOB with kanji NCLOB with kanji ');
  str := NULL;
  ncl := null;
  tmp := chr(14989980) || chr(14989980) || chr(14989980) || chr(14989980) || chr(14989980) || 
chr(14989980) || chr(14989980) || chr(14989980) || chr(14989980) || chr(14989980);  BEGIN
    FOR idx IN 1 .. 1092 LOOP
      -- ends with "buffer too small" when trying to
      -- assign nclob longer then 16383 to varchar2 variable
      ncl  := ncl || tmp;
      str := ncl;
    END LOOP;
    ncl  := ncl || '¿¿¿a';
    str := ncl;
    dbms_output.put_line('ok so far');
    dbms_output.put_line('nclob2varchar length = ' || length(str) || ', nclob length ' || 
length(ncl));
    dbms_output.put_line('nclob2varchar lengthb= ' || lengthb(str) || ', dbms_lob.getlength ' ||
                         dbms_lob.getlength(ncl));
    ncl  := ncl || 'a';
    str := ncl;
    dbms_output.put_line('ok so far');
    dbms_output.put_line('nclob2varchar length = ' || length(str) || ', nclob length ' || 
length(ncl));
    dbms_output.put_line('nclob2varchar lengthb= ' || lengthb(str) || ', dbms_lob.getlength ' ||
                         dbms_lob.getlength(ncl));
  
  EXCEPTION
    WHEN OTHERS THEN
      dbms_output.put_line(SQLERRM);
      dbms_output.put_line('nclob2varchar length = ' || length(str) || ', nclob length ' || 
length(ncl));
      dbms_output.put_line('nclob2varchar lengthb= ' || lengthb(str) || ', dbms_lob.getlength ' ||
                           dbms_lob.getlength(ncl));
  END;

  
END;
/

CLOB CLOB CLOB CLOB CLOB CLOB
ok so far
clob2varchar length = 8191, clob length 8191
clob2varchar lengthb= 16382, dbms_lob.getlength 8191
ORA-06502: PL/SQL: numeric or value error
clob2varchar length = 8191, clob length 8192
clob2varchar lengthb= 16382, dbms_lob.getlength 8192
NCLOB NCLOB NCLOB NCLOB NCLOB NCLOB
ok so far
nclob2varchar length = 16383, nclob length 16383
nclob2varchar lengthb= 32766, dbms_lob.getlength 16383
ORA-06502: PL/SQL: numeric or value error
nclob2varchar length = 16383, nclob length 16384
nclob2varchar lengthb= 32766, dbms_lob.getlength 16384
NCLOB with kanji NCLOB with kanji NCLOB with kanji 
ok so far
nclob2varchar length = 10924, nclob length 10924
nclob2varchar lengthb= 32767, dbms_lob.getlength 10924
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
nclob2varchar length = 10924, nclob length 10925
nclob2varchar lengthb= 32767, dbms_lob.getlength 10925


Varchar2 max size is 32k -1 bytes. Utf-8 takes up to 4 chars so I think that Oracle takes the safe 
and fast way of raising an error when there is potential of buffer overflow regardless of actual 
data. So for this assignment  there is a limit of trunc((32768 - 1) /4) = 8191 characters.

With NCLOB the situation is a bit different. I think that I am missing something, but don't know 
what.

Unicode with aforementioned charset (NLS_NCHAR_CHARACTERSET         AL16UTF16) uses I think exactly 
two bytes per character. However you need 3 bytes to save some characters (codepoints) 
representable in this encoding when you want to reencode it in utf-8. So Oracle shouldn't allow 
(32767 /2) chars to be assigned to varchar2, because it can't guarantee that it will map all 
characters within NCLOB to utf-8 varchar2 with 2 or less bytes. Which is demonstrated in NCLOB with 
Kanji section. 
Looks like Oracle takes the "fast and rather safe" way of allowing only (32768 - 1) / 2 chars to be 
assigned and relies on other mechanisms (buffer overflow check) to prevent the bad from happen.


4 stars OK   July 9, 2009 - 9am Central time zone
Reviewer: Kumar from Pune,India
Hi Tom,
Can't a varchar2 value be added to a clob variable?
I dynamically construct a query from 3 clob variables and later I open a ref cursor like

open refc for select * from dyn_sql1||dyn_sql2||dyn_sql3;

But I am getting error as expression is of wrong type at the open statement.
Can you please advise?


Followup   July 14, 2009 - 2pm Central time zone:

... Can't a varchar2 value be added to a clob variable? ...

it could be pre-pended or appended - you don't really "add" strings.

and you still have the 32k limit - so it would not matter.

and the type of input to native dynamic sql in 10g and before is a varchar2, not a clob.


ops$tkyte%ORA10GR2> declare
  2          l_clob clob := 'select * from dual';
  3          c      sys_refcursor;
  4  begin
  5          open c for l_clob;
  6  end;
  7  /
        open c for l_clob;
                   *
ERROR at line 5:
ORA-06550: line 5, column 13:
PLS-00382: expression is of wrong type
ORA-06550: line 5, column 2:
PL/SQL: Statement ignored


ops$tkyte%ORA10GR2> declare
  2          l_clob varchar2(100) := 'select * from dual';
  3          c      sys_refcursor;
  4  begin
  5          open c for l_clob;
  6  end;
  7  /

PL/SQL procedure successfully completed.




and the result of clob || varchar2 is going to be a clob. you could to_char() it

ops$tkyte%ORA10GR2> declare
  2          l_clob clob := 'select * from dual';
  3          l_varchar2 varchar2(100) := ' xxx';
  4          c      sys_refcursor;
  5  begin
  6          open c for to_char(l_clob||l_varchar2);
  7  end;
  8  /

PL/SQL procedure successfully completed.



In 11g -clobs are OK with native dynamic sql.


Write a Review
 


All information and materials provided here are provided "as-is"; Oracle disclaims all express and implied warranties, including, the implied warranties of merchantability or fitness for a particular use. Oracle shall not be liable for any damages, including, direct, indirect, incidental, special or consequential damages for loss of profits, revenue, data or data use, incurred by you or any third party in connection with the use of this information or these materials.

About Oracle | Legal Notices and Terms of Use | Privacy Statement