Print Record fields in PL/SQL - oracle

How can I print all the fields of a record variable in PL/SQL.
The record variable has got lots of fields so is there a better way than printing each field?
Also tried dynamic sql but didn't help.

Building on Ollies use of dbms_output, but for to dynamically go through the cursor
set up for test
/*create table temp (aa varchar2(50) , bb number , cc date ) ;
insert into temp (aa,bb,cc)
select chr(level+100) , level, sysdate+level
from dual
connect by level < 15 ;
/
*/
Block to show the test (this assumes 11g)
set serveroutput on
declare
l_cur SYS_REFCURSOR ;
PROCEDURE CursorOutput(
p_refcursor IN OUT SYS_REFCURSOR
)
AS
l_desc DBMS_SQL.DESC_TAB ;
l_cols BINARY_INTEGER ;
l_cursor BINARY_INTEGER ;
v_varchar2 VARCHAR2( 4000 ) ;
v_number NUMBER ;
v_date DATE ;
l_data varchar2( 32767 ) ;
l_columnValue VARCHAR2( 32767 ) ;
l_processedRows Number := 0;
BEGIN
/* Convert refcursor "parameter" to DBMS_SQL cursor... */
l_cursor := DBMS_SQL.TO_CURSOR_NUMBER( p_refcursor );
/* Describe the cursor... */
DBMS_SQL.DESCRIBE_COLUMNS( l_cursor, l_cols, l_desc );
/* Define columns to be fetched. We're only using V2, NUM, DATE for example...
for a complete list of the col_types this link is accessible.
http://download.oracle.com/docs/cd/B10501_01/server.920/a96540/sql_elements2a.htm#45504
http://forums.oracle.com/forums/thread.jspa?threadID=912475
if not a usable type, will throw new exception
*/
FOR i IN 1 .. l_cols LOOP
IF l_desc(i).col_type = 2 THEN
DBMS_SQL.DEFINE_COLUMN(l_cursor, i, v_number);
ELSIF l_desc(i).col_type = 12 THEN
DBMS_SQL.DEFINE_COLUMN(l_cursor, i, v_date);
ELSif l_desc(i).col_type = 01 or l_desc(i).col_type = 96 then
DBMS_SQL.DEFINE_COLUMN(l_cursor, i, v_varchar2, 4000);
else
--raise an exception if the user's query contains a datatype not (yet) supported by this procedure
RAISE_APPLICATION_ERROR(-20000, 'Invalid Data Type for conversion to delimited file. {' || l_desc(i).col_name || '}');
END IF;
END LOOP;
/* -- print out the column names if desired
FOR i IN 1 .. l_cols LOOP
dbms_output.put_line('** ' || l_desc(i).col_name) ;
END LOOP;
*/
/* Fetch all data... */
WHILE DBMS_SQL.FETCH_ROWS(l_cursor) > 0 LOOP
dbms_output.put_line('LINE: ' || l_processedRows || '');
FOR i IN 1 .. l_cols LOOP
if l_desc(i).col_type = 12 THEN --we are in a date
DBMS_SQL.COLUMN_VALUE(l_cursor, i, v_date);
v_varchar2 := to_char(v_date , 'dd-MON-yyyy' ) ;
elsif l_desc(i).col_type = 2 THEN --we are in a number
DBMS_SQL.COLUMN_VALUE(l_cursor, i, v_number);
v_varchar2 := to_char(v_number) ;
else --treat it as a string (should be varchar2,char,etc)
DBMS_SQL.COLUMN_VALUE(l_cursor, i, v_varchar2);
IF v_varchar2 IS NOT NULL THEN
v_varchar2 := '"' || v_varchar2 || '"' ;
ELSE
v_varchar2 := '';
END IF ;
end if ;
dbms_output.put_line(l_desc(i).col_name || '=>' || v_varchar2) ;
END LOOP;
l_processedRows := l_processedRows + 1 ;
END LOOP;
dbms_sql.close_cursor(l_cursor);
dbms_output.put_line('I found and processed ' || l_processedRows || ' rows .');
END;
begin
open l_cur for select * from temp;
CursorOutput(p_refcursor => l_cur) ;
end ;
/
will give you this result
LINE: 0
AA=>"e"
BB=>1
CC=>04-JAN-2012
LINE: 1
AA=>"f"
BB=>2
CC=>05-JAN-2012
LINE: 2
AA=>"g"
BB=>3
CC=>06-JAN-2012
LINE: 3
AA=>"h"
BB=>4
CC=>07-JAN-2012
LINE: 4
AA=>"i"
BB=>5
CC=>08-JAN-2012
LINE: 5
AA=>"j"
BB=>6
CC=>09-JAN-2012
LINE: 6
AA=>"k"
BB=>7
CC=>10-JAN-2012
LINE: 7
AA=>"l"
BB=>8
CC=>11-JAN-2012
LINE: 8
AA=>"m"
BB=>9
CC=>12-JAN-2012
LINE: 9
AA=>"n"
BB=>10
CC=>13-JAN-2012
LINE: 10
AA=>"o"
BB=>11
CC=>14-JAN-2012
LINE: 11
AA=>"p"
BB=>12
CC=>15-JAN-2012
LINE: 12
AA=>"q"
BB=>13
CC=>16-JAN-2012
LINE: 13
AA=>"r"
BB=>14
CC=>17-JAN-2012
I found and processed 14 rows .
I had done something similar to this to dynamically build a csv file utilizing these two links as sources
http://www.oracle-developer.net/display.php?id=505
http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:88212348059
Depending on what you are going for, however, you may just want to run it in SQL Developer (or Toad) and export the results!

If it's a PL/SQL block that you are running in an IDE then you could at a pinch use DBMS_OUTPUT to output the values.
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_output.htm
For example:
SET SERVEROUTPUT ON
DECLARE
-- Define the record
TYPE test_rectype IS RECORD (
field1 NUMBER,
field2 VARCHAR2
);
-- Define a variable for the record
test_rec TEST_RECTYPE;
BEGIN
-- Populate the record
test_rec.field1 := 1;
test_rec.field2 := 'my value';
-- Enable the DBMS_OUTPUT
DBMS_OUTPUT.enable(1000000);
-- Send the output to the buffer
DBMS_OUTPUT.put_line('Field1: '||test_rec.field1||', Field2: '||test_rec.field2);
END;
There is more to DBMS_OUTPUT so take a look at the docs from the link above.
Alternatively, you could write the values to a file using UTL_FILE.
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/u_file.htm
Hope it helps...

Related

How can I do a procedure that tells me the size of the substrings?

I am learning plsql and I have some doubts.
How do I get the size of all substrings in the string? The way I did it, I'm only managing to return the size of just the first substring.
set serveroutput on;
create or replace procedure pr_size (value varchar2)
as
answer varchar2 (300): = '';
begin
for x in 1 .. length (value) loop
cont: = x;
if substr (value, x, 1) = ''
then dbms_output.put_line ('Size:' || length (answer));
exit;
else answer: = answer || substr (value, x, 1);
end if;
end loop;
end;
call pr_size ('Word size calculation test');
you can do something like this
DECLARE
type string_table is table of VARCHAR2(100);
st string_table;
BEGIN
SELECT regexp_substr('THIS IS TEST', '[^ ]+', 1, LEVEL) BULK COLLECT INTO st FROM dual CONNECT by level <= regexp_count('THIS IS TEST', '[^ ]+');
for i in 1..st.COUNT
LOOP
dbms_output.put_line(length(st(i)));
END LOOP;
END;
If you want a string of length like in your code then
CREATE OR REPLACE PROCEDURE pr_size (value varchar2)
AS
type string_table is table of VARCHAR2(100);
st string_table;
answer VARCHAR2(100);
BEGIN
SELECT regexp_substr(value, '[^ ]+', 1, LEVEL) BULK COLLECT INTO st FROM dual CONNECT by level <= regexp_count(value, '[^ ]+');
for i in 1..st.COUNT
LOOP
answer := length(st(i))||' '||answer;
END LOOP;
dbms_output.put_line(answer);
END;
If you want to use your logic then I have corrected your logic
DECLARE
value VARCHAR2(100) := 'this is test for word length';
answer VARCHAR2(100);
cont PLS_INTEGER := 0;
BEGIN
for x in 1 .. length (value) loop
if substr (value, x, 1) = ' ' then
dbms_output.put_line ('Size:' || cont);
cont := 0;
ELSE
cont := cont + 1;
end if;
end loop;
dbms_output.put_line ('Size:' || cont);
END;

sending large sql queries as input to a procedure in oracle

I wrote a procedure that will automatically get the data from the given query and put the reuslt data set into a file in directory with delimiters. It works good for smaller queries like select * from table_1 but not for big queries like :
SELECT
i.row_id,
translate(i.x_notes_txt, chr(10)||chr(13)||'|' , ' '),
null
FROM communication i,
contact c
WHERE i.last_upd >= (SELECT to_char(last_updated_dt, 'DD-MON-YYYY')
FROM extract_status
WHERE extract_nm = 'INTN300')
AND i.last_upd < sysdate
AND i.x_interaction_type_cd NOT IN ('XRAC','FMS','ATV','IRL')
AND i.pr_con_id = c.row_id
AND c.x_Prospect_Ind = 'Y';
while calling procedure that accepts query procdure : - tab_to_flat('with_the_above_query') it is showing errors like
1) PLS-00103: Encountered the symbol "|" .
2) Encountered the symbol "),
null
FROM communication i,
And many more ..Can anybody help how to pass these queries as a input????
/* Formatted on 06/06/2013 1:42:56 PM (QP5 v5.163.1008.3004) */
CREATE OR REPLACE FUNCTION tab_to_flat (input_query IN CLOB,
dir_name IN VARCHAR2,
file_name IN VARCHAR2,
seperator IN VARCHAR2)
RETURN NUMBER
IS
c_seperator VARCHAR2 (3) := ' ';
incoming_seperator VARCHAR2 (3) := seperator;
no_of_rows NUMBER;
rec_tab DBMS_SQL.DESC_TAB;
col_cnt INTEGER;
src_id INTEGER DEFAULT DBMS_SQL.open_cursor;
val_varchar VARCHAR2 (32767);
val_num NUMBER;
val_date DATE;
file_input UTL_FILE.file_type;
l_start NUMBER;
row_cnt NUMBER := 0;
BEGIN
l_start := DBMS_UTILITY.get_time;
file_input :=
UTL_FILE.fopen (dir_name,
file_name,
'w',
32767);
DBMS_SQL.parse (src_id, input_query, 1);
DBMS_SQL.describe_columns (src_id, col_cnt, rec_tab);
FOR i IN 1 .. col_cnt
LOOP
CASE (rec_tab (i).col_type)
WHEN 1
THEN
DBMS_SQL.define_column (src_id,
i,
val_varchar,
32767);
WHEN 2
THEN
DBMS_SQL.define_column (src_id, i, val_num);
--when 8 then dbms_sql.define_column_long(src_id,i);
WHEN 12
THEN
DBMS_SQL.define_column (src_id, i, val_date);
ELSE
DBMS_SQL.define_column (src_id,
i,
val_varchar,
32767);
END CASE;
END LOOP;
no_of_rows := DBMS_SQL.execute (src_id);
LOOP
EXIT WHEN (DBMS_SQL.FETCH_ROWS (src_id) <= 0);
c_seperator := ' ';
FOR j IN 1 .. col_cnt
LOOP
CASE (rec_tab (j).col_type)
WHEN 1
THEN
DBMS_SQL.COLUMN_VALUE (src_id, j, val_varchar);
UTL_FILE.put (file_input, c_seperator || val_varchar);
WHEN 2
THEN
DBMS_SQL.COLUMN_VALUE (src_id, j, val_num);
UTL_FILE.put (file_input, c_seperator || val_num);
-- when 8 then dbms_sql.column_value_long(src_id,j,4000,1);
WHEN 12
THEN
DBMS_SQL.COLUMN_VALUE (src_id, j, val_date);
UTL_FILE.put (
file_input,
c_seperator || TO_CHAR (val_date, 'MM/DD/YYYY HH24:MI:SS'));
ELSE
DBMS_SQL.COLUMN_VALUE (src_id, j, val_varchar);
UTL_FILE.put (file_input, c_seperator || val_varchar);
END CASE;
c_seperator := incoming_seperator;
END LOOP;
UTL_FILE.new_line (file_input);
row_cnt := row_cnt + 1;
END LOOP;
DBMS_SQL.close_cursor (src_id);
DBMS_OUTPUT.put_line (
'The execution time is : ' || (DBMS_UTILITY.get_time - l_start));
RETURN row_cnt;
EXCEPTION
WHEN OTHERS
THEN
IF (SQLCODE = -942)
THEN
DBMS_OUTPUT.put_line ('Please check the table_name');
ELSE
RAISE;
END IF;
END;
It looks like you just aren't escaping the quotes that are included in the query string, so you're calling it as
tab_to_flat('SELECT i.row_id, translate(i.x_notes_txt, chr(10)||chr(13)||'|' , ' '),...')
The single quote around the | you're concatenating after the chr(13) is the immediate problem, but there are others. You could either go through and carefully double-up every quote inside the string:
tab_to_flat('SELECT i.row_id, translate(i.x_notes_txt, chr(10)||chr(13)||''|'' , '' ''),...')
... or more readibly use the quoted value syntax:
tab_to_flat(q'[SELECT i.row_id, translate(i.x_notes_txt, chr(10)||chr(13)||'|' , ' '),...]')
... where the q'[ ... ]' enclose your original string and allow you to use single quote marks without having to escape them. You just need to be sure that the actual query doesn't contain [ or ], or pick different delimiters if it does.
This says nothing about whether what you're doing is a good approach and if you can find a better way to approach your problem, and doesn't address SQL injection etc.; this is just to fix the problem with what you're currently calling and how you're doing it.
What you can do is to use am oracle cursor like this :
FUNCTION SELECT_FROM_MY_TABLE(v_QUERY_TO_BE_EXECUTED VARCHAR2)
RETURN SYS_REFCURSOR
IS
c_my_cursor SYS_REFCURSOR;
BEGIN
OPEN c_my_cursor FOR
v_QUERY_TO_BE_EXECUTED -- When working with Ref Cursors, open-for can be used directly, instead of execute immediate.
RETURN c_my_cursor;
END SELECT_FROM_MY_TABLE;
So basically you have a function, that returns a cursor which contains the information from your query. When you use the cursor, you do it like this :
PROCEDURE procedure_use_cursor
IS
c_my_cursor SYS_REFCURSOR;
r_my_table_row my_table%ROWTYPE;
BEGIN
c_my_cursor := SELECT_FROM_MY_TABLE;
LOOP
FETCH c_my_cursor INTO r_my_table_row;
EXIT WHEN c_my_cursor%NOTFOUND ;
-- do what you want with r_my_table_row
END LOOP;
END procedure_use_cursor;

How to change the Oracle Sequence using loop?

Hope someone can help. When I tried to insert something into a table it give me error saying the primary key is already existed. So I need to reset my sequence so that it is always max(id)+1.
The table is called 'People' with 2 columns (ID, Name). The sequence is called SEQ.
I am thinking of doing a loop. To run select SEQ.nextval from dual for n times. this n= max(id)-SEQ.currval
Wwill this work? and how Can I put it into the syntax?
Thanks a lot.
declare
l_MaxVal pls_integer;
l_Currval pls_integer default - 1;
begin
select max(id)
into l_MaxVal
from people;
while l_Currval < l_Maxval
loop
select my_seq.nextval
into l_Currval
from dual;
end loop;
end;
If this is a one off, you can use the alter sequence
alter sequence sequenceName increment by val ;
whereas val is +1 to the maximum
then call get nextVal, then set the increment back to 1.
I threw the below together to show you how it can be done without looping.
create sequence changeValue start with 18 increment by 1 nocache ;
select changeValue.nextval from dual ;
/
NEXTVAL
----------------------
18
set serveroutput on
declare
maxVal number := 24 ;
curVal number ;
diffVal number ;
incrementVal number ;
procedure alterSequence(seqName in varchar2, incVal in number) as
s varchar2(500);
begin
s := 'alter sequence ' || seqName || ' increment by ' || incVal ;
dbms_output.put_line(s);
execute immediate s;
end alterSequence;
begin
--(done in 11gr2 so if in earlier version select into)
curVal := changeValue.currval ;
dbms_output.put_line('curValue=>' || curVal );
diffVal := maxVal - curVal ;
dbms_output.put_line('diffVal=>' || diffVal );
alterSequence ( 'changeValue' , diffVal + 1 );
incrementVal := changeValue.nextval ;
dbms_output.put_line('incrementVal=>' || incrementVal );
alterSequence ( 'changeValue' , 1 );
curVal := changeValue.currval ;
dbms_output.put_line('curValue=>' || curVal );
end ;
/
curValue=>18
diffVal=>6
alter sequence changeValue increment by 7
incrementVal=>25
alter sequence changeValue increment by 1
curValue=>25
or better yet, as #Dave suggests, just drop and recreate the sequence with the acceptable Start With value.
With this one you can synchronize the sequence whatever it is forward or behind the max of the ID.
Just need to change the parameters in the final of the code.
declare
procedure SYNC_SEQUENCE
( P_IN_SEQ in varchar2
, P_IN_TABLE in varchar2
, P_IN_ID in varchar2
)
is
LV_MAXVAL number := 0;
LV_CURRVAL number := -1;
LV_AUX NUMBER;
begin
execute immediate
'select max('||P_IN_ID||')
from '||P_IN_TABLE into LV_MAXVAL;
execute immediate
'select '||P_IN_SEQ||'.nextval
from dual ' into LV_CURRVAL;
if LV_MAXVAL < LV_CURRVAL then
LV_AUX := (LV_CURRVAL - LV_MAXVAL);
execute immediate
'ALTER SEQUENCE '||P_IN_SEQ||' INCREMENT BY -'||LV_AUX;
execute immediate
'SELECT '||P_IN_SEQ||'.NEXTVAL FROM dual' INTO LV_AUX;
execute immediate
'ALTER SEQUENCE '||P_IN_SEQ||' INCREMENT BY 1';
end if;
while LV_CURRVAL < LV_MAXVAL
loop
execute immediate
'select '||P_IN_SEQ||'.nextval
from dual ' into LV_CURRVAL;
end loop;
end SYNC_SEQUENCE;
begin
SYNC_SEQUENCE('MY_SEQUENCIE_NAME','MY_TABLE_NAME','MY_FIELD_ID_NAME');
end;
/

Getting index of element in PL/SQL collection

Is there a built-in function to determine the (first) index of an element in a PL/SQL collection?
Something like
DECLARE
TYPE t_test IS TABLE OF VARCHAR2(1);
v_test t_test;
BEGIN
v_test := NEW t_test('A', 'B', 'A');
dbms_output.put_line( 'A: ' || get_index( v_test, 'A' ) );
dbms_output.put_line( 'B: ' || get_index( v_test, 'B' ) );
dbms_output.put_line( 'C: ' || get_index( v_test, 'C' ) );
END;
A: 1
B: 2
C:
I can use Associative Arrays, Nested Tables or Varrays, whatever necessary. If the same element exists more than once, then the index of the first occurrence is sufficient.
Otherwise I'd have to do something like
CREATE FUNCTION get_index ( in_test IN t_test, in_value IN VARCHAR2 )
RETURN PLS_INTEGER
AS
i PLS_INTEGER;
BEGIN
i := in_test.FIRST;
WHILE( i IS NOT NULL ) LOOP
IF( in_test(i) = in_value ) THEN
RETURN i;
END IF;
i := in_test.NEXT(i);
END LOOP;
RETURN NULL;
END get_index;
Not sure, if this really helps, or if you think it is more elegant:
create type t_test as table of varchar2(1);
/
DECLARE
--TYPE t_test IS TABLE OF VARCHAR2(1);
v_test t_test;
function get_index(q in t_test, c in varchar2) return number is
ind number;
begin
select min(rn) into ind from (
select column_value cv, rownum rn
from table(q)
)
where cv = c;
return ind;
end get_index;
BEGIN
v_test := NEW t_test('A', 'B', 'A');
dbms_output.put_line( 'A: ' || get_index( v_test, 'A' ) );
dbms_output.put_line( 'B: ' || get_index( v_test, 'B' ) );
dbms_output.put_line( 'C: ' || get_index( v_test, 'C' ) );
END;
/
show errors
drop type t_test;
I don't think there is a built-in function that searches a collection. However, if you know you will need to search a collection a lot, you could build an index. Adding element to the collection will be a bit more expensive, but looking for an element will be an O(1) operation (instead of O(n) for a brute force search). For example, you could use something like this:
SQL> DECLARE
2 TYPE t_test IS TABLE OF VARCHAR2(1);
3 TYPE t_test_r IS TABLE OF NUMBER INDEX BY VARCHAR2(1);
4
5 v_test t_test;
6 v_test_r t_test_r;
7
8 FUNCTION get_index(p_test_r t_test_r,
9 p_element VARCHAR2) RETURN NUMBER IS
10 BEGIN
11 RETURN p_test_r(p_element);
12 EXCEPTION
13 WHEN no_data_found THEN
14 RETURN NULL;
15 END get_index;
16
17 PROCEDURE add_element(p_test IN OUT t_test,
18 p_test_r IN OUT t_test_r,
19 p_element VARCHAR2) IS
20 BEGIN
21 p_test.extend;
22 p_test(p_test.count) := p_element;
23 p_test_r(p_element) := least(p_test.count,
24 nvl(get_index(p_test_r, p_element),
25 p_test.count));
26 END add_element;
27 BEGIN
28 v_test := NEW t_test();
29 add_element(v_test, v_test_r, 'A');
30 add_element(v_test, v_test_r, 'B');
31 add_element(v_test, v_test_r, 'A');
32 dbms_output.put_line('A: ' || get_index(v_test_r, 'A'));
33 dbms_output.put_line('B: ' || get_index(v_test_r, 'B'));
34 dbms_output.put_line('C: ' || get_index(v_test_r, 'C'));
35 END;
36 /
A: 1
B: 2
C:
PL/SQL procedure successfully completed
You could also define a record that contains both arrays and all functions/procedures to interact with arrays would use this record type.
When in doubt, consult the documentation ;) (here)
DECLARE
TYPE aa_type_int IS TABLE OF INTEGER INDEX BY PLS_INTEGER;
aa_int aa_type_int;
PROCEDURE print_first_and_last IS
BEGIN
DBMS_OUTPUT.PUT_LINE('FIRST = ' || aa_int.FIRST);
DBMS_OUTPUT.PUT_LINE('LAST = ' || aa_int.LAST);
END print_first_and_last;
BEGIN
aa_int(1) := 3;
aa_int(2) := 6;
aa_int(3) := 9;
aa_int(4) := 12;
DBMS_OUTPUT.PUT_LINE('Before deletions:');
print_first_and_last;
aa_int.DELETE(1);
aa_int.DELETE(4);
DBMS_OUTPUT.PUT_LINE('After deletions:');
print_first_and_last;
END;
/
Result:
Before deletions:
FIRST = 1
LAST = 4
After deletions:
FIRST = 2
LAST = 3

Convert comma separated string to array in PL/SQL

How do I convert a comma separated string to a array?
I have the input '1,2,3' , and I need to convert it into an array.
here is another easier option
select to_number(column_value) as IDs from xmltable('1,2,3,4,5');
Oracle provides the builtin function DBMS_UTILITY.COMMA_TO_TABLE.
Unfortunately, this one doesn't work with numbers:
SQL> declare
2 l_input varchar2(4000) := '1,2,3';
3 l_count binary_integer;
4 l_array dbms_utility.lname_array;
5 begin
6 dbms_utility.comma_to_table
7 ( list => l_input
8 , tablen => l_count
9 , tab => l_array
10 );
11 dbms_output.put_line(l_count);
12 for i in 1 .. l_count
13 loop
14 dbms_output.put_line
15 ( 'Element ' || to_char(i) ||
16 ' of array contains: ' ||
17 l_array(i)
18 );
19 end loop;
20 end;
21 /
declare
*
ERROR at line 1:
ORA-00931: missing identifier
ORA-06512: at "SYS.DBMS_UTILITY", line 132
ORA-06512: at "SYS.DBMS_UTILITY", line 164
ORA-06512: at "SYS.DBMS_UTILITY", line 218
ORA-06512: at line 6
But with a little trick to prefix the elements with an 'x', it works:
SQL> declare
2 l_input varchar2(4000) := '1,2,3';
3 l_count binary_integer;
4 l_array dbms_utility.lname_array;
5 begin
6 dbms_utility.comma_to_table
7 ( list => regexp_replace(l_input,'(^|,)','\1x')
8 , tablen => l_count
9 , tab => l_array
10 );
11 dbms_output.put_line(l_count);
12 for i in 1 .. l_count
13 loop
14 dbms_output.put_line
15 ( 'Element ' || to_char(i) ||
16 ' of array contains: ' ||
17 substr(l_array(i),2)
18 );
19 end loop;
20 end;
21 /
3
Element 1 of array contains: 1
Element 2 of array contains: 2
Element 3 of array contains: 3
PL/SQL procedure successfully completed.
Regards,
Rob.
We can never run out of alternatives of doing the same thing differently, right?
I recently found this is pretty handy:
DECLARE
BAR VARCHAR2 (200) := '1,2,3';
BEGIN
FOR FOO IN ( SELECT REGEXP_SUBSTR (BAR,
'[^,]+',
1,
LEVEL)
TXT
FROM DUAL
CONNECT BY REGEXP_SUBSTR (BAR,
'[^,]+',
1,
LEVEL)
IS NOT NULL)
LOOP
DBMS_OUTPUT.PUT_LINE (FOO.TXT);
END LOOP;
END;
Outputs:
1
2
3
I know Stack Overflow frowns on pasting URLs without explanations, but this particular page has a few really good options:
http://www.oratechinfo.co.uk/delimited_lists_to_collections.html
I particularly like this one, which converts the delimited list into a temporary table you can run queries against:
/* Create the output TYPE, here using a VARCHAR2(100) nested table type */
SQL> CREATE TYPE test_type AS TABLE OF VARCHAR2(100);
2 /
Type created.
/* Now, create the function.*/
SQL> CREATE OR REPLACE FUNCTION f_convert(p_list IN VARCHAR2)
2 RETURN test_type
3 AS
4 l_string VARCHAR2(32767) := p_list || ',';
5 l_comma_index PLS_INTEGER;
6 l_index PLS_INTEGER := 1;
7 l_tab test_type := test_type();
8 BEGIN
9 LOOP
10 l_comma_index := INSTR(l_string, ',', l_index);
11 EXIT WHEN l_comma_index = 0;
12 l_tab.EXTEND;
13 l_tab(l_tab.COUNT) := SUBSTR(l_string, l_index, l_comma_index - l_index);
14 l_index := l_comma_index + 1;
15 END LOOP;
16 RETURN l_tab;
17 END f_convert;
18 /
Function created.
/* Prove it works */
SQL> SELECT * FROM TABLE(f_convert('AAA,BBB,CCC,D'));
COLUMN_VALUE
--------------------------------------------------------------------------------
AAA
BBB
CCC
D
4 rows selected.
Simple Code
create or replace function get_token(text_is varchar2, token_in number, delim_is varchar2 := ';') return varchar2 is
text_ls varchar2(2000);
spos_ln number;
epos _ln number;
begin
text_ls := delim_is || text_is || rpad(delim_is, token_in, delim_is);
spos_ln := instr(text_ls, delim_is, 1, token_in);
epos_ln := instr(text_ls, delim_is, 1, token_in+1);
return substr(text_ls, spos_ln+1, epos_ln-spos_ln-1);
end get_token;
Yes, it is very frustrating that dbms_utility.comma_to_table only supports comma delimieted lists and then only when elements in the list are valid PL/SQL identifies (so numbers cause an error).
I have created a generic parsing package that will do what you need (pasted below). It is part of my "demo.zip" file, a repository of over 2000 files that support my training materials, all available at PL/SQL Obsession: www.toadworld.com/SF.
Regards,
Steven Feuerstein
www.plsqlchallenge.com
(daily PL/SQL quiz)
CREATE OR REPLACE PACKAGE parse
/*
Generalized delimited string parsing package
Author: Steven Feuerstein, steven#stevenfeuerstein.com
Latest version always available on PL/SQL Obsession:
www.ToadWorld.com/SF
Click on "Trainings, Seminars and Presentations" and
then download the demo.zip file.
Modification History
Date Change
10-APR-2009 Add support for nested list variations
Notes:
* This package does not validate correct use of delimiters.
It assumes valid construction of lists.
* Import the Q##PARSE.qut file into an installation of
Quest Code Tester 1.8.3 or higher in order to run
the regression test for this package.
*/
IS
SUBTYPE maxvarchar2_t IS VARCHAR2 (32767);
/*
Each of the collection types below correspond to (are returned by)
one of the parse functions.
items_tt - a simple list of strings
nested_items_tt - a list of lists of strings
named_nested_items_tt - a list of named lists of strings
This last type also demonstrates the power and elegance of string-indexed
collections. The name of the list of elements is the index value for
the "outer" collection.
*/
TYPE items_tt IS TABLE OF maxvarchar2_t
INDEX BY PLS_INTEGER;
TYPE nested_items_tt IS TABLE OF items_tt
INDEX BY PLS_INTEGER;
TYPE named_nested_items_tt IS TABLE OF items_tt
INDEX BY maxvarchar2_t;
/*
Parse lists with a single delimiter.
Example: a,b,c,d
Here is an example of using this function:
DECLARE
l_list parse.items_tt;
BEGIN
l_list := parse.string_to_list ('a,b,c,d', ',');
END;
*/
FUNCTION string_to_list (string_in IN VARCHAR2, delim_in IN VARCHAR2)
RETURN items_tt;
/*
Parse lists with nested delimiters.
Example: a,b,c,d|1,2,3|x,y,z
Here is an example of using this function:
DECLARE
l_list parse.nested_items_tt;
BEGIN
l_list := parse.string_to_list ('a,b,c,d|1,2,3,4', '|', ',');
END;
*/
FUNCTION string_to_list (string_in IN VARCHAR2
, outer_delim_in IN VARCHAR2
, inner_delim_in IN VARCHAR2
)
RETURN nested_items_tt;
/*
Parse named lists with nested delimiters.
Example: letters:a,b,c,d|numbers:1,2,3|names:steven,george
Here is an example of using this function:
DECLARE
l_list parse.named_nested_items_tt;
BEGIN
l_list := parse.string_to_list ('letters:a,b,c,d|numbers:1,2,3,4', '|', ':', ',');
END;
*/
FUNCTION string_to_list (string_in IN VARCHAR2
, outer_delim_in IN VARCHAR2
, name_delim_in IN VARCHAR2
, inner_delim_in IN VARCHAR2
)
RETURN named_nested_items_tt;
PROCEDURE display_list (string_in IN VARCHAR2
, delim_in IN VARCHAR2:= ','
);
PROCEDURE display_list (string_in IN VARCHAR2
, outer_delim_in IN VARCHAR2
, inner_delim_in IN VARCHAR2
);
PROCEDURE display_list (string_in IN VARCHAR2
, outer_delim_in IN VARCHAR2
, name_delim_in IN VARCHAR2
, inner_delim_in IN VARCHAR2
);
PROCEDURE show_variations;
/* Helper function for automated testing */
FUNCTION nested_eq (list1_in IN items_tt
, list2_in IN items_tt
, nulls_eq_in IN BOOLEAN
)
RETURN BOOLEAN;
END parse;
/
CREATE OR REPLACE PACKAGE BODY parse
IS
FUNCTION string_to_list (string_in IN VARCHAR2, delim_in IN VARCHAR2)
RETURN items_tt
IS
c_end_of_list CONSTANT PLS_INTEGER := -99;
l_item maxvarchar2_t;
l_startloc PLS_INTEGER := 1;
items_out items_tt;
PROCEDURE add_item (item_in IN VARCHAR2)
IS
BEGIN
IF item_in = delim_in
THEN
/* We don't put delimiters into the collection. */
NULL;
ELSE
items_out (items_out.COUNT + 1) := item_in;
END IF;
END;
PROCEDURE get_next_item (string_in IN VARCHAR2
, start_location_io IN OUT PLS_INTEGER
, item_out OUT VARCHAR2
)
IS
l_loc PLS_INTEGER;
BEGIN
l_loc := INSTR (string_in, delim_in, start_location_io);
IF l_loc = start_location_io
THEN
/* A null item (two consecutive delimiters) */
item_out := NULL;
ELSIF l_loc = 0
THEN
/* We are at the last item in the list. */
item_out := SUBSTR (string_in, start_location_io);
ELSE
/* Extract the element between the two positions. */
item_out :=
SUBSTR (string_in
, start_location_io
, l_loc - start_location_io
);
END IF;
IF l_loc = 0
THEN
/* If the delimiter was not found, send back indication
that we are at the end of the list. */
start_location_io := c_end_of_list;
ELSE
/* Move the starting point for the INSTR search forward. */
start_location_io := l_loc + 1;
END IF;
END get_next_item;
BEGIN
IF string_in IS NULL OR delim_in IS NULL
THEN
/* Nothing to do except pass back the empty collection. */
NULL;
ELSE
LOOP
get_next_item (string_in, l_startloc, l_item);
add_item (l_item);
EXIT WHEN l_startloc = c_end_of_list;
END LOOP;
END IF;
RETURN items_out;
END string_to_list;
FUNCTION string_to_list (string_in IN VARCHAR2
, outer_delim_in IN VARCHAR2
, inner_delim_in IN VARCHAR2
)
RETURN nested_items_tt
IS
l_elements items_tt;
l_return nested_items_tt;
BEGIN
/* Separate out the different lists. */
l_elements := string_to_list (string_in, outer_delim_in);
/* For each list, parse out the separate items
and add them to the end of the list of items
for that list. */
FOR indx IN 1 .. l_elements.COUNT
LOOP
l_return (l_return.COUNT + 1) :=
string_to_list (l_elements (indx), inner_delim_in);
END LOOP;
RETURN l_return;
END string_to_list;
FUNCTION string_to_list (string_in IN VARCHAR2
, outer_delim_in IN VARCHAR2
, name_delim_in IN VARCHAR2
, inner_delim_in IN VARCHAR2
)
RETURN named_nested_items_tt
IS
c_name_position constant pls_integer := 1;
c_items_position constant pls_integer := 2;
l_elements items_tt;
l_name_and_values items_tt;
l_return named_nested_items_tt;
BEGIN
/* Separate out the different lists. */
l_elements := string_to_list (string_in, outer_delim_in);
FOR indx IN 1 .. l_elements.COUNT
LOOP
/* Extract the name and the list of items that go with
the name. This collection always has just two elements:
index 1 - the name
index 2 - the list of values
*/
l_name_and_values :=
string_to_list (l_elements (indx), name_delim_in);
/*
Use the name as the index value for this list.
*/
l_return (l_name_and_values (c_name_position)) :=
string_to_list (l_name_and_values (c_items_position), inner_delim_in);
END LOOP;
RETURN l_return;
END string_to_list;
PROCEDURE display_list (string_in IN VARCHAR2
, delim_in IN VARCHAR2:= ','
)
IS
l_items items_tt;
BEGIN
DBMS_OUTPUT.put_line (
'Parse "' || string_in || '" using "' || delim_in || '"'
);
l_items := string_to_list (string_in, delim_in);
FOR indx IN 1 .. l_items.COUNT
LOOP
DBMS_OUTPUT.put_line ('> ' || indx || ' = ' || l_items (indx));
END LOOP;
END display_list;
PROCEDURE display_list (string_in IN VARCHAR2
, outer_delim_in IN VARCHAR2
, inner_delim_in IN VARCHAR2
)
IS
l_items nested_items_tt;
BEGIN
DBMS_OUTPUT.put_line( 'Parse "'
|| string_in
|| '" using "'
|| outer_delim_in
|| '-'
|| inner_delim_in
|| '"');
l_items := string_to_list (string_in, outer_delim_in, inner_delim_in);
FOR outer_index IN 1 .. l_items.COUNT
LOOP
DBMS_OUTPUT.put_line( 'List '
|| outer_index
|| ' contains '
|| l_items (outer_index).COUNT
|| ' elements');
FOR inner_index IN 1 .. l_items (outer_index).COUNT
LOOP
DBMS_OUTPUT.put_line( '> Value '
|| inner_index
|| ' = '
|| l_items (outer_index) (inner_index));
END LOOP;
END LOOP;
END display_list;
PROCEDURE display_list (string_in IN VARCHAR2
, outer_delim_in IN VARCHAR2
, name_delim_in IN VARCHAR2
, inner_delim_in IN VARCHAR2
)
IS
l_items named_nested_items_tt;
l_index maxvarchar2_t;
BEGIN
DBMS_OUTPUT.put_line( 'Parse "'
|| string_in
|| '" using "'
|| outer_delim_in
|| '-'
|| name_delim_in
|| '-'
|| inner_delim_in
|| '"');
l_items :=
string_to_list (string_in
, outer_delim_in
, name_delim_in
, inner_delim_in
);
l_index := l_items.FIRST;
WHILE (l_index IS NOT NULL)
LOOP
DBMS_OUTPUT.put_line( 'List "'
|| l_index
|| '" contains '
|| l_items (l_index).COUNT
|| ' elements');
FOR inner_index IN 1 .. l_items (l_index).COUNT
LOOP
DBMS_OUTPUT.put_line( '> Value '
|| inner_index
|| ' = '
|| l_items (l_index) (inner_index));
END LOOP;
l_index := l_items.NEXT (l_index);
END LOOP;
END display_list;
PROCEDURE show_variations
IS
PROCEDURE show_header (title_in IN VARCHAR2)
IS
BEGIN
DBMS_OUTPUT.put_line (RPAD ('=', 60, '='));
DBMS_OUTPUT.put_line (title_in);
DBMS_OUTPUT.put_line (RPAD ('=', 60, '='));
END show_header;
BEGIN
show_header ('Single Delimiter Lists');
display_list ('a,b,c');
display_list ('a;b;c', ';');
display_list ('a,,b,c');
display_list (',,b,c,,');
show_header ('Nested Lists');
display_list ('a,b,c,d|1,2,3|x,y,z', '|', ',');
show_header ('Named, Nested Lists');
display_list ('letters:a,b,c,d|numbers:1,2,3|names:steven,george'
, '|'
, ':'
, ','
);
END;
FUNCTION nested_eq (list1_in IN items_tt
, list2_in IN items_tt
, nulls_eq_in IN BOOLEAN
)
RETURN BOOLEAN
IS
l_return BOOLEAN := list1_in.COUNT = list2_in.COUNT;
l_index PLS_INTEGER := 1;
BEGIN
WHILE (l_return AND l_index IS NOT NULL)
LOOP
l_return := list1_in (l_index) = list2_in (l_index);
l_index := list1_in.NEXT (l_index);
END LOOP;
RETURN l_return;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
RETURN FALSE;
END nested_eq;
END;
/
Using a pipelined table function:
SQL> CREATE OR REPLACE TYPE test_type
2 AS
3 TABLE OF VARCHAR2(100)
4 /
Type created.
SQL> CREATE OR REPLACE FUNCTION comma_to_table(
2 p_list IN VARCHAR2)
3 RETURN test_type PIPELINED
4 AS
5 l_string LONG := p_list || ',';
6 l_comma_index PLS_INTEGER;
7 l_index PLS_INTEGER := 1;
8 BEGIN
9 LOOP
10 l_comma_index := INSTR(l_string, ',', l_index);
11 EXIT
12 WHEN l_comma_index = 0;
13 PIPE ROW ( TRIM(SUBSTR(l_string, l_index, l_comma_index - l_index)));
14 l_index := l_comma_index + 1;
15 END LOOP;
16 RETURN;
17 END comma_to_table;
18 /
Function created.
Let's see the output:
SQL> SELECT *
2 FROM TABLE(comma_to_table('12 3,456,,,,,abc,def'))
3 /
COLUMN_VALUE
------------------------------------------------------------------------------
12 3
456
abc
def
8 rows selected.
SQL>
A quick search on my BBDD took me to a function called split:
create or replace function split
(
p_list varchar2,
p_del varchar2 := ','
)
return split_tbl pipelined
is
l_idx pls_integer;
l_list varchar2(32767) := p_list;AA
l_value varchar2(32767);
begin
loop
l_idx := instr(l_list,p_del);
if l_idx > 0 then
pipe row(substr(l_list,1,l_idx-1));
l_list := substr(l_list,l_idx+length(p_del));
else
pipe row(l_list);
exit;
end if;
end loop;
return;
end split;
I don't know if it'll be of use, but we found it here...
I was looking for a similar solution where I had multi-byte characters (hyphen, whitespace, underscore) in comma separated lists. So dbms_utility.comma_to_table didn't work for me.
declare
curr_val varchar2 (255 byte);
input_str varchar2 (255 byte);
remaining_str varchar2 (255 byte);
begin
remaining_str := input_str || ',dummy'; -- this value won't output
while (regexp_like (remaining_str, '.+,.+'))
loop
curr_val := substr (remaining_str, 1, instr (remaining_str, ',') - 1);
remaining_str = substr (remaining_str, instr (remaining_str, ',') + 1);
dbms_output.put_line (curr_val);
end loop;
end;
This is not an expert answer so I hope someone would improve this answer.
TYPE string_aa IS TABLE OF VARCHAR2(32767) INDEX BY PLS_INTEGER;
FUNCTION string_to_list(p_string_in IN VARCHAR2)
RETURN string_aa
IS
TYPE ref_cursor IS ref cursor;
l_cur ref_cursor;
l_strlist string_aa;
l_x PLS_INTEGER;
BEGIN
IF p_string_in IS NOT NULL THEN
OPEN l_cur FOR
SELECT regexp_substr(p_string_in,'[^,]+', 1, level) FROM dual
CONNECT BY regexp_substr(p_string_in, '[^,]+', 1, level) IS NOT NULL;
l_x := 1;
LOOP
FETCH l_cur INTO l_strlist(l_x);
EXIT WHEN l_cur%notfound;
-- excludes NULL items e.g. 1,2,,,,5,6,7
l_x := l_x + 1;
END LOOP;
END IF;
RETURN l_strlist;
END string_to_list;
I just want to write more clearly Richard and Mike's answers.
Method 1: VARRAY USAGE
DECLARE
TYPE v_array_type IS VARRAY (10) OF NUMBER;
var v_array_type;
begin
select to_number(column_value) BULK COLLECT into var from
xmltable('1,2,3,4,5,6,7,8,9,10');
for i in 1..var.count LOOP
dbms_output.put_line(var(i));
END LOOP;
end;
But in this method you must declare array size. If you want more dynamic method you could use Mike's method.
METHOD 2:
begin
FOR obj IN (SELECT TO_NUMBER(column_value) as ID FROM
xmltable('1,2,3,4,5,6,7,8,9,10,11')) LOOP
dbms_output.put_line(obj.ID);
END LOOP;
end;
you can use oracle method
declare
myarray apex_application_global.vc_arr2;
begin
myarray = apex_util.string_to_table('1,2,3', ',');//you can use any symbols instead of ',' for split
for i in 1..myarray.COUNT
loop
dbms_output.put_line(myarray(i));
end loop;
Another possibility is:
create or replace FUNCTION getNth (
input varchar2,
nth number
) RETURN varchar2 AS
nthVal varchar2(80);
BEGIN
with candidates (s,e,n) as (
select 1, instr(input,',',1), 1 from dual
union all
select e+1, instr(input,',',e+1), n+1
from candidates where e > 0)
select substr(input,s,case when e > 0 then e-s else length(input) end)
into nthVal
from candidates where n=nth;
return nthVal;
END getNth;
It's a little too expensive to run, as it computes the complete split every time the caller asks for one of the items in there...
The solution presented below by Stewart Ashton in this link is pretty handy.
He eliminates the need for the value list to be integer, so you can use string list.
In the WITH clause, he surrounds the values with single quotes then converts it to a table having a single column of type VARCHAR2.
https://stewashton.wordpress.com
with data as (
select '"'||replace(:txt, ',', '","')||'"' str from dual
)
select xmlcast(column_value as varchar2(4000)) subs
from data, xmltable(str);
declare
seprator varchar2(1):=',';
dosweeklist varchar2(4000):='a,b,c';
begin
for i in (SELECT SUBSTR(dosweeklist,
case when level=1 then 1 else INSTR(dosweeklist,seprator,1,LEVEL-1)+1 end,
NVL(NULLIF(INSTR(dosweeklist,seprator,1,LEVEL),0),length(dosweeklist)+1) - case when level=1 then 1 else INSTR(dosweeklist,seprator,1,LEVEL-1)+1 end) dat
FROM dual
CONNECT BY LEVEL <= LENGTH(dosweeklist) - LENGTH(REPLACE(dosweeklist,seprator,'')) +1)
loop
dbms_output.put_line(i.dat);
end loop;
end;
/
so select query only in for loop can do the trick, by replacing dosweeklist as your delimited string and seprator as your delimited character.
Lets see output
a
b
c
declare
v_str varchar2(100) := '1,2,3,4,6,7,8,9,0,';
v_str1 varchar2(100);
v_comma_pos number := 0;
v_start_pos number := 1;
begin
loop
v_comma_pos := instr(v_str,',',v_start_pos);
v_str1 := substr(v_str,v_start_pos,(v_comma_pos - v_start_pos));
dbms_output.put_line(v_str1);
if v_comma_pos = 0 then
v_str1 := substr(v_str,v_start_pos);
dbms_output.put_line(v_str1);
exit;
end if;
v_start_pos := v_comma_pos + 1;
if v_comma_pos = 0 then
exit;
end if;
end loop;
end;
You can use Replace Function to replace comma easily.
To Do this-
The syntax for the REPLACE function in SQL Server (Transact-SQL) is:
REPLACE( string, string_to_replace, replacement_string )
Parameters or Arguments
string : The source string from which a sequence of characters will be replaced by another set of characters.
string_to_replace : The string that will be searched for in string1.
replacement_string : The replacement string. All occurrences of string_to_replace will be replaced with replacement_string in string1.
Note :
The REPLACE function performs a replacement that is not
case-sensitive. So all occurrences of string_to_replace will be
replaced with replacement_string regardless of the case of
string_to_replace or replacement_string
For Example :
SELECT REPLACE('Kapil,raj,chouhan', ',' , ' ') from DUAL;
Result : Kapil raj chouhan
SELECT REPLACE('I Live In India', ' ' , '-') from DUAL;
Result : I-Live-In-India
SELECT REPLACE('facebook.com', 'face' , 'friends') from DUAL;
Result : friendsbook.com
I Hope it will be usefull for you.

Resources