PL/SQL file writing with generic input - oracle

I recently created a PL/SQL program that creates five different pipe delimited files from related data in a database.
I could not find a way to dynamically pull different tabular data in this case cursors, into a generic procedure that would create the files.
Instead I had to create five separate procedures, one for each file, that took in five different cursors, one for each file requirement record selection.
I can't help but think that there has to be a better way. I was looking into reference cursors but I don't think they are exactly what I am looking for.
How can I achieve this in PL/SQL?
I think what I am looking for is some generic type that can take any data from a cursor given any amount of records and record columns and have the ability to query itself to find what data is in it.

Pass the cursor into your procedure as a SYS_REFCURSOR. Then, use DBMS_SQL.TO_CURSOR_NUMBER(); to convert the ref cursor to a DBMS_SQL cursor.
Then, use DBMS_SQL.DESCRIBE_COLUMNS to figure out the columns in the cursor and DBMS_SQL.DEFINE_COLUMN, DBMS_SQL.FETCH_ROWS and DBMS_SQL.VALUE to get the data from the cursor into PL/SQL variables. Then, write your PL/SQL variables to your output file.
Here's some code that puts all that together for you.
DECLARE
l_rc SYS_REFCURSOR;
PROCEDURE dump_cursor (p_rc IN OUT SYS_REFCURSOR) IS
-- Dump the results of p_rc to log
l_cursor INTEGER;
l_column_count INTEGER;
l_column_descriptions SYS.DBMS_SQL.desc_tab;
l_status INTEGER;
l_column_value VARCHAR2 (4000);
l_column_width NUMBER;
l_rec_count NUMBER := 0;
l_line VARCHAR2 (4000);
FUNCTION get_length (l_column_def IN SYS.DBMS_SQL.desc_rec)
RETURN NUMBER IS
l_width NUMBER;
BEGIN
l_width := l_column_def.col_max_len;
l_width := CASE l_column_def.col_type WHEN 12 THEN /* DATE */
20 WHEN 2 THEN /* NUMBER */
10 ELSE l_width END;
-- Don't display more than 256 characters of any one column (this was my requirement -- your file writer probably doesn't need to do this
l_width := LEAST (256, GREATEST (l_width, l_column_def.col_name_len));
RETURN l_width;
END get_length;
BEGIN
-- This is the date format that I want to use for dates in my output
EXECUTE IMMEDIATE 'alter session set nls_date_format=''DD-MON-YYYY HH24:MI:SS''';
l_cursor := sys.DBMS_SQL.to_cursor_number (p_rc);
-- Describe columns
sys.DBMS_SQL.describe_columns (c => l_cursor, col_cnt => l_column_count, desc_t => l_column_descriptions);
l_line := '';
FOR i IN 1 .. l_column_count LOOP
l_column_width := get_length (l_column_descriptions (i));
l_line := l_line || RPAD (l_column_descriptions (i).col_name, l_column_width);
l_line := l_line || ' ';
DBMS_SQL.define_column (l_cursor,
i,
l_column_value,
4000);
END LOOP;
DBMS_OUTPUT.put_line (l_line);
l_line := '';
FOR i IN 1 .. l_column_count LOOP
l_column_width := get_length (l_column_descriptions (i));
l_line := l_line || RPAD ('-', l_column_width, '-');
l_line := l_line || ' ';
DBMS_SQL.define_column (l_cursor,
i,
l_column_value,
4000);
END LOOP;
DBMS_OUTPUT.put_line (l_line);
-- l_status := sys.DBMS_SQL.execute (l_cursor);
WHILE (sys.DBMS_SQL.fetch_rows (l_cursor) > 0) LOOP
l_rec_count := l_rec_count + 1;
l_line := '';
FOR i IN 1 .. l_column_count LOOP
DBMS_SQL.COLUMN_VALUE (l_cursor, i, l_column_value);
l_column_value := TRANSLATE (l_column_value, CHR (10), CHR (200));
l_column_width := get_length (l_column_descriptions (i));
IF l_column_value IS NULL THEN
l_line := l_line || RPAD (' ', l_column_width);
ELSE
l_line := l_line || RPAD (l_column_value, l_column_width);
END IF;
l_line := l_line || ' ';
END LOOP;
DBMS_OUTPUT.put_line (l_line);
END LOOP;
IF l_rec_count = 0 THEN
DBMS_OUTPUT.put_line ('No data found.');
ELSE
DBMS_OUTPUT.put_line (l_rec_count || ' rows returned.');
END IF;
sys.DBMS_SQL.close_cursor (l_cursor);
-- It would be better to store the current NLS_DATE_FORMAT on entry and restore it here, instead of assuming that it was
-- set to DD-MON-YYYY.
EXECUTE IMMEDIATE 'alter session set nls_date_format=''DD-MON-YYYY''';
EXCEPTION
WHEN OTHERS THEN
EXECUTE IMMEDIATE 'alter session set nls_date_format=''DD-MON-YYYY''';
-- Add your own handling here.
END dump_cursor;
-- Tester code, make sure server output is on
BEGIN
OPEN l_rc FOR 'SELECT object_id, object_name, object_type FROM dba_objects WHERE rownum <= 15';
dump_cursor(l_rc);
END;

Related

Oracle program too large (codegen operands) error

I'd like to build shapes in Oracle from several thousand points, but upon running the created code, I get the error:
ORA-06550: program too large (codegen operands)
What limit am I hitting? How can I overcome on it?
A similar code to reproduce the error (it runs or fails in a minute):
declare
s clob;
begin
s := '
declare
type t_x is table of number index by pls_integer;
x t_x;
varr sdo_ordinate_array;
begin
';
for i in 1..23000 loop --21825: ok, 21850: error
s := s || 'x('||to_char(i)||') := 46.709864 + '||to_char(i)||'/23000;';
end loop;
s := s || '
varr := sdo_ordinate_array();
varr.extend(x.count);
for i in 1 .. x.count loop
varr(i) := x(i);
end loop;
end;';
execute immediate s;
end;
Is there a reason why you put everything into a single dynamic statement? It looks really strange.
Try it similar to this:
declare
varr sdo_ordinate_array;
begin
varr := sdo_ordinate_array();
for i in 1..23000 loop
varr.extend;
varr(i) := 46.709864 + i/23000;
end loop;
end;
I don't know your real code but you can also return value from execute immediate as this example. Perhaps it can simplify your problem.
DECLARE
type t_x is table of number index by pls_integer;
x t_x;
str varchar2(100);
BEGIN
FOR i in 10..20 LOOP
str := i|| ' * 2';
EXECUTE IMMEDIATE 'BEGIN :ret := '||str||'; END;' USING OUT x(i);
dbms_output.put_line(x(i));
END LOOP;
END;
It seems, that bulk collect can be sort of a solution to this problem. The following code runs 10x longer, but does not give an error:
declare
s clob;
begin
s := '
declare
type t_x is table of number index by pls_integer;
x t_x;
varr sdo_ordinate_array;
begin
select coord
bulk collect into x
from (';
for i in 1..23000 loop --21825: ok, 21850: error
s := s || 'select '||to_char(i)||' rn, 46.709864 + '||to_char(i)||'/23000 coord from dual union all'||chr(10);
end loop;
s := s || '
select null,null from dual
)
where rn is not null
order by rn;
varr := sdo_ordinate_array();
varr.extend(x.count);
for i in 1 .. x.count loop
varr(i) := x(i);
end loop;
end;';
execute immediate s;
end;

data loss with parallel enabled pipelined function

I have a pipelined function that loads data into file.
The following is the code of function.
CREATE OR REPLACE FUNCTION DATA_UNLOAD
( p_source IN SYS_REFCURSOR,
p_filename IN VARCHAR2,
p_directory IN VARCHAR2
) RETURN dump_ntt PIPELINED PARALLEL_ENABLE (PARTITION p_source BY ANY)
AS
TYPE row_ntt IS TABLE OF VARCHAR2(32767);
v_rows row_ntt;
v_file UTL_FILE.FILE_TYPE;
v_buffer VARCHAR2(32767);
v_sid VARCHAR(255);
v_name VARCHAR2(255);
v_lines PLS_INTEGER := 0;
v_start_dttm TIMESTAMP WITH TIME ZONE:= SYSTIMESTAMP;
v_end_dttm TIMESTAMP WITH TIME ZONE;
c_eol CONSTANT VARCHAR2(1) := CHR(10);
c_eollen CONSTANT PLS_INTEGER := LENGTH(c_eol);
c_maxline CONSTANT PLS_INTEGER := 32767;
BEGIN
--v_sid := lpad(sys_context('USERENV', 'sid'), 10, '0');
v_name:=p_filename;
LOOP
if utl_file.is_open(v_file)
then
utl_file.fclose(v_file);
end if;
v_file := UTL_FILE.FOPEN(p_directory, v_name, 'A', c_maxline);
FETCH p_source BULK COLLECT INTO v_rows LIMIT 100;
FOR i IN 1 .. v_rows.COUNT LOOP
IF LENGTH(v_buffer) + c_eollen + LENGTH(v_rows(i)) <= c_maxline THEN
v_buffer := v_buffer || c_eol || v_rows(i);
ELSE
IF v_buffer IS NOT NULL THEN
UTL_FILE.PUT_LINE(v_file, v_buffer);
END IF;
v_buffer := v_rows(i);
END IF;
END LOOP;
v_lines := v_lines + v_rows.COUNT;
EXIT WHEN p_source%NOTFOUND;
END LOOP;
CLOSE p_source;
UTL_FILE.PUT_LINE(v_file, v_buffer);
UTL_FILE.FCLOSE(v_file);
v_end_dttm := SYSTIMESTAMP;
--PIPE ROW (dump_ot(v_name, p_directory, v_lines, v_sid, v_start_dttm, v_end_dttm));
--RETURN ;
END;
i call the function this way.
SELECT * from table(DATA_UNLOAD(
CURSOR(select /*+ PARALLEL */ a || b || c from sample_table),
'sample.txt',
'99_DIR'));
a real life select that i pass as a parameter to function returns 30000 rows, but when i use the function to load the result into a file some rows are lost. During the execution with PARALLEL hint there are 24 parallel sessions, and i dont want to make it less. My guess is that the problem is in parallel execution, because when i dont use PARALLEL hint no data is lost. Can anyone suggest something to get rid of that problem without removing the hint?
Even though you are creating sample.txt with Append mode - you have 24 parallel sessions each writing to it. I always use unique filenames by appending the SID to your variable:
SELECT sid INTO v_sid FROM v$mystat WHERE ROWNUM = 1;
v_name := p_filename || '_' || v_sid || '.dat';
Depending on the # of parallel sessions you should 1 to many files with the format sample_nnnn.txt where nnnn is the SID number.

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;

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.

Can you help me write a procedure in Oracle to spool data from a table to a CSV file?

I am writing a procedure to create a CSV file with the data in an Oracle table. I used "spool filename;" but an error is coming. Can I use spool in PL/SQL?
I think that there are better ways to implement this on Oracle 10g/11g, but this should work fine on Oracle 9i or higher:
CREATE OR REPLACE PROCEDURE prc_file_mult_column_generate(
p_file_dir VARCHAR2, -- mandatory (Oracle directory name)
p_file_name VARCHAR2, -- mandatory
p_sql_query VARCHAR2, -- Multiple column SQL SELECT statement that needs to be executed and processed
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
/* Open file for append*/
l_file_handle := UTL_FILE.FOPEN(p_file_dir, p_file_name, 'a', 2047); --Append Mode, 2047 chars per line max, possibly increasable
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);
/* Output column names and define them for latter retrieval of data */
DBMS_SQL.DESCRIBE_COLUMNS(l_cursor_handle, l_col_cnt, l_rec_tab); -- get column names
/* Append to file column headers */
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,' ');
/* Append data for each row */
LOOP
EXIT WHEN DBMS_SQL.FETCH_ROWS(l_cursor_handle) = 0; -- no more rows to be fetched
l_current_line := '';
/* Append data for each column */
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);
EXCEPTION
WHEN OTHERS THEN
-- Release resources
IF DBMS_SQL.IS_OPEN(l_cursor_handle) THEN
DBMS_SQL.CLOSE_CURSOR(l_cursor_handle);
END IF;
IF UTL_FILE.IS_OPEN (l_file_handle) THEN
UTL_FILE.FCLOSE (l_file_handle);
END IF;
--RAISE ;
DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.format_error_stack);
END;
/
spool is a sqlplus command. it cannot be used in pl/sql.
it seems that you have been trying a variety of ways to get oracle to do your formatting and file saving. why not have your program that is calling the proc do this work for you?
If you only need the data in a cvs file you can do this:
create a sql file with the query like this:
set feedback off verify off heading off pagesize 0
select field1 || ',' || field2 ... from table;
quit;
/
then call sqlplus from a terminal like this:
sqlplus -S user/password #file.sql> cvsfile.cvs
No, SPOOL is a SQL Plus command so you would have to do this in SQL Plus:
spool myfile.txt
exec myproc
spool off
You would probably also need to set some values before starting the process e.g.
set pagesize 0 linesize 1000 trimspool on
... to get the correct formatting.
Here are a couple of links you might find helpful:
A PL/SQL Tutorial and SQL*Plus User Guide (11g)
Creating an ascii file: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:459020243348 and http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:68212348056 and http://www.oracle-developer.net/display.php?id=425 .

Resources