Export query result to csv in oracle stored procedure - oracle

So i have a query that i would like to execute through a stored procedure and export the output of the query to a CSV file. So i am using the following stored procedure to do it:
CREATE OR REPLACE PROCEDURE parseCSV(
p_file_dir VARCHAR2, -- Oracle directory name
p_file_name VARCHAR2, -- filename
p_sql_query VARCHAR2, -- select * from table or some such query
p_delimiter CHAR -- column delimiter
)
AS
l_cursor_handle INTEGER;
l_dummy NUMBER;
l_col_cnt INTEGER;
l_rec_tab DBMS_SQL.DESC_TAB;
l_current_col NUMBER(16);
l_current_line VARCHAR2(2047);
l_column_value VARCHAR2(300);
l_file_handle UTL_FILE.FILE_TYPE;
l_print_text VARCHAR2(100);
l_record_count NUMBER(16) := 0;
BEGIN
l_file_handle := UTL_FILE.FOPEN(p_file_dir, p_file_name, 'a', 2047);
l_cursor_handle := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(l_cursor_handle, p_sql_query, DBMS_SQL.native);
l_dummy := DBMS_SQL.EXECUTE(l_cursor_handle);
DBMS_SQL.DESCRIBE_COLUMNS(l_cursor_handle, l_col_cnt, l_rec_tab);
l_current_col := l_rec_tab.FIRST;
IF (l_current_col IS NOT NULL) THEN
LOOP
DBMS_SQL.DEFINE_COLUMN(l_cursor_handle, l_current_col, l_column_value, 300);
l_print_text := l_rec_tab(l_current_col).col_name || p_delimiter;
UTL_FILE.PUT (l_file_handle, l_print_text);
l_current_col := l_rec_tab.NEXT(l_current_col);
EXIT WHEN (l_current_col IS NULL);
END LOOP;
END IF;
UTL_FILE.PUT_LINE (l_file_handle,' ');
LOOP
EXIT WHEN DBMS_SQL.FETCH_ROWS(l_cursor_handle) = 0;
l_current_line := '';
FOR l_current_col IN 1..l_col_cnt LOOP
DBMS_SQL.COLUMN_VALUE (l_cursor_handle, l_current_col, l_column_value);
l_print_text := l_column_value || p_delimiter;
l_current_line := l_current_line || l_column_value || p_delimiter;
END LOOP;
l_record_count := l_record_count + 1;
UTL_FILE.PUT_LINE (l_file_handle, l_current_line);
END LOOP;
UTL_FILE.FCLOSE (l_file_handle);
DBMS_SQL.CLOSE_CURSOR(l_cursor_handle);
END;
/
The procedure when executed processes the query and then stores the result into a delimited file. For example, the output of the procedure for a regular SELECT statement will be of this form:
ID,ROLL_NO,RANK,
1,123456,1620,
2,987654,1344,
Now herein lies my issue. As you can see each row within the output file is ending with an extra trailing ,. Now, due to my lack of knowledge in plsql, i can't think of a modification that i can do to the procedure so that the expected output file would be of this form:
ID,ROLL_NO,RANK
1,123456,1620
2,987654,1344
Could someone be kind enough to help out an Oracle newbie here and give me some pointers as to how i can do it? I would appreciate it a lot.

Please try the below where comments have been added
CREATE OR REPLACE PROCEDURE parseCSV(
p_file_dir VARCHAR2, -- Oracle directory name
p_file_name VARCHAR2, -- filename
p_sql_query VARCHAR2, -- select * from table or some such query
p_delimiter CHAR -- column delimiter
)
AS
l_cursor_handle INTEGER;
l_dummy NUMBER;
l_col_cnt INTEGER;
l_rec_tab DBMS_SQL.DESC_TAB;
l_current_col NUMBER(16);
l_current_line VARCHAR2(2047);
l_column_value VARCHAR2(300);
l_file_handle UTL_FILE.FILE_TYPE;
l_print_text VARCHAR2(100);
l_record_count NUMBER(16) := 0;
BEGIN
l_file_handle := UTL_FILE.FOPEN(p_file_dir, p_file_name, 'a', 2047);
l_cursor_handle := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(l_cursor_handle, p_sql_query, DBMS_SQL.native);
l_dummy := DBMS_SQL.EXECUTE(l_cursor_handle);
DBMS_SQL.DESCRIBE_COLUMNS(l_cursor_handle, l_col_cnt, l_rec_tab);
l_current_col := l_rec_tab.FIRST;
IF (l_current_col IS NOT NULL) THEN
LOOP
DBMS_SQL.DEFINE_COLUMN(l_cursor_handle, l_current_col, l_column_value, 300);
l_print_text := l_rec_tab(l_current_col).col_name ||
p_delimiter;
l_current_col := l_rec_tab.NEXT(l_current_col);
IF l_current_col IS NULL/*handling for last delimiter for
column */
THEN
l_print_text:=substr(l_print_text,-1);
END IF;
UTL_FILE.PUT (l_file_handle, l_print_text);
EXIT WHEN (l_current_col IS NULL);
END LOOP;
END IF;
UTL_FILE.PUT_LINE (l_file_handle,' ');
LOOP
EXIT WHEN DBMS_SQL.FETCH_ROWS(l_cursor_handle) = 0;
l_current_line := '';
FOR l_current_col IN 1..l_col_cnt LOOP
DBMS_SQL.COLUMN_VALUE (l_cursor_handle, l_current_col, l_column_value);
l_print_text := l_column_value || p_delimiter;
IF l_current_col =l_col_cnt
then
l_current_line := l_current_line || l_column_value;
ELSE
l_current_line := l_current_line || l_column_value ||
p_delimiter;
END IF;
END LOOP;
l_record_count := l_record_count + 1;
UTL_FILE.PUT_LINE (l_file_handle, l_current_line);
END LOOP;
UTL_FILE.FCLOSE (l_file_handle);
DBMS_SQL.CLOSE_CURSOR(l_cursor_handle);
END;

First of all, I'd say procedure which exports CSV file should be named createCSV, makeCSV or something similar, but never parseCSV.
Next, I'm not sure that this application design in best. Usually database should worry of data while external client thinking about medias, formats and so on.
At last, for elimination of trailing delimiter you should use something like this:
...
p_delimiter CHAR -- column delimiter
)
AS
l_delimiter varchar2(1 char);
...
BEGIN
....
l_current_col := l_rec_tab.FIRST;
l_delimiter := '';
....
l_print_text := l_delimiter || l_rec_tab(l_current_col).col_name;
l_delimiter := p_delimiter;
....
/

Try this procedure:
CREATE OR REPLACE PROCEDURE parseCSV(
p_file_dir VARCHAR2, -- Oracle directory name
p_file_name VARCHAR2, -- filename
p_sql_query VARCHAR2, -- select * from table or some such query
p_delimiter CHAR -- column delimiter
)
AS
l_cursor_handle INTEGER;
l_dummy NUMBER;
l_col_cnt INTEGER;
l_rec_tab DBMS_SQL.DESC_TAB;
l_current_col NUMBER(16);
l_current_line VARCHAR2(2047);
l_column_value VARCHAR2(300);
l_file_handle UTL_FILE.FILE_TYPE;
l_print_text VARCHAR2(100);
l_record_count NUMBER(16) := 0;
BEGIN
l_file_handle := UTL_FILE.FOPEN(p_file_dir, p_file_name, 'a', 2047);
l_cursor_handle := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(l_cursor_handle, p_sql_query, DBMS_SQL.native);
l_dummy := DBMS_SQL.EXECUTE(l_cursor_handle);
DBMS_SQL.DESCRIBE_COLUMNS(l_cursor_handle, l_col_cnt, l_rec_tab);
l_current_col := l_rec_tab.FIRST;
IF (l_current_col IS NOT NULL) THEN
LOOP
DBMS_SQL.DEFINE_COLUMN(l_cursor_handle, l_current_col, l_column_value, 300);
IF l_print_text IS NOT NULL THEN
l_print_text := l_print_text || p_delimiter;
END IF;
l_print_text := l_rec_tab(l_current_col).col_name;
UTL_FILE.PUT (l_file_handle, l_print_text);
l_current_col := l_rec_tab.NEXT(l_current_col);
EXIT WHEN (l_current_col IS NULL);
END LOOP;
END IF;
UTL_FILE.PUT_LINE (l_file_handle,' ');
l_print_text := NULL;
LOOP
EXIT WHEN DBMS_SQL.FETCH_ROWS(l_cursor_handle) = 0;
l_current_line := '';
FOR l_current_col IN 1..l_col_cnt LOOP
DBMS_SQL.COLUMN_VALUE (l_cursor_handle, l_current_col, l_column_value);
IF l_print_text IS NOT NULL THEN
l_print_text := l_print_text || p_delimiter;
END IF;
l_print_text := l_column_value;
END LOOP;
l_record_count := l_record_count + 1;
UTL_FILE.PUT_LINE (l_file_handle, l_print_text );
END LOOP;
UTL_FILE.FCLOSE (l_file_handle);
DBMS_SQL.CLOSE_CURSOR(l_cursor_handle);
END;

You can do two things. The first is to update your call to UTL_FILE.put so you conditionally add the delimiter (example below for the header record but the same can be applied to the data) :
IF l_current_col < l_rec_tab.LAST THEN
l_print_text := l_rec_tab(l_current_col).col_name || p_delimiter;
ELSE
l_print_text := l_rec_tab(l_current_col).col_name ;
END IF ;
UTL_FILE.PUT (l_file_handle, l_print_text);
The second approach would be to build up a string with a complete row of data and then manipulate that string before you call UTL FILE (in this case I am assuming l_print_text is long enough for a row of data) :
IF (l_current_col IS NOT NULL) THEN
LOOP
DBMS_SQL.DEFINE_COLUMN(l_cursor_handle, l_current_col, l_column_value, 300);
--append to variable
l_print_text := l_print_text||l_rec_tab(l_current_col).col_name || p_delimiter;
l_current_col := l_rec_tab.NEXT(l_current_col);
EXIT WHEN (l_current_col IS NULL);
END LOOP;
END IF;
--trim trailing delimiter
l_print_text := TRIM(TRAILING p_delimiter FROM l_print_text) ;
--send whole line to file
UTL_FILE.PUT_LINE (l_file_handle,l_print_text);

I have changed your code a little. User this procedure:
CREATE OR REPLACE PROCEDURE PARSECSV
(
P_FILE_DIR VARCHAR2, -- Oracle directory name
P_FILE_NAME VARCHAR2, -- filename
P_SQL_QUERY VARCHAR2, -- select * from table or some such query
P_DELIMITER CHAR -- column delimiter
) IS
L_CURSOR_HANDLE INTEGER;
L_DUMMY NUMBER;
L_COL_CNT INTEGER;
L_REC_TAB DBMS_SQL.DESC_TAB;
L_COLUMN_VALUE VARCHAR2(300);
L_FILE_HANDLE UTL_FILE.FILE_TYPE;
L_PRINT_TEXT CLOB;
BEGIN
L_FILE_HANDLE := UTL_FILE.FOPEN(P_FILE_DIR,
P_FILE_NAME,
'a',
2047);
L_CURSOR_HANDLE := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(L_CURSOR_HANDLE,
P_SQL_QUERY,
DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS(L_CURSOR_HANDLE,
L_COL_CNT,
L_REC_TAB);
FOR L_CURRENT_COL IN 1 .. L_COL_CNT
LOOP
DBMS_SQL.DEFINE_COLUMN(L_CURSOR_HANDLE,
L_CURRENT_COL,
L_COLUMN_VALUE,
300);
IF L_PRINT_TEXT IS NOT NULL THEN
L_PRINT_TEXT := L_PRINT_TEXT || P_DELIMITER;
END IF;
L_PRINT_TEXT := L_PRINT_TEXT || L_REC_TAB(L_CURRENT_COL).COL_NAME;
END LOOP;
L_PRINT_TEXT := L_PRINT_TEXT || CHR(10) || CHR(13);
UTL_FILE.PUT(L_FILE_HANDLE,
L_PRINT_TEXT);
L_PRINT_TEXT := NULL;
L_DUMMY := DBMS_SQL.EXECUTE(L_CURSOR_HANDLE);
DBMS_OUTPUT.PUT_LINE(L_DUMMY);
LOOP
EXIT WHEN DBMS_SQL.FETCH_ROWS(L_CURSOR_HANDLE) = 0;
FOR L_CURRENT_COL IN 1 .. L_COL_CNT
LOOP
DBMS_SQL.COLUMN_VALUE(L_CURSOR_HANDLE,
L_CURRENT_COL,
L_COLUMN_VALUE);
IF L_PRINT_TEXT IS NOT NULL THEN
L_PRINT_TEXT := L_PRINT_TEXT || P_DELIMITER;
END IF;
L_PRINT_TEXT := L_PRINT_TEXT || L_COLUMN_VALUE;
END LOOP;
L_PRINT_TEXT := L_PRINT_TEXT || CHR(10) || CHR(13);
UTL_FILE.PUT(L_FILE_HANDLE,
L_PRINT_TEXT);
END LOOP;
UTL_FILE.FCLOSE(L_FILE_HANDLE);
DBMS_SQL.CLOSE_CURSOR(L_CURSOR_HANDLE);
END;

First, create a directory in the database and provide the read, write access to that directory.
To create a directory:
CREATE OR REPLACE DIRECTORY alias AS 'pathname';
To Grant Read,Write:
GRANT read,write ON DIRECTORY alias TO {user | role | PUBLIC};
After that use below stored procedure to get the output of SQL query in any file format.
CREATE OR REPLACE PROCEDURE CSV_EXPORT AS
CURSOR c_data IS
SELECT * from table_name;
v_file UTL_FILE.FILE_TYPE;
BEGIN
v_file := UTL_FILE.FOPEN(location => 'FILES1',
filename => 'csv_exp.txt',
open_mode => 'w',
max_linesize => 32767);
FOR cur_rec IN c_data LOOP
UTL_FILE.PUT_LINE(v_file,
cur_rec.column1 || ',' ||
cur_rec.column2 );
END LOOP;
UTL_FILE.FCLOSE(v_file);
EXCEPTION
WHEN OTHERS THEN
UTL_FILE.FCLOSE(v_file);
RAISE;
END;
To run the stored procedure:
EXEC CSV_EXPORT;
here in code FILES1 is directory name.

Related

Return results of query inside PL/SQL script

Is it possible that SELECT statement inside PL/SQL block will return table records like during executing SELECT query in standard way?
I mean why code:
DECLARE
sql_qry VARCHAR2 (150);
BEGIN
sql_qry:= 'SELECT ''1'' FROM dual';
EXECUTE IMMEDIATE sql_qry;
END;
/
after execusion returns only information:
PL/SQL procedure successfully completed.
Is it possible that SELECT statement enclosed in PL/SQL block will behave the same way like executing:
SELECT '1' FROM dual;
If you're using Oracle 12c and above you may use DBMS_SQL.RETURN_RESULT
For 11g, you may make use of this standard procedure
create or replace procedure return_result( l_query varchar2 )
is
l_theCursor integer default dbms_sql.open_cursor;
l_columnValue varchar2(4000);
l_status integer;
l_colCnt number := 0;
l_separator varchar2(1);
l_descTbl dbms_sql.desc_tab;
begin
dbms_sql.parse( l_theCursor, l_query, dbms_sql.native );
dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
l_separator := '';
for i in 1 .. l_colCnt loop
dbms_output.put( l_separator || l_descTbl(i).col_name );
l_separator := ',';
end loop;
dbms_output.put_line('');
for i in 1 .. l_colCnt loop
dbms_sql.define_column( l_theCursor, i, l_columnValue, 4000 );
end loop;
l_status := dbms_sql.execute(l_theCursor);
while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
l_separator := '';
for i in 1 .. l_colCnt loop
dbms_sql.column_value( l_theCursor, i, l_columnValue );
dbms_output.put( l_separator || l_columnValue );
l_separator := ',';
end loop;
dbms_output.new_line;
end loop;
dbms_sql.close_cursor(l_theCursor);
end;
/
Call it as
set serveroutput on
EXECUTE return_result('SELECT ''1'' as col FROM dual');
Res
COL
1

How to specify max number of rows to be written in one csv file in oracle?

I am using the procedure below as mentioned in this to dump data into csv.
This writes data into a single csv file test.csv. In case table is very big, I don't want to write into one csv file. I want to specify a threshold of 10k into it with a file name as
test_sequenceNumber _timeOfOpeningInYYYYMMDDHHMMSSsss.csv
so for example if row count of a table is 15k then I should have 2 files:
test_1_20181112012830000.csv
test_2_20181112012835005.csv
How can we I achieve this by modifying the below SP?
create or replace procedure dump_table_to_csv
( p_tname in varchar2
, p_dir in varchar2
)
as
l_output utl_file.file_type;
l_theCursor integer default dbms_sql.open_cursor;
l_columnValue varchar2(4000);
l_status integer;
l_query varchar2(1000)
default 'select * from ' || p_tname;
l_colCnt number := 0;
l_separator varchar2(1);
l_descTbl dbms_sql.desc_tab;
p_filename varchar2(100) := 'test.csv' ;
begin
l_output := utl_file.fopen( p_dir, p_filename, 'w' , 32760);
execute immediate 'alter session set nls_date_format=''dd-mon-yyyy hh24:mi:ss'' ';
dbms_sql.parse( l_theCursor, l_query, dbms_sql.native );
dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
for i in 1 .. l_colCnt loop
utl_file.put( l_output, l_separator || '"' || l_descTbl(i).col_name || '"' );
dbms_sql.define_column( l_theCursor, i, l_columnValue, 4000 );
l_separator := ',';
end loop;
utl_file.new_line( l_output );
l_status := dbms_sql.execute(l_theCursor);
while (dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
l_separator := '';
for i in 1 .. l_colCnt loop
dbms_sql.column_value( l_theCursor, i, l_columnValue );
utl_file.put( l_output, l_separator || l_columnValue );
l_separator := ',';
end loop;
utl_file.new_line(l_output );
end loop;
dbms_sql.close_cursor(l_theCursor);
utl_file.fclose(l_output );
execute immediate 'alter session set nls_date_format=''dd-MON-yy'' ';
exception
when others then
utl_file.fclose( l_output );
execute immediate 'alter session set nls_date_format=''dd-MON-yy'' ';
raise;
end;
EDIT1:
I was trying of applying a logic of mod(num_rows_written,10000) == 0 it's time to close old file and start a new one but just stuck at a point that, what should be top most loop to continue the same operation on new file ?
Well, it's a fairly straightforward task. All you need to do is close the old file and open a new one. There's no need for outer loop, all you need is to put a condition and retrace the steps for preparing the csv.
create or replace procedure dump_table_to_csv
( p_tname in varchar2
, p_dir in varchar2
)
as
l_output utl_file.file_type;
l_theCursor integer default dbms_sql.open_cursor;
l_columnValue varchar2(4000);
l_status integer;
l_query varchar2(1000)
default 'select * from ' || p_tname;
l_colCnt number := 0;
l_separator varchar2(1);
l_descTbl dbms_sql.desc_tab;
l_file_seq number := 1;
l_rows_written number := 0;
l_nls_format varchar2(200);
begin
l_output := utl_file.fopen( p_dir, 'test_'||l_file_seq||'_'||to_char(systimestamp, 'YYYYMMDDHH24MISSFF3'), 'w' , 32760);
l_file_seq := l_file_seq + 1;
SELECT value into l_nls_format
FROM nls_session_parameters
WHERE parameter = 'NLS_DATE_FORMAT';
execute immediate 'alter session set nls_date_format=''dd-mon-yyyy hh24:mi:ss'' ';
dbms_sql.parse( l_theCursor, l_query, dbms_sql.native );
dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
for i in 1 .. l_colCnt loop
utl_file.put( l_output, l_separator || '"' || l_descTbl(i).col_name || '"' );
dbms_sql.define_column( l_theCursor, i, l_columnValue, 4000 );
l_separator := ',';
end loop;
utl_file.new_line( l_output );
l_status := dbms_sql.execute(l_theCursor);
while (dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
if l_rows_written >= 10000 then
utl_file.fclose( l_output );
l_output := utl_file.fopen( p_dir, 'test_'||l_file_seq||'_'||to_char(systimestamp, 'YYYYMMDDHH24MISSFF3'), 'w' , 32760);
for i in 1 .. l_colCnt loop
utl_file.put( l_output, l_separator || '"' || l_descTbl(i).col_name || '"' );
l_separator := ',';
end loop;
utl_file.new_line( l_output );
l_file_seq := l_file_seq + 1;
l_rows_written := 0;
end if;
l_separator := '';
for i in 1 .. l_colCnt loop
dbms_sql.column_value( l_theCursor, i, l_columnValue );
utl_file.put( l_output, l_separator || l_columnValue );
l_separator := ',';
end loop;
utl_file.new_line(l_output );
l_rows_written := l_rows_written + 1;
end loop;
dbms_sql.close_cursor(l_theCursor);
utl_file.fclose(l_output );
execute immediate 'alter session set nls_date_format='''||l_nls_format||''' ';
exception
when others then
utl_file.fclose( l_output );
execute immediate 'alter session set nls_date_format='''||l_nls_format||''' ';
raise;
end;
It might be a good idea to offload the code for preparing CSV to a separate (sub)procedure to avoid code duplication. Also beware that original procedure has a nasty side effect of overriding your nls_date_format to dd-MON-yy without any regards to how it was set before execution; I fixed that.

Dump table containing CLOB to csv using PL/SQL proc

I have assembled a procedure to dump a query containing CLOB columns to a csv file.
It seems to be working fine until I encounter a query containing dates.
ORA-00932: inconsistent datatypes: expected CLOB got DATE
Is there a way to dynamically convert those dates to some default string format to be able to use the procedure as it is now. Or how can I refactor it if necessary?
create or replace
PROCEDURE export_query_csv(
p_query IN VARCHAR2,
p_filename IN VARCHAR2)
IS
l_separator VARCHAR2 (10 CHAR) := ';';
l_dir VARCHAR2 (128 CHAR) := 'MY_DIR';
l_output utl_file.file_type;
l_theCursor INTEGER DEFAULT dbms_sql.open_cursor;
l_columnValue CLOB;
l_status INTEGER;
l_colCnt NUMBER DEFAULT 0;
l_cnt NUMBER DEFAULT 0;
l_descTbl dbms_sql.desc_tab;
l_substrVal VARCHAR2(4000) ;
l_offset NUMBER :=1;
l_amount NUMBER := 3000;
l_clobLen NUMBER :=0;
BEGIN
EXECUTE IMMEDIATE 'alter session set nls_date_format = ''dd-mon-yyyy hh24:mi:ss''';
l_output := utl_file.fopen(l_dir, p_filename, 'wb');
dbms_sql.parse(l_theCursor, p_query, dbms_sql.native);
FOR i IN 1 .. 1000
LOOP
BEGIN
dbms_sql.define_column(l_theCursor, i, l_columnValue);
l_colCnt := i;
EXCEPTION
WHEN OTHERS THEN
IF ( SQLCODE = -1007 ) THEN
EXIT;
ELSE
RAISE;
END IF;
END;
END LOOP;
dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
FOR i IN 1 .. l_colCnt
LOOP
utl_file.put_raw(l_output,utl_raw.cast_to_raw('"'));
utl_file.put_raw(l_output,utl_raw.cast_to_raw(l_descTbl(i).col_name));
utl_file.put_raw(l_output,utl_raw.cast_to_raw('"'));
IF i < l_colCnt THEN
utl_file.put_raw(l_output,utl_raw.cast_to_raw(l_separator));
END IF;
END LOOP;
utl_file.put_raw(l_output,utl_raw.cast_to_raw(chr(13) || chr(10)));
l_status := dbms_sql.execute(l_theCursor);
LOOP
EXIT WHEN (dbms_sql.fetch_rows(l_theCursor) <= 0);
FOR i IN 1 .. l_colCnt
LOOP
dbms_sql.column_value(l_theCursor, i, l_columnValue);
l_clobLen := dbms_lob.getlength(l_columnValue);
WHILE l_offset <= l_clobLen
LOOP
l_substrVal := dbms_lob.substr(l_columnValue,l_amount,l_offset);
utl_file.put_raw(l_output,utl_raw.cast_to_raw('"'));
utl_file.put_raw(l_output,utl_raw.cast_to_raw(l_substrVal));
utl_file.put_raw(l_output,utl_raw.cast_to_raw('"'));
l_offset:=l_offset+l_amount;
END LOOP;
l_offset := 1;
IF i < l_colCnt THEN
utl_file.put_raw(l_output,utl_raw.cast_to_raw(l_separator));
END IF;
END LOOP;
utl_file.put_raw(l_output,utl_raw.cast_to_raw(chr(13) || chr(10)));
l_cnt := l_cnt + 1;
END LOOP;
dbms_sql.close_cursor(l_theCursor);
utl_file.fclose(l_output);
END;
Found it myself, following this pattern:
-- Define columns
FOR i IN 1 .. colcnt LOOP
IF desctab(i).col_type = 2 THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, numvar);
ELSIF desctab(i).col_type = 12 THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, datevar);
......
ELSE
DBMS_SQL.DEFINE_COLUMN(curid, i, namevar);
END IF;
END LOOP;

utl file oracle with buffer

I've read this article about Tuning UTL_FILE
Technically the approach is to concatenate records while the size is less than maximum length of the buffer and write the entire buffer when the length is greater
Excerpt from site (code):
IF LENGTH(v_buffer) + c_eollen + LENGTH(r.csv) <= c_maxline THEN
v_buffer := v_buffer || c_eol || r.csv;
ELSE
IF v_buffer IS NOT NULL THEN
UTL_FILE.PUT_LINE(v_file, v_buffer);
END IF;
v_buffer := r.csv;
END IF;
So, I've decided to to move this one in a function
-- constants
C_CHR CONSTANT VARCHAR2(2) := CHR(10);
C_CHRLEN CONSTANT PLS_INTEGER := LENGTH(C_CHR);
C_MAXLEN CONSTANT PLS_INTEGER := 32767;
function FN_GET_BUFFER(p_rec IN VARCHAR2, p_buffer IN VARCHAR2) RETURN VARCHAR2
is
begin
IF LENGTH(p_buffer) + C_CHRLEN + LENGTH(p_rec) <= C_MAXLEN THEN
RETURN p_buffer || C_CHR || p_rec;
ELSE
IF p_buffer IS NOT NULL THEN
RETURN p_buffer;
END IF;
RETURN p_rec;
END IF;
end FN_GET_BUFFER;
And here's how I call my function which doesn't work as expected..
procedure export as
l_tmp_file_name VARCHAR2(30);
l_csv_file_name VARCHAR2(30);
l_file UTL_FILE.FILE_TYPE;
l_buffer VARCHAR2(32767);
CURSOR cur_table
IS
SELECT * FROM table
begin
l_tmp_file_name := 'file.tmp';
BEGIN
l_file := UTL_FILE.FOPEN(C_DIRECTORY_PATH, l_tmp_file_name,'A',C_MAXLEN);
FOR rec IN cur_table
LOOP
l_rec := CONVERT(rec.id || ',' || rec.user ,'AL32UTF8');
l_buffer := l_buffer || FN_GET_BUFFER(l_rec, l_buffer);
if l_buffer is NOT NULL then
UTL_FILE.PUT_LINE(l_file, l_buffer);
l_buffer := NULL;
end if;
END LOOP rec;
UTL_FILE.FCLOSE(l_file);
l_csv_file_name := 'file.csv';
UTL_FILE.FRENAME(src_location => C_DIRECTORY_PATH, src_filename => l_tmp_file_name, dest_location => C_DIRECTORY_PATH, dest_filename => l_csv_file_name, overwrite => FALSE);
EXCEPTION
WHEN OTHERS THEN
UTL_FILE.FCLOSE(l_file);
UTL_FILE.FREMOVE(location => C_DIRECTORY_PATH, filename => l_tmp_file_name);
END;
end export;
The problem is that I get
1,user1
2,user2
3,user3
4,user4
5,
user5
6,user6
7,user7
8,user8
9,user9
10,user10
11,user11
12,user12
13,user13
14,
user14
15,user15
16,user16
17,user17
18,user19
As you can see, after 4 records the buffer is 'full' so it writes instead
the buffer which is user14 instead of writing all on the same line
Thank you
The problem is not your function as such, it's the test you make after the call:
if l_buffer is NOT NULL then
UTL_FILE.PUT_LINE(l_file, l_buffer);
l_buffer := NULL;
end if;
l_buffer is always populated, it's never null. So the test is always true and you write to the file for each row in the table. You need to test for the length of l_buffer and only write when the length is greater than your limit.
But don't just change the test. You need to unpick the logic of FN_GET_BUFFER() to include the buffer population and flushing in a single subroutine, otherwise you will lose data. Something like this:
FOR rec IN cur_table
LOOP
l_rec := CONVERT(rec.id || ',' || rec.user ,'AL32UTF8');
IF LENGTH(l_buffer) + LENGTH(C_CHRLEN) + LENGTH(l_rec) > C_MAXLEN THEN
-- buffer full, write to file
UTL_FILE.PUT_LINE(l_file, l_buffer);
l_buffer := l_rec;
ELSIF LENGTH(l_buffer) = 0 THEN
-- first record
l_buffer := l_rec;
ELSE
-- buffer not full
l_buffer := _l_buffer || C_CHRLEN || l_rec;
END IF;
END LOOP rec;
if LENGTH(l_buffer) > 0 THEN
-- end of table, write last record
UTL_FILE.PUT_LINE(l_file, l_buffer);
end if;
warning coded wildstyle, not tested

Concatenation of CLOB datatypes in a LOOP in PL/SQL

I am trying to concatenate clobs in a PL/SQL loop and it has been returning null whilst when using DBMS_OUTPUT prints out the loop values and when executing each result of the clobs gives an output as well.
The system is meant to execute an already stored SQL in a table based on the report name passed into it. This particular report has many report names; hence the concatenation of each of the reports. The arguments passed are the report name, version of the report you're interested in, the kind of separator you want, and an argument list for the unknowns in the SQL if any. There are also two main types of SQL; 1 that needs the table_name be replaced with a temp table_name and another that needs an ID be appended to a table_name in the SQL.
please find below the code for the REPREF1 function.
CREATE OR REPLACE FUNCTION REPREF1(P_VER IN VARCHAR2 DEFAULT 'LATEST',
P_SEPARATOR IN VARCHAR2 DEFAULT ', ',
P_ARGLIST IN VAR DEFAULT NULL) RETURN CLOB IS
L_CLOB CLOB;
FUNCTION GET_CLOB(P_REPNAM IN VARCHAR2,
P_VER IN VARCHAR2 DEFAULT 'LATEST',
P_SEPARATOR IN VARCHAR2 DEFAULT ', ',
P_ARGLIST IN VAR DEFAULT NULL) RETURN CLOB IS
---------------------------------------------------------------------------------
-- TITLE - GET_CLOB beta - b.0 DATE 2010Mar12
--
-- DESCRIPTION - A function that return a report based on the report name put in
--
-- USAGE - select get_clob(p_repnam,p_ver, p_separator, var(varay(val_1,...val_n), varay(val_1,...val_n))) FROM dual
-----------------------------------------------------------------------------------------------------------------------------
V_SQL VARCHAR2(32767);
L_RESULT CLOB;
V_TITLE VARCHAR2(4000);
V_REPDATE VARCHAR2(30);
V_CNT NUMBER(2);
V_NUMARG NUMBER(3);
V_CDCRU NUMBER(3);
V_BCNT NUMBER(3);
V_NEWTABDAT VARCHAR2(30);
V_NEWTABLIN VARCHAR2(30);
L_COLLIST VARAY;
V_VER VARCHAR2(6);
N PLS_INTEGER;
V_CNTTAB NUMBER(3);
-- EXEC_SQL_CLOB
FUNCTION EXEC_SQL_CLOB(P_SQL IN VARCHAR2,
P_NUMARG IN NUMBER,
P_COLLIST IN VARAY DEFAULT NULL,
P_ARGLIST IN VARAY DEFAULT NULL,
P_SEPARATOR IN VARCHAR2 DEFAULT '') RETURN CLOB IS
------------------------------------------------------------------------------------------------------
-- TITLE - EXEC_SQL_CLOB beta - b.0 DATE 2010Mar22
--
-- DESCRIPTION - A function that returns a clob value after executing the sql query that is passed into it
--
-- USAGE - select exec_sql_clob(p_sql, p_numarg, var(varay(val_1, val_2,...val_n), varay(val_1, val_2,...val_n))) FROM dual
---------------------------------------------------------------------------------------------------------------
L_CUR INTEGER DEFAULT DBMS_SQL.OPEN_CURSOR;
L_STATUS INTEGER;
V_COL VARCHAR2(4000);
L_RESULT CLOB;
L_COLCNT NUMBER DEFAULT 0;
L_SEPARATOR VARCHAR2(10) DEFAULT '';
V_NUMARG NUMBER(3);
BEGIN
-- parse the query for the report
DBMS_SQL.PARSE(L_CUR, P_SQL, DBMS_SQL.NATIVE);
-- whilst it is not more than 255 per line
FOR I IN 1 .. 255
LOOP
BEGIN
-- define each column in the select list
DBMS_SQL.DEFINE_COLUMN(L_CUR, I, V_COL, 2000);
L_COLCNT := I;
EXCEPTION
WHEN OTHERS THEN
IF (SQLCODE = -1007) THEN
EXIT;
ELSE
RAISE;
END IF;
END;
END LOOP;
-- If query has no bind variables
IF (P_ARGLIST IS NULL) THEN
IF (P_NUMARG = 0) THEN
-- Execute the query in the cursor
L_STATUS := DBMS_SQL.EXECUTE(L_CUR);
LOOP
-- Exit loop when fetch is complete
EXIT WHEN(DBMS_SQL.FETCH_ROWS(L_CUR) <= 0);
L_SEPARATOR := '';
FOR I IN 1 .. L_COLCNT
LOOP
DBMS_SQL.COLUMN_VALUE(L_CUR, I, V_COL);
L_RESULT := L_RESULT || L_SEPARATOR || V_COL;
L_RESULT := REPLACE(REPLACE(L_RESULT, CHR(13) || CHR(10), ' '), CHR(10), ' ');
L_SEPARATOR := P_SEPARATOR;
END LOOP;
L_RESULT := L_RESULT || CHR(13);
END LOOP;
ELSE
RAISE_APPLICATION_ERROR(-20011, ' INCORRECT NUMBER OF ARGUMENTS PASSED IN LIST ');
END IF;
-- Query has bind variables
ELSE
-- Check if the numarg passed is the same has stored in the table
SELECT NUMARG
INTO V_NUMARG
FROM REPVER
WHERE REPCODE = P_SQL;
-- If number of arguments is greater than 0
IF (V_NUMARG > 0) THEN
-- Check if the number of arguments are the same
IF (P_NUMARG = V_NUMARG) THEN
-- Replace the bind variables in the query
FOR J IN 1 .. P_ARGLIST.COUNT
LOOP
DBMS_SQL.BIND_VARIABLE(L_CUR, P_COLLIST(J), P_ARGLIST(J));
END LOOP;
-- Execute the query in the cursor
L_STATUS := DBMS_SQL.EXECUTE(L_CUR);
LOOP
-- Exit loop when fetch is complete
EXIT WHEN(DBMS_SQL.FETCH_ROWS(L_CUR) <= 0);
L_SEPARATOR := '';
FOR I IN 1 .. L_COLCNT
LOOP
DBMS_SQL.COLUMN_VALUE(L_CUR, I, V_COL);
L_RESULT := L_RESULT || L_SEPARATOR || V_COL;
L_RESULT := REPLACE(REPLACE(L_RESULT, CHR(13) || CHR(10), ' '), CHR(10), ' ');
L_SEPARATOR := P_SEPARATOR;
END LOOP;
L_RESULT := L_RESULT || CHR(13);
END LOOP;
ELSE
RAISE_APPLICATION_ERROR(-20011, ' INCORRECT NUMBER OF ARGUMENTS PASSED IN LIST ');
END IF;
ELSE
-- If the number of argument is equal to 0
IF (P_NUMARG = 0) THEN
-- Execute the query in the cursor
L_STATUS := DBMS_SQL.EXECUTE(L_CUR);
LOOP
-- Exit loop when fetch is complete
EXIT WHEN(DBMS_SQL.FETCH_ROWS(L_CUR) <= 0);
L_SEPARATOR := '';
FOR I IN 1 .. L_COLCNT
LOOP
DBMS_SQL.COLUMN_VALUE(L_CUR, I, V_COL);
L_RESULT := L_RESULT || L_SEPARATOR || V_COL;
L_RESULT := REPLACE(REPLACE(L_RESULT, CHR(13) || CHR(10), ' '), CHR(10), ' ');
L_SEPARATOR := P_SEPARATOR;
END LOOP;
L_RESULT := L_RESULT || CHR(13);
END LOOP;
ELSE
RAISE_APPLICATION_ERROR(-20011, ' INCORRECT NUMBER OF ARGUMENTS PASSED IN LIST ');
END IF;
END IF;
END IF;
-- Close cursor
DBMS_SQL.CLOSE_CURSOR(L_CUR);
RETURN L_RESULT;
END EXEC_SQL_CLOB;
BEGIN
-- Check if the version entered is null or latest
IF (P_VER IS NULL)
OR (UPPER(P_VER) = UPPER('LATEST')) THEN
SELECT MAX(VER)
INTO V_VER
FROM REPORT B, REPVER R
WHERE UPPER(REPNAM) = UPPER(P_REPNAM)
AND B.REPREF = R.REPREF;
ELSE
V_VER := P_VER;
END IF;
-- Check if the repname and version entered exists
SELECT COUNT(*)
INTO V_CNT
FROM REPORT B, REPVER R
WHERE UPPER(REPNAM) = UPPER(P_REPNAM)
AND VER = V_VER
AND B.REPREF = R.REPREF;
IF (V_CNT > 0) THEN
-- Store the SQL statement, title and number of arguments of the report name passed.
SELECT REPCODE, REPTITLE, NUMARG, COLLIST
INTO V_SQL, V_TITLE, V_NUMARG, L_COLLIST
FROM REPVER R, REPORT B
WHERE UPPER(REPNAM) = UPPER(P_REPNAM)
AND B.REPREF = R.REPREF
AND VER = V_VER;
V_REPDATE := TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI');
L_RESULT := V_TITLE || ' (' || P_REPNAM || ' version ' || V_VER || ') generated ' || V_REPDATE || CHR(13) || CHR(13);
-- Check for some specific type of queries
SELECT COUNT(*)
INTO V_CDCRU
FROM REPVER R, REPORT B
WHERE CTDDATA = 'Y'
AND UPPER(REPNAM) = UPPER(P_REPNAM)
AND B.REPREF = R.REPREF
AND VER = V_VER;
SELECT COUNT(*)
INTO V_BCNT
FROM REPVER R, BODCREPS B
WHERE BENLIST = 'Y'
AND UPPER(REPNAM) = UPPER(P_REPNAM)
AND B.REPREF = R.REPREF
AND VER = V_VER;
IF (V_CDCRU > 0) THEN
V_NEWTABDATA := 'CT_' || 'DAT_' || P_ARGLIST(1) (P_ARGLIST(1).FIRST);
V_NEWTABLINK := 'CT_' || 'LIN_' || P_ARGLIST(1) (P_ARGLIST(1).FIRST);
-- Check if the tables exist
SELECT COUNT(*)
INTO V_CNTTAB
FROM ALL_TABLES
WHERE TABLE_NAME = V_NEWTABDAT
OR TABLE_NAME = V_NEWTABLIN
AND OWNER = 'SCOTT';
IF (V_CNTTAB > 0) THEN
V_SQL := UPPER(V_SQL);
V_SQL := REPLACE(V_SQL, 'CT_DAT_CRU', V_NEWTABDAT);
V_SQL := REPLACE(V_SQL, 'CT_LIN_CRU', V_NEWTABLIN);
ELSE
V_SQL := 'SELECT ''THE TABLE NOT CREATED YET''
FROM DUAL';
END IF;
END IF;
IF (V_BCNT > 0) THEN
V_SQL := UPPER(V_SQL);
V_SQL := REPLACE(V_SQL, 'LIST', P_ARGLIST(1) (P_ARGLIST(1).LAST));
END IF;
IF (P_ARGLIST IS NULL) THEN
-- execute the query
L_RESULT := L_RESULT || EXEC_SQL_CLOB(V_SQL, V_NUMARG, L_COLLIST, NULL, P_SEPARATOR);
ELSE
N := P_ARGLIST.COUNT;
-- execute the query
L_RESULT := L_RESULT || EXEC_SQL_CLOB(V_SQL, V_NUMARG, L_COLLIST, P_ARGLIST(N), P_SEPARATOR);
END IF;
RETURN L_RESULT;
ELSE
RAISE_APPLICATION_ERROR(-20012, P_REPNAM || ' or ' || P_VER || ' DOES NOT EXIST ');
END IF;
END GET_CLOB;
BEGIN
FOR I IN (SELECT REPNAM
FROM REPORT
WHERE REPREF NOT IN ('R01', 'R02', 'R03', 'R04'))
LOOP
SELECT CONCAT_CLOB(GET_CLOB(I.REPNAM, P_VER, P_SEPARATOR, P_ARGLIST))
INTO L_CLOB
FROM DUAL;
DBMS_OUTPUT.PUT_LINE(I.REPNAM);
-- DBMS_OUTPUT.PUT_LINE (COUNT(i.REPNAM));
END LOOP;
RETURN L_CLOB;
END REPREF1;
/
Cheers,
Tunde
Many thanks APC for making the code look better.
#Robert, the last loop in the code returns null even with the CONCAT_CLOB aggregate function that concatenates clobs.
FOR I IN (SELECT REPNAM
FROM REPORT
WHERE REPREF NOT IN ('R01', 'R02', 'R03', 'R04'))
LOOP
SELECT CONCAT_CLOB(GET_CLOB(I.REPNAM, P_VER, P_SEPARATOR, P_ARGLIST))
INTO L_CLOB
FROM DUAL;
DBMS_OUTPUT.PUT_LINE(I.REPNAM);
END LOOP;
when I try this,
FOR I IN (SELECT REPNAM
FROM REPORT
WHERE REPREF NOT IN ('R01', 'R02', 'R03', 'R04'))
LOOP
L_CLOB := L_CLOB || CHR(13) || GET_CLOB(I.REPNAM, P_VER, P_SEPARATOR, P_ARGLIST);
DBMS_OUTPUT.PUT_LINE(I.REPNAM);
END LOOP;
It also gives null; but this time the dbms output for the repnam are not complete.
Don't know about your code. Here is how it works for me:
Whenever I create a function returning a clob value I do this:
function foo return clob is
l_clob clob;
begin
dbms_lob.createtemporary(lob_loc => l_clob, cache => true, dur => dbms_lob.call);
...
return l_clob;
end;
When concatenating values into a clob I use a function:
procedure add_string_to_clob(p_lob in out nocopy clob
,p_string varchar2) is
begin
dbms_lob.writeappend(lob_loc => p_lob, amount => length(p_string), buffer => p_string);
end;
You have to use
dbms_lob.substr(your clob parameter,start position, length)
e.g
dbms_output('my clob value:' || dbms_lob.substr(your clob parameter,start position, length);
But you can print in a string max 4000 character, you can then use this in a looping function to print 4000 characters in each line.

Resources