How can I write a re-usable function to return CSV output from a query?
-
07-07-2021 - |
Question
In a number of places in my applications, I need to display data as CSV.
For this, I usually loop through a cursor and build the CSV.
for j in c_get_mydata loop
v_CSV := v_CSV || ', ' || j.column_value;
end loop;
How can I write a re-usable function that could accept a query and return a CSV string? Do you recommend having this as a separate function or is it better to process each query like above? If you recommend having it as a separate function, do you recommend that the function take the string of the query or the resultant cursor as input?
EDIT:
Clarification: I want to display a single column of data as one row, separated by commas.
E.g.: Tom, Dick, Harry, Sally
I'm not looking to display multi-column data on multiple rows as:
Tom, 18, London
Dick, 22, New York
Harry, 16, San Fransisco
Sally, 18, Paris
EDIT 2:
I found out about the collect function:
select collect(employee_name)
from employees
This returns me a dataset, but how can I convert it into a string?
Solution
The simplest option would generally by to use the DBMS_SQL
package. Tom Kyte has a good example with his dump_csv
procedure. Of course, you probably want to write the results to a CLOB rather than writing it to a file using utl_file
but that's a relatively easy tweak to make.
OTHER TIPS
Old question and already correctly answered I know, but as this is one of the top Google hits for "oracle cursor to csv" and I've just written something to do this, I thought I'd post a new answer.
From Oracle 11.2 onwards DBMS_SQL can handle a normal ref cursor rather than just a string as in Tom Kyte's 2000 solution. You ingest it into DBMS_SQL using
l_cursor_id := dbms_sql.to_cursor_number(your_refcursor);
(l_cursor_id
will then contain a meaningless generated number that DBMS_SQL uses to track its corresponding internal values.)
Since the ref cursor was already opened and parsed, you skip those steps and call
dbms_sql.describe_columns(l_cursor_id, l_col_count, l_cursor_columns);
where l_cursor_columns
(an OUT parameter that DBMS_SQL populates for you) is a dbms_sql.desc_tab
, which is an array of column definitions populated with an entry for each column in the query and containing the column name, datatype etc, and l_col_count
somewhat redundantly contains the same value as l_cursor_columns.count
. You then need to loop through this array calling dbms_sql.define_column
to tell DBMS_SQL to handle each one using the datatype it just told you, a rather verbose step I can never quite see the point of. (It does allow you to treat all '%CHAR%'
types as varchar2
etc so I guess it allows some flexibility.)
Then it's just a matter of calling dbms_sql.fetch_rows
in a loop and processing the results. I put this in a pipelined table function which accepts a ref cursor argument, so you get something like this:
select column_value
from table(csv.report(cursor(
select * from dept -- << Use any query here
)));
COLUMN_VALUE
----------------------------------------------
10,ACCOUNTING,NEW YORK
20,RESEARCH,DALLAS
30,SALES,CHICAGO
40,OPERATIONS,BOSTON
4 rows selected.
Full writeup and code here: www.williamrobertson.net/documents/refcursor-to-csv.shtml