Splitting string in oracle - fastest way - sql

Does anybody know how to split string separated by ';' in a trigger, faster than this code:
SELECT regexp_substr('asd;asaaaad;dd;', '([^;]*)(;)', 1, level, null, 1)
BULK COLLECT INTO array_TREATMENT_TR_CD
FROM DUAL
CONNECT BY LEVEL < REGEXP_COUNT('asd;asaaaad;dd;', '[;]') + 1 ;

Regexp functions are usually way slower than standard db functions. When I run your code 100.000 times it takes around 12 seconds (on my db, YMMV).
Then I created this small test:
declare
type t_vc2 is table of varchar2(250) index by pls_integer;
procedure split_str(
p_str1 in out nocopy varchar2
, p_str2 out varchar2
, p_separator in varchar2 default ';'
)
is
l_pos pls_integer;
begin
l_pos := instr(p_str1, p_separator);
if nvl(l_pos, 0) = 0
then
p_str2 := p_str1;
p_str1 := null;
else
p_str2 := substr(p_str1, 1, l_pos-1);
p_str1 := substr(p_str1, l_pos+1);
end if;
end split_str;
begin
for l_i in 1 .. 1000000
loop
declare
array_TREATMENT_TR_CD t_vc2;
l_input varchar2(250);
l_return varchar2(250);
begin
l_input := 'asd;asaaaad;dd;';
loop
split_str(l_input, l_return);
if l_return is not null
then
array_TREATMENT_TR_CD(array_TREATMENT_TR_CD.count+1) := l_return;
end if;
exit when l_return is null;
end loop;
end;
end loop;
end;
/
This parses your example string 100.000 times to an array with standard db functions. It takes around 0.5 seconds. That's a significant improvement if "real time" is what you aim for.

Related

Return SQL Array from string [duplicate]

I'd like to create an in-memory array variable that can be used in my PL/SQL code. I can't find any collections in Oracle PL/SQL that uses pure memory, they all seem to be associated with tables. I'm looking to do something like this in my PL/SQL (C# syntax):
string[] arrayvalues = new string[3] {"Matt", "Joanne", "Robert"};
Edit:
Oracle: 9i
You can use VARRAY for a fixed-size array:
declare
type array_t is varray(3) of varchar2(10);
array array_t := array_t('Matt', 'Joanne', 'Robert');
begin
for i in 1..array.count loop
dbms_output.put_line(array(i));
end loop;
end;
Or TABLE for an unbounded array:
...
type array_t is table of varchar2(10);
...
The word "table" here has nothing to do with database tables, confusingly. Both methods create in-memory arrays.
With either of these you need to both initialise and extend the collection before adding elements:
declare
type array_t is varray(3) of varchar2(10);
array array_t := array_t(); -- Initialise it
begin
for i in 1..3 loop
array.extend(); -- Extend it
array(i) := 'x';
end loop;
end;
The first index is 1 not 0.
You could just declare a DBMS_SQL.VARCHAR2_TABLE to hold an in-memory variable length array indexed by a BINARY_INTEGER:
DECLARE
name_array dbms_sql.varchar2_table;
BEGIN
name_array(1) := 'Tim';
name_array(2) := 'Daisy';
name_array(3) := 'Mike';
name_array(4) := 'Marsha';
--
FOR i IN name_array.FIRST .. name_array.LAST
LOOP
-- Do something
END LOOP;
END;
You could use an associative array (used to be called PL/SQL tables) as they are an in-memory array.
DECLARE
TYPE employee_arraytype IS TABLE OF employee%ROWTYPE
INDEX BY PLS_INTEGER;
employee_array employee_arraytype;
BEGIN
SELECT *
BULK COLLECT INTO employee_array
FROM employee
WHERE department = 10;
--
FOR i IN employee_array.FIRST .. employee_array.LAST
LOOP
-- Do something
END LOOP;
END;
The associative array can hold any make up of record types.
Hope it helps,
Ollie.
You can also use an oracle defined collection
DECLARE
arrayvalues sys.odcivarchar2list;
BEGIN
arrayvalues := sys.odcivarchar2list('Matt','Joanne','Robert');
FOR x IN ( SELECT m.column_value m_value
FROM table(arrayvalues) m )
LOOP
dbms_output.put_line (x.m_value||' is a good pal');
END LOOP;
END;
I would use in-memory array. But with the .COUNT improvement suggested by uziberia:
DECLARE
TYPE t_people IS TABLE OF varchar2(10) INDEX BY PLS_INTEGER;
arrayvalues t_people;
BEGIN
SELECT *
BULK COLLECT INTO arrayvalues
FROM (select 'Matt' m_value from dual union all
select 'Joanne' from dual union all
select 'Robert' from dual
)
;
--
FOR i IN 1 .. arrayvalues.COUNT
LOOP
dbms_output.put_line(arrayvalues(i)||' is my friend');
END LOOP;
END;
Another solution would be to use a Hashmap like #Jchomel did here.
NB:
With Oracle 12c you can even query arrays directly now!
Another solution is to use an Oracle Collection as a Hashmap:
declare
-- create a type for your "Array" - it can be of any kind, record might be useful
type hash_map is table of varchar2(1000) index by varchar2(30);
my_hmap hash_map ;
-- i will be your iterator: it must be of the index's type
i varchar2(30);
begin
my_hmap('a') := 'apple';
my_hmap('b') := 'box';
my_hmap('c') := 'crow';
-- then how you use it:
dbms_output.put_line (my_hmap('c')) ;
-- or to loop on every element - it's a "collection"
i := my_hmap.FIRST;
while (i is not null) loop
dbms_output.put_line(my_hmap(i));
i := my_hmap.NEXT(i);
end loop;
end;
Sample programs as follows and provided on link also https://oracle-concepts-learning.blogspot.com/
plsql table or associated array.
DECLARE
TYPE salary IS TABLE OF NUMBER INDEX BY VARCHAR2(20);
salary_list salary;
name VARCHAR2(20);
BEGIN
-- adding elements to the table
salary_list('Rajnish') := 62000; salary_list('Minakshi') := 75000;
salary_list('Martin') := 100000; salary_list('James') := 78000;
-- printing the table name := salary_list.FIRST; WHILE name IS NOT null
LOOP
dbms_output.put_line ('Salary of ' || name || ' is ' ||
TO_CHAR(salary_list(name)));
name := salary_list.NEXT(name);
END LOOP;
END;
/
Using varray is about the quickest way to duplicate the C# code that I have found without using a table.
Declare your public array type to be use in script
type t_array is varray(10) of varchar2(60);
This is the function you need to call - simply finds the values in the string passed in using a comma delimiter
function ConvertToArray(p_list IN VARCHAR2)
RETURN t_array
AS
myEmailArray t_array := t_array(); --init empty array
l_string varchar2(1000) := p_list || ','; - (list coming into function adding final comma)
l_comma_idx integer;
l_index integer := 1;
l_arr_idx integer := 1;
l_email varchar2(60);
BEGIN
LOOP
l_comma_idx := INSTR(l_string, ',', l_index);
EXIT WHEN l_comma_idx = 0;
l_email:= SUBSTR(l_string, l_index, l_comma_idx - l_index);
dbms_output.put_line(l_arr_idx || ' - ' || l_email);
myEmailArray.extend;
myEmailArray(l_arr_idx) := l_email;
l_index := l_comma_idx + 1;
l_arr_idx := l_arr_idx + 1;
END LOOP;
for i in 1..myEmailArray.count loop
dbms_output.put_line(myEmailArray(i));
end loop;
dbms_output.put_line('return count ' || myEmailArray.count);
RETURN myEmailArray;
--exception
--when others then
--do something
end ConvertToArray;
Finally Declare a local variable, call the function and loop through what is returned
l_array t_array;
l_Array := ConvertToArray('email1#gmail.com,email2#gmail.com,email3#gmail.com');
for idx in 1 .. l_array.count
loop
l_EmailTo := Trim(replace(l_arrayXX(idx),'"',''));
if nvl(l_EmailTo,'#') = '#' then
dbms_output.put_line('Empty: l_EmailTo:' || to_char(idx) || l_EmailTo);
else
dbms_output.put_line
( 'Email ' || to_char(idx) ||
' of array contains: ' ||
l_EmailTo
);
end if;
end loop;

Handle a very large string in pl/sql script

I am trying to run below code which reads the index definition for table A so that it can be created again after I delete/create that in this script. This script runs fine when the returned value(ddl) is small but in other environments where the value is large with 140K characters in one row this script fails with below mentioned error. Please note that I cannot use spool in this case due to some restrictions. Could someone help on how to resolve this issue or suggest some another approach?
Thanks in advance.
"An arithmetic, numeric, string, conversion, or constraint error
occurred. For example, this error occurs if an attempt is made to
assign the value NULL to a variable declared NOT NULL, or if an
attempt is made to assign an integer larger than 99 to a variable
declared NUMBER(2)."
SET SERVEROUTPUT ON;
DECLARE
my_cursor SYS_REFCURSOR;
TYPE clob_array IS VARRAY(15) OF CLOB;
index_array clob_array := clob_array();
v_clob CLOB;
--index_array SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST();
BEGIN
OPEN my_cursor FOR 'select replace(dbms_metadata.get_ddl (''INDEX'', index_name), ''"C",'', '''')
from user_indexes
where table_name = ''A''';
LOOP FETCH my_cursor INTO v_clob;
EXIT WHEN my_cursor%NOTFOUND;
index_array.extend;
index_array(index_array.count) := v_clob;
dbms_output.put_line(index_array(index_array.count));
END LOOP;
CLOSE my_cursor;
END;
/
I simulated this issue you are getting this error because of the dbms_output.put_line which displays the output.Try switching to UTL_FILE at the server side OR Try for any alternatives
By the way, the code can be simplified to:
declare
type clob_array is table of clob;
index_array clob_array := clob_array();
begin
for r in (
select replace(dbms_metadata.get_ddl('INDEX', index_name), '"C",') as index_ddl
from user_indexes
where table_name = 'A'
)
loop
index_array.extend;
index_array(index_array.count) := r.index_ddl;
dbms_output.put_line(substr(index_array(index_array.count), 1, 32767));
end loop;
end;
I used substr() to limit the value passed to dbms_output.put_line to its documented limit. You could probably work around it by splitting the text into smaller chunks, and maybe finding the position of the last blank space before position 32767 in order to avoid splitting a word.
Here's what I came up with:
declare
type clob_array is table of clob;
index_array clob_array := clob_array();
procedure put_line
( p_text clob )
is
max_len constant simple_integer := 32767;
line varchar2(max_len);
remainder clob := p_text;
begin
while dbms_lob.getlength(remainder) > max_len loop
line := dbms_lob.substr(remainder,max_len);
line := substr(line, 1, instr(line, ' ', -1));
remainder := substr(remainder, length(line) +1);
dbms_output.put_line(line);
end loop;
if length(trim(remainder)) > 0 then
dbms_output.put_line(remainder);
end if;
end put_line;
begin
for r in (
select replace(dbms_metadata.get_ddl('INDEX', index_name), '"C",') as index_ddl
from user_indexes
where table_name = 'A'
)
loop
index_array.extend;
index_array(index_array.count) := r.index_ddl;
put_line(index_array(index_array.count));
end loop;
end;

How to export result of SQL query into csv-style formatted string, that will be later saved into clob in Oracle?

I have the following problem:
I have a table Source_Data(actually, quite many different source tables), and I need to export some data from them into Result_Table. For each source table I have some sql that returns the data, and it needs to be transformed into csv-like string.
For example, Source_Data1 table has PK - ID, some foreign keys - A_ID, B_ID, CREATION_DATE date, and some other columns.
I need to export the result of SQL query that selects data from Source_Data1 table, it usually returns the primary key, some foreign keys which are not null and a date which is also not null, and transform it into cvs-like string, which should be saved later in some other table as a clob.
CREATE TABLE Source_Data1 (
ID NUMBER(3) NOT NULL,
A_ID NUMBER(10) NOT NULL,
B_ID NUMBER(10) NOT NULL,
CREATION_DATE DATE NOT NULL,
some other columns );
and I have SQL query:
select ID, A_ID, B_ID, CREATION_DATE
from Source_Data1
where <expression>
Based on this query, I need to generate string like this:
'ID,A_ID,B_ID,CREATION_DATE
1,200,10,2018-03-01 00:00:00.0
7,202,11,2018-03-02 00:00:00.0
8,205,11,2018-03-02 00:00:00.0'
and I need to save it into the Result table as a clob:
Insert into Result_Table (ID, SOURCE_NAME, DATA) values
(result_seq.nextval, 'Source_Data1', result of sqlquery 1);
And this needs to be done for many source tables.
I was wondering if there is some general way to do it? Because for each source table I have its own sql query, different set of selected columns, and generating this csv string manually looks not very nice.
Thank you for your advice.
Step 1 should be to download SQLcl, you can easily save the output of SQL select statements in CSV format via "SET SQLFORMAT CSV" and SPOOL, it does the work for you. So you can use that to loop through a bunch of table names / select statements, spool and save the output of each to a directory via a shell script.
Next, here's a package that has a bunch of file utilities that all work with oracle DIRECTORY objects and the files within them. With this and some PL/SQL, you could easily pull those files that you've saved off into a table. There may be easier ways to do what you're trying to do, and if there is, I look forward to hearing about them for others. It's kind of a big problem to solve.
1) file_into_blob - To get the file into the database
2) convert_blob_to_clob - To convert to clob
3) You can then insert that into your table.
Package spec
Create or replace package fileutils as
--
-- This procedure deletes a file, and depends on an Oracle DIRECTORY object being passed
--
Procedure delete_os_file (i_directory varchar2, i_filename varchar2);
--
-- This procedure moves and optionally renames a file,
-- and depends on an Oracle DIRECTORY object being passed
--
Procedure move_os_file ( i_source_directory in varchar2, i_source_file in varchar2, i_target_directory in varchar2, i_target_file in varchar2);
--
-- This procedure takes a blob variable and writes it to a file,
-- and depends on an Oracle DIRECTORY object being passed
--
Procedure blob_into_file (i_directory in varchar2, i_file_name in varchar2, i_blob in blob);
--
-- This procedure takes a file and uploads it into a blob variable
-- and depends on an Oracle DIRECTORY object being passed
--
Procedure file_into_blob(i_directory in varchar2, i_file_name in varchar2, o_blob out blob);
--
-- This procedure converts a clob to a blob
--
Procedure convert_clob_to_blob (i_clob in clob, o_blob out blob);
--
-- This procedure converts a blob to a clob
--
Procedure convert_blob_to_clob (i_blob in blob, o_clob out clob);
--
-- This one checks for file existence without Java
--
Function file_exists (i_directory in varchar2, i_filename in varchar2) return boolean;
--
-- Returns the basename of a filename
-- Works with Windows and UNIX pathnames
--
Function basename (i_filename in varchar2) return varchar2;
--
-- This takes a Base64 string and converts it to a binary BLOB
--
Procedure base64_string_to_blob (i_clob in clob, o_blob out blob);
Function base64_string_to_blob (i_clob in clob) return blob;
--
-- This takes a binary BLOB and converts it to a Base64 string
--
Procedure blob_to_base64_string (i_blob in blob, o_clob out clob);
Function blob_to_base64_string (i_blob in blob) return clob;
End fileutils;
/
Show error;
Package body
Set define off;
Create or replace package body fileutils as
Procedure delete_os_file (i_directory varchar2, i_filename varchar2)
is
Begin
utl_file.fremove(i_directory,i_filename);
End;
Procedure move_os_file
(
i_source_directory in varchar2,
i_source_file in varchar2,
i_target_directory in varchar2,
i_target_file in varchar2
)
is
srcdir varchar2(255) := upper(i_source_directory);
tgtdir varchar2(255) := upper(i_target_directory);
begin
--
-- NOTE: If you're getting the all-too-familiar
-- ORA-29292: file rename operation failed
-- and you're SURE that your directory names are correct,
-- and you're SURE that your privileges are correct, both at the
-- OS level, and within the database, there's one last thing that
-- can get you. I learned the hard way that this command will NOT
-- work successfully renaming a file from one filesystem to another,
-- at least when those filesystems are NFS mounted. That is all.
--
utl_file.frename(srcdir,i_source_file,tgtdir,i_target_file,TRUE);
end move_os_file;
Procedure blob_into_file (i_directory in varchar2, i_file_name in varchar2, i_blob in blob)
is
l_file utl_file.file_type;
l_buffer raw(32767);
l_amount binary_integer := 32767;
l_pos integer := 1;
i_blob_len integer;
Begin
i_blob_len := dbms_lob.getlength(i_blob);
l_pos:= 1;
-- Open the destination file.
l_file := utl_file.fopen(i_directory,i_file_name,'wb', 32767);
-- Read chunks of the BLOB and write them to the file
-- until complete.
while l_pos < i_blob_len loop
dbms_lob.read(i_blob, l_amount, l_pos, l_buffer);
utl_file.put_raw(l_file, l_buffer, TRUE);
l_pos := l_pos + l_amount;
end loop;
-- Close the file.
utl_file.fclose(l_file);
End blob_into_file;
Procedure file_into_blob(i_directory in varchar2, i_file_name in varchar2, o_blob out blob)
is
src_loc bfile := bfilename(i_directory, i_file_name);
Begin
-- Initialize the dest blob
o_blob := empty_blob();
-- Open source binary file from OS
dbms_lob.open(src_loc, dbms_lob.lob_readonly);
-- Create temporary LOB object
dbms_lob.createtemporary(
lob_loc => o_blob
, cache => true
, dur => dbms_lob.session
);
-- Open temporary lob
dbms_lob.open(o_blob, dbms_lob.lob_readwrite);
-- Load binary file into temporary LOB
dbms_lob.loadfromfile(
dest_lob => o_blob
, src_lob => src_loc
, amount => dbms_lob.getLength(src_loc));
-- Close lob objects
dbms_lob.close(o_blob);
dbms_lob.close(src_loc);
End file_into_blob;
Function basename (i_filename in varchar2) return varchar2
is
v_basename varchar2(1024);
Begin
--
-- If the regex's below don't match, then it's already at its base name
-- Return what was passed.
--
v_basename := i_filename;
if regexp_like(i_filename,'^.*\\') then
dbms_output.put_line('This is a Windows file');
v_basename := regexp_substr(i_filename,'[^\]*$');
dbms_output.put_line('Basename is : '||v_basename);
end if;
if regexp_like(i_filename,'^/') then
dbms_output.put_line('This is a UNIX file');
v_basename := regexp_substr(i_filename,'[^/]*$');
dbms_output.put_line('Basename is : '||v_basename);
end if;
return v_basename;
End basename;
Function file_exists (i_directory in varchar2, i_filename in varchar2) return boolean
is
v_exists boolean;
v_file_length number;
v_block_size number;
Begin
utl_file.fgetattr(upper(i_directory), i_filename, v_exists, v_file_length, v_block_size);
if (v_exists) then
dbms_output.put_line('File '||i_filename||' exists, '||v_file_length||' bytes');
else
dbms_output.put_line('File '||i_filename||' does not exist');
end if;
return v_exists;
end file_exists;
Procedure convert_clob_to_blob (i_clob in clob, o_blob out blob)
is
v_in pls_Integer := 1;
v_out pls_Integer := 1;
v_lang pls_Integer := 0;
v_warning pls_Integer := 0;
Begin
dbms_lob.createtemporary(o_blob,TRUE);
dbms_lob.converttoblob(o_blob,i_clob,DBMS_lob.getlength(i_clob),v_in,v_out,dbms_lob.default_csid,v_lang,v_warning);
End convert_clob_to_blob;
Procedure convert_blob_to_clob (i_blob in blob, o_clob out clob)
is
v_in pls_Integer := 1;
v_out pls_Integer := 1;
v_lang pls_Integer := 0;
v_warning pls_Integer := 0;
Begin
dbms_lob.createtemporary(o_clob,TRUE);
dbms_lob.converttoclob(o_clob,i_blob,DBMS_lob.getlength(i_blob),v_in,v_out,dbms_lob.default_csid,v_lang,v_warning);
End convert_blob_to_clob;
Procedure blob_to_base64_string (i_blob in blob, o_clob out clob)
is
v_out_cl clob;
file_len pls_integer;
modulo pls_integer;
pieces pls_integer;
amt binary_integer := 23808;
buf raw (32767);
buf_tx varchar2(32767);
pos pls_integer := 1;
filepos pls_integer := 1;
counter pls_integer := 1;
Begin
dbms_lob.createtemporary (v_out_cl, true, dbms_lob.call);
file_len := dbms_lob.getlength (i_blob);
modulo := mod (file_len, amt);
pieces := trunc (file_len / amt);
while (counter <= pieces) loop
dbms_lob.read (i_blob, amt, filepos, buf);
buf_tx:=utl_raw.cast_to_varchar2 (utl_encode.base64_encode (buf));
dbms_lob.writeappend (v_out_cl,length(buf_tx),buf_tx);
filepos := counter * amt + 1;
counter := counter + 1;
end loop;
if (modulo <> 0) THEN
dbms_lob.read (i_blob, modulo, filepos, buf);
buf_tx:=utl_raw.cast_to_varchar2 (utl_encode.base64_encode (buf));
dbms_lob.writeappend (v_out_cl,length(buf_tx),buf_tx);
end if;
o_clob := v_out_cl;
End blob_to_base64_string;
Function blob_to_base64_string (i_blob in blob) return clob
is
v_out_cl clob;
file_len pls_integer;
modulo pls_integer;
pieces pls_integer;
amt binary_integer := 23808;
buf raw (32767);
buf_tx varchar2(32767);
pos pls_integer := 1;
filepos pls_integer := 1;
counter pls_integer := 1;
Begin
dbms_lob.createtemporary (v_out_cl, true, dbms_lob.call);
file_len := dbms_lob.getlength (i_blob);
modulo := mod (file_len, amt);
pieces := trunc (file_len / amt);
while (counter <= pieces) loop
dbms_lob.read (i_blob, amt, filepos, buf);
buf_tx:=utl_raw.cast_to_varchar2 (utl_encode.base64_encode (buf));
dbms_lob.writeappend (v_out_cl,length(buf_tx),buf_tx);
filepos := counter * amt + 1;
counter := counter + 1;
end loop;
if (modulo <> 0) THEN
dbms_lob.read (i_blob, modulo, filepos, buf);
buf_tx:=utl_raw.cast_to_varchar2 (utl_encode.base64_encode (buf));
dbms_lob.writeappend (v_out_cl,length(buf_tx),buf_tx);
end if;
return v_out_cl;
End blob_to_base64_string;
Procedure base64_string_to_blob (i_clob in clob, o_blob out blob)
is
v_out_bl blob;
clob_size number;
pos number;
charBuff varchar2(32767);
dBuffer RAW(32767);
v_readSize_nr number;
v_line_nr number;
begin
dbms_lob.createTemporary (v_out_bl, true, dbms_lob.call);
v_line_nr:=greatest(65, instr(i_clob,chr(10)), instr(i_clob,chr(13)));
v_readSize_nr:= floor(32767/v_line_nr)*v_line_nr;
clob_size := dbms_lob.getLength(i_clob);
pos := 1;
while (pos < clob_size) loop
dbms_lob.read (i_clob, v_readSize_nr, pos, charBuff);
dBuffer := UTL_ENCODE.base64_decode (utl_raw.cast_to_raw(charBuff));
dbms_lob.writeAppend (v_out_bl,utl_raw.length(dBuffer),dBuffer);
pos := pos + v_readSize_nr;
end loop;
o_blob := v_out_bl;
end base64_string_to_blob;
Function base64_string_to_blob (i_clob in clob) return blob
is
v_out_bl blob;
clob_size number;
pos number;
charBuff varchar2(32767);
dBuffer RAW(32767);
v_readSize_nr number;
v_line_nr number;
begin
dbms_lob.createTemporary (v_out_bl, true, dbms_lob.call);
v_line_nr:=greatest(65, instr(i_clob,chr(10)), instr(i_clob,chr(13)));
v_readSize_nr:= floor(32767/v_line_nr)*v_line_nr;
clob_size := dbms_lob.getLength(i_clob);
pos := 1;
while (pos < clob_size) loop
dbms_lob.read (i_clob, v_readSize_nr, pos, charBuff);
dBuffer := UTL_ENCODE.base64_decode (utl_raw.cast_to_raw(charBuff));
dbms_lob.writeAppend (v_out_bl,utl_raw.length(dBuffer),dBuffer);
pos := pos + v_readSize_nr;
end loop;
return v_out_bl;
end base64_string_to_blob;
end fileutils;
/
Show error;
I think you can use LISTAGG function
select listagg(ONE_LINE) WITHIN GROUP (ORDER BY ROW_NUM)
from (
select 'ID,A_ID,B_ID,CREATION_DATE' || CHR(10) as ONE_LINE,
-1 as ROW_NUM
from dual
union all
select ID ||','|| A_ID ||','|| B_ID ||','|| CREATION_DATE || CHR(10) as ONE_LINE,
ROWNUM as ROW_NUM
from Source_Data1
where <expression>
);
The following code will create a CSV version of a table as a CLOB row, using DATA_DUMP.SQL and the function DBMS_XSLPROCESSOR.READ2CLOB.
First, install the DATA_PUMP procedure on your schema. The program is a single command in a single file, you can run it through SQL*Plus or just copy and paste it into your IDE.
Next, create a directory on the server to temporarily store the files. Most existing utilities are meant to create files. It's easier to write a file and read it as a CLOB, than to modify the utility to write directly into a table.
create or replace directory temp_dir as 'C:\temp';
Then this PL/SQL block will read and write the data:
--Store a table as a single CSV clob.
--TODO: Delete the files from the server when done.
declare
v_clob clob;
begin
for tables in
(
--Query that returns the table names.
--(Doesn't have to be hard-coded, maybe a query on DBA_TABLES would help?)
select column_value table_name
from table(sys.odcivarchar2list('source_data'))
) loop
data_dump
(
query_in => 'select * from source_data1',
file_in => 'source_data1.csv',
directory_in => 'temp_dir',
nls_date_fmt_in => 'YYYY-MM-DD HH24:MI:SS',
delimiter_in => ',',
header_row_in => true
);
v_clob := dbms_xslprocessor.read2clob
(
flocation => 'TEMP_DIR',
fname => 'source_data1.csv'
);
insert into result_table (id, source_name, data) values
(result_seq.nextval, 'source_data1', v_clob);
--I wouldn't normally commit after each row, but when exporting large
--tables the script may run too long and there's a good chance of an
--error, so partial results may be helpful.
commit;
end loop;
end;
/
Reading and writing to CSV files is not rocket science but it's not as trivial as most people think. 99% of the CSV programs out there can't handle things like commas in the data, or adding a header row. So it's best to look for preexisting code instead of writing your own utility.

Creating a table from a Comma Separated List in Oracle (> 11g) - Input string limit 4000 chars

I have an Oracle (11g) SP which takes a comma separated string of inputs (e.g. 'Cats, Dogs, Monkeys'). The crux of the code is taken from Tony Andrews' blog and works as expected but it will not accept input strings greater than 4000 characters:
PROCEDURE delimstring_to_table ( p_delimstring IN VARCHAR2
, p_table OUT VARCHAR2_TABLE
, p_nfields OUT INTEGER
, p_delim IN VARCHAR2 DEFAULT ','
)
IS
v_string VARCHAR2(32767) := p_delimstring;
v_nfields PLS_INTEGER := 1;
v_table VARCHAR2_TABLE;
v_delimpos PLS_INTEGER := INSTR(p_delimstring, p_delim);
v_delimlen PLS_INTEGER := LENGTH(p_delim);
BEGIN
WHILE v_delimpos > 0
LOOP
v_table(v_nfields) := SUBSTR(v_string,1,v_delimpos-1);
v_string := SUBSTR(v_string,v_delimpos+v_delimlen);
v_nfields := v_nfields+1;
v_delimpos := INSTR(v_string, p_delim);
END LOOP;
v_table(v_nfields) := v_string;
p_table := v_table;
p_nfields := v_nfields;
END delimstring_to_table;
Reference - Tony Andrews Blog
The functions above are then used inside any relevant SPs within the package:
PROCEDURE Get_Animals(
p_Animals IN VARCHAR2 := NULL
, resultset_out OUT resultset_typ
, error_out OUT VARCHAR2
)
IS
BEGIN
IF p_Animals IS NULL THEN
OPEN resultset_out FOR
SELECT /*+ ALL_ROWS */ * FROM ANIMALS ORDER BY NAME;
ELSE
OPEN resultset_out FOR
SELECT * FROM ANIMALS where NAME in (SELECT * FROM TABLE (Comma_To_Table(p_RICs)));
END IF;
error_out := NULL;
EXCEPTION
WHEN OTHERS THEN
error_out := 'Get_Animals() -> ' || SUBSTR(SQLERRM,1,200);
END Get_Animals;
If the input string is > 4000 characters, I get the following error from the SP:
ORA-00604: error occurred at recursive SQL level 1
ORA-01003: no statement parsed - GET_ANIMALS
I have two questions:
Can I make the function work with input strings greater than 4000 characters?
Is there a more effective way of achieving the same result?
Any help or suggestions would be much appreciated.
Can I make the function work with input strings greater than 4000 characters?
Yes, you can use for example CLOB
Is there a more effective way of achieving the same result?
I saw in the comments of the blog a good answer, which is about a recursive solution.
just make some datatype changes for making it to work e.g.:
change the varchar2_table type to CLOB
TYPE varchar2_table IS TABLE OF CLOB INDEX BY BINARY_INTEGER;
change the VARCHAR2 datatype to CLOB in all p_delimstring occurences
change original SUBSTR functions to DBMS_LOB.SUBSTR
(if you need more info about that: http://docs.oracle.com/cd/A91202_01/901_doc/appdev.901/a89852/dbms_23b.htm)
CREATE OR REPLACE PACKAGE parse AS
/*
|| Package of utility procedures for parsing delimited or fixed position strings into tables
|| of individual values, and vice versa.
*/
TYPE varchar2_table IS TABLE OF CLOB INDEX BY BINARY_INTEGER;
PROCEDURE delimstring_to_table
( p_delimstring IN CLOB
, p_table OUT varchar2_table
, p_nfields OUT INTEGER
, p_delim IN VARCHAR2 DEFAULT ','
);
PROCEDURE table_to_delimstring
( p_table IN varchar2_table
, p_delimstring OUT CLOB
, p_delim IN VARCHAR2 DEFAULT ','
);
END parse;
/
CREATE OR REPLACE PACKAGE BODY parse AS
PROCEDURE delimstring_to_table
( p_delimstring IN CLOB
, p_table OUT varchar2_table
, p_nfields OUT INTEGER
, p_delim IN VARCHAR2 DEFAULT ','
)
IS
v_string CLOB := p_delimstring;
v_nfields PLS_INTEGER := 1;
v_table varchar2_table;
v_delimpos PLS_INTEGER := INSTR(p_delimstring, p_delim);
v_delimlen PLS_INTEGER := LENGTH(p_delim);
BEGIN
WHILE v_delimpos > 0
LOOP
v_table(v_nfields) := DBMS_LOB.SUBSTR(v_string,1,v_delimpos-1);
v_string := DBMS_LOB.SUBSTR(v_string,v_delimpos+v_delimlen);
v_nfields := v_nfields+1;
v_delimpos := INSTR(v_string, p_delim);
END LOOP;
v_table(v_nfields) := v_string;
p_table := v_table;
p_nfields := v_nfields;
END delimstring_to_table;
PROCEDURE table_to_delimstring
( p_table IN varchar2_table
, p_delimstring OUT CLOB
, p_delim IN VARCHAR2 DEFAULT ','
)
IS
v_nfields PLS_INTEGER := p_table.COUNT;
v_string CLOB;
BEGIN
FOR i IN 1..v_nfields
LOOP
v_string := v_string || p_table(i);
IF i != v_nfields THEN
v_string := v_string || p_delim;
END IF;
END LOOP;
p_delimstring := v_string;
END table_to_delimstring;
END parse;
/

How do I view a CLOB output parameter in TOAD from an Oracle Stored Procedure?

I have a stored procedure in a package in an Oracle database that has 2 input parameters + 1 output CLOB parameter. How do I view the output in Toad? (Preferably with the user only having execute/select permissions)
Solution:
DECLARE
my_output_parameter CLOB;
BEGIN
my_package.my_stored_proc(1, 2, my_output_parameter);
DBMS_OUTPUT.PUT_LINE(my_output_parameter);
END;
Don't forget to execute as script, rather than just execute statement, and results appear in the DBMS Output window, not the datagrid.
I guess DBMS_OUTPUT.PUT_LINE has an internal line limit of 255 chars. However it has been removed from 10g Release 2 onwards. You can try inserting the column data in a table and view it later on by querying that table.
Please refer -
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:146412348066
Would you consider printing the CLOB as a result set? You could then use a PIPELINED function (more about them here: PIPELINED functions by Tim Hall) which would return the CLOB line by line, take a look at the example below:
CREATE TABLE my_clob_tab (
id NUMBER,
clob_col CLOB
)
/
INSERT INTO my_clob_tab
VALUES (1,
to_clob('first line' || chr(10) ||
'second line, a longer one' || chr(10) ||
'third'))
/
CREATE OR REPLACE TYPE t_my_line_str AS TABLE OF VARCHAR2(2000)
/
CREATE OR REPLACE FUNCTION print_clob_func(p_id IN NUMBER)
RETURN t_my_line_str PIPELINED
AS
v_buffer VARCHAR2(32767);
v_clob CLOB;
v_len NUMBER;
v_offset NUMBER := 1;
v_line_break_pos NUMBER;
v_amount NUMBER;
BEGIN
SELECT clob_col
INTO v_clob
FROM my_clob_tab
WHERE id = p_id;
IF v_clob IS NOT NULL THEN
v_len := dbms_lob.getlength(v_clob);
WHILE v_offset < v_len
LOOP
v_line_break_pos := instr(v_clob, chr(10), v_offset);
IF v_line_break_pos = 0 THEN
v_amount := v_len - v_offset + 1;
ELSE
v_amount := v_line_break_pos - v_offset;
END IF;
dbms_lob.read(v_clob, v_amount, v_offset, v_buffer);
v_offset := v_offset + v_amount + 1;
PIPE ROW (v_buffer);
END LOOP;
END IF;
END;
/
(the function can be changed so that it takes as a parameter the CLOB you get from your procedure)
The code reads the content of the CLOB line by line (I assumed that the line separator is CHR(10) - if you are on Windows, you can change it to CHR(10) || CHR(13)) and PIPEs each line to the SELECT statement.
The function that reads the clob could also print the output to the standard output via dbms_output.put_line, but it would be trickier, because you'd have to take into account that standard output's maximal line length is limitied to, correct me if I'm wrong, 2000 characters, but it is doable (can't try that solution right now, unfortunately). In the meanwhile, please check above proposal and give me some feedback if that would work for you.
Back to the solution, now we can issue this SELECT statement:
SELECT COLUMN_VALUE AS clob_line_by_line FROM TABLE(print_clob_func(1));
Which will give us the following output:
CLOB_LINE_BY_LINE
-------------------------
first line
second line, a longer one
third
Check it at SQLFiddle: SQLFiddle example
Approach with inserting PL/SQL block and dbms_output:
DECLARE
my_output_parameter CLOB;
BEGIN
my_package.my_stored_proc(1, 2, my_output_parameter);
declare
vClob CLOB := my_output_parameter;
vPos number;
vLen number;
begin
vLen := DBMS_LOB.GetLength(vClob);
vPos := 1;
while vPos < vLen loop
DBMS_OUTPUT.Put(DBMS_LOB.Substr(vCLOB, 200, vPos));
vPos := vPos + 200;
end loop;
DBMS_OUTPUT.new_line;
end;
END;