How do i pass a {Base64 encoded content} value in a json object. the file that i want to send is a blob type in my Oracle Table.
FOLLOWING snippets of my code
Cursor csr_con_attach is
select file_name documentName,file_blob ,
to_char(created_date,'DD-MON-YYYY')||'T'||to_char(created_date,'HH24:MI:SS') documentDate
From contact.con_attachments
WHERE con_header_id_fk = 2770;
e.g.
For rec_con_attach in csr_con_attach
Loop
v_obj_build :=
json_object_t
('{'
|| '"documentName"' || ':"' || rec_con_attach.documentName || '",'
|| '"blobData"' || ':"' || rec_con_attach.file-blob ||'",'
|| '"documentNotes"' || ':"' || 'Test' ||'",'
|| '"documentDate"' || ':"' || rec_con_attach.documentDate
||'"'||
'}');
end loop;
When i compile this I get an error "wrong number or type of arguments in call to '||" . The rest API that i am calling expects a {Base64 encoded content} for blobData.
I also tried converting blob file to base64encode(using procedure below), but then i get JSONT syntax error ORA-06512 sys.JDOM_T
Declare
l_step pls_integer := 22500; -- make sure you set a multiple of 3 not higher than 24573
l_clob clob;
begin
for i in 0 .. trunc((dbms_lob.getlength(i_blob) - 1 )/l_step)
loop
dbms_output.put_line(i);
l_clob := utl_raw.cast_to_varchar2(utl_encode.base64_encode(dbms_lob.substr(i_blob, l_step, i * l_step + 1)));
end loop;
io_clob := l_clob;
end;
Any help would be appreciated
Thanks
Related
I'm trying to send with webservice POST method a dozens of records to other side but problem is that other side get only one raw of data.
I put cursor and in begin section open cursor and make loop.
I think the problem is somewhere in part UTL_HTTP.read_raw. I have to add something additional or modify function.
Where is the catch, where I'm wrong?
create or replace FUNCTION "TST" return clob is
l_http_request UTL_HTTP.req;
l_http_response UTL_HTTP.resp;
l_buffer_size NUMBER(10) := 20000;
l_line_size NUMBER(10) := 50;
l_lines_count NUMBER(10) := 20;
l_string_request VARCHAR2(32767);
l_line VARCHAR2(128);
l_substring_msg VARCHAR2(20000);
l_raw_data RAW(20000);
l_clob_response CLOB ;
v_clob clob;
url varchar2(200) := 'http://tst';
cursor c_get_prihod is
select IDORIG , IDSUSTAVA , DATUM , SIFAGENCIJE , SIFDRZAVE ,
SIFOSUSL , IZNOSNETO , IZNOSBRUTO , IZNOSPOREZA , SIFHOTELA
from HRS_LASERLINE_PRIHOD
where datum = pms_p.business_date
and sifhotela = pms_p.resort
;
begin
for a in c_get_prihod loop
l_string_request:=
'{
IdOrig:"'||a.IDORIG||'",
IdSustava:"'||a.IDSUSTAVA||'",
Datum:"'||a.DATUM||'",
SifAgencije:"'||a.SIFAGENCIJE||'",
SifDrzave:"'||a.SIFDRZAVE||'",
SifOsUsl:"'||a.SIFOSUSL||'",
IznosNeto:"'||a.IZNOSNETO||'",
IznosBruto:"'||a.IZNOSBRUTO||'",
IznosPoreza:"'||a.IZNOSPOREZA||'",
SifHotela:"'||a.SIFHOTELA||'",
}}'
;
end loop;
l_http_request := UTL_HTTP.begin_request(url , method => 'POST', http_version => 'HTTP/1.1');
utl_http.set_header(l_http_request, 'user-agent', 'mozilla/4.0');
utl_http.set_header(l_http_request, 'content-type', 'application/json');
utl_http.set_header(l_http_request, 'Content-Length', length(l_string_request));
BEGIN
<<request_loop>>
FOR i IN 0..CEIL(LENGTH(l_string_request) / l_buffer_size) - 1 LOOP
l_substring_msg := SUBSTR(l_string_request, i * l_buffer_size + 1, l_buffer_size);
BEGIN
l_raw_data := utl_raw.cast_to_raw(l_substring_msg);
UTL_HTTP.write_raw(r => l_http_request, data => l_raw_data);
EXCEPTION
WHEN NO_DATA_FOUND THEN
EXIT request_loop;
END;
END LOOP request_loop;
l_http_response := UTL_HTTP.get_response(l_http_request);
BEGIN
LOOP
UTL_HTTP.read_raw(l_http_response, l_raw_data, l_buffer_size);
l_clob_response := l_clob_response || UTL_RAW.cast_to_varchar2(l_raw_data);
END LOOP response_loop;
EXCEPTION
WHEN UTL_HTTP.end_of_body THEN
UTL_HTTP.end_response(l_http_response);
END;
<<print_response>>
FOR i IN 0..CEIL(LENGTH(l_clob_response) / l_line_size) - 1
LOOP
l_line := SUBSTR(l_clob_response, i * l_line_size + 1, l_line_size);
EXIT WHEN i > l_lines_count - 1;
END LOOP print_response;
v_clob:= l_clob_response;
return v_clob ;
exception
when utl_http.end_of_body then
utl_http.end_response(l_http_response);
end;
end TST;
Your cursor query might find dozens of rows, but you're doing this:
for a in c_get_prihod loop
l_string_request:= ... ;
end loop;
Each time around that loop you're replacing the entire contents of l_string_request, so however many rows you get and how many times you go around the loop, you'll always end up with that containing only the last row returned by that query. So you only send that single, final, record to the web service.
Assuming the receiving service is expecting an array of records, you might want something like:
begin
l_string_request := '[ ';
for a in c_get_prihod loop
if a.FLAG > 1 then
l_string_request := l_string_request || ', ';
end if;
l_string_request := l_string_request || '{ ';
l_string_request := l_string_request || '"IdOrig": "' || a.IDORIG || '", ';
l_string_request := l_string_request || '"IdSustava": "' || a.IDSUSTAVA ||' ", ';
l_string_request := l_string_request || '"Datum": "' || a.DATUM || '", ';
l_string_request := l_string_request || '"SifAgencije": "' || a.SIFAGENCIJE || '", ';
l_string_request := l_string_request || '"SifDrzave": "' || a.SIFDRZAVE || '", ';
l_string_request := l_string_request || '"SifOsUsl": "' || a.SIFOSUSL || '", ';
l_string_request := l_string_request || '"IznosNeto": "' || a.IZNOSNETO || '", ';
l_string_request := l_string_request || '"IznosBruto": "' || a.IZNOSBRUTO || '", ';
l_string_request := l_string_request || '"IznosPoreza": "' || a.IZNOSPOREZA || '", ';
l_string_request := l_string_request || '"SifHotela": "' || a.SIFHOTELA || '"';
l_string_request := l_string_request || ' }';
end loop;
l_string_request := l_string_request || ' ]';
dbms_output.put_line(l_string_request);
...
where flag is an extra column added to the cursor result as e.g. , ROWNUM AS FLAG - just as a mechanism to know if you are on the first row or a subsequent row (and so need as comma between array elements).
The dbms_output lets you see the entire generated value, which you can manually inspect and/or pass through a JSON validator to check it is what you (and more importantly the receiving service) expect to see. You can still add newlines during the JSON construction for readability, though the service won't care about those.
Depending on how many rows you could have you might need l_string_request to be a CLOB instead of a varchar2. I'd also suggest you explicitly format date/timestamp values, and handle numbers properly if you have any of those.
After modification according to your advice I'm getting this error:
ORA-06502: PL/SQL: numeric or value error ORA-06512: at "OPERA_MARRIOTT.HRS_LASERLINE_BI_PRIHOD", line 87
You would get this if l_clob_response is empty, so perhaps your read loop isn't getting what you expect now. (Which may well be because the request is now invalid JSON, or doesn't match what the service expects). After the UTL_HTTP.get_response() call, see what l_http_response.status_code and l_http_response.reason_phrase show. The status codes are in the documentation.
When I send mail with attachment through procedure using clob to create data.
For small data it's working fine. But for large data it's misbehaving (alignment changing). can you help whether anything needs to done with the code.
create or replace PROCEDURE RPT AS
l_clob clob;
l_bfile bfile;
l_fhandle utl_file.file_type;
l_buffer VARCHAR2(8192);
v_count number;
a_count number;
k_count number;
ka_count number;
currentdate DATE;
non_work_days_count number;
BEGIN
--preparing header
dbms_lob.createtemporary (l_clob, TRUE);
l_clob := l_clob
|| 'Column1'|| ','
|| 'Column2' || ','
|| 'Column3' || ','
|| 'Column4' || ','
|| UTL_TCP.crlf;
for crq in (select col1,col2,col3,col4 from table where id=1 ) loop
/* Prepare Details data using Clob */
l_clob := l_clob
|| to_clob(crq.COl1)|| ','
|| to_clob(crq.COL2) || ','
|| to_clob(crq.COL3) || ','
|| to_clob(crq.COL4) || ','
|| UTL_TCP.crlf;
end loop;
for crq in (select col1,col2,col3,col4 from table where id=2 ) loop
/* Prepare Details data using Clob */
l_clob := l_clob
|| to_clob(crq.COl1)|| ','
|| to_clob(crq.COL2) || ','
|| to_clob(crq.COL3) || ','
|| to_clob(crq.COL4) || ','
|| UTL_TCP.crlf;
end loop;
dbms_output.put_line('Sending mail with attachment ');
ATTACHMENT_SEND(p_to=> 'req#Email.com',
p_from=> 'req#Email.com',
p_subject=> ' Report ',
p_text_msg=>'Hi All',
p_attach_name =>'report_'||sysdate||'.csv',
p_attach_mime =>'text/plain',
p_attach_clob =>l_clob,
p_smtp_host=>'host.com');
dbms_lob.freetemporary(l_clob);
END RPT;
Also I observe when character length reach 32000 it's breaking(leaving remaining data).
The data in the created excel attachment is having alignment issue
After searching a lot, I found a different approach which worked as a gem for me(which can handle lakhs of records). I thought it would help someone someday.
Steps:
1)Created a directory (in which the expected excel is stored).
2)Write the output of the select statement into the above file.
3)Pick the file at above location and send it in the mail attachment.
Note: if any one needs SMTP code part or any assistance let me know
|| concatenation limits you up to 4000 in Oracle SQL while limits to 32000 in PL/SQL. You may try [DBMS_LOB.APPEND][1] procedure to concatenate long CLOBs. So you may try to convert your code to -
CREATE OR REPLACE PROCEDURE RPT AS
l_clob clob;
l_bfile bfile;
l_fhandle utl_file.file_type;
l_buffer VARCHAR2(8192);
v_count number;
a_count number;
k_count number;
ka_count number;
currentdate DATE;
non_work_days_count number;
BEGIN
--preparing header
dbms_lob.createtemporary (l_clob, TRUE);
l_clob := l_clob
|| 'Column1'|| ','
|| 'Column2' || ','
|| 'Column3' || ','
|| 'Column4' || ','
|| UTL_TCP.crlf;
for crq in (select col1,col2,col3,col4 from table where id=1 ) loop
/* Prepare Details data using Clob */
l_clob := DBMS_LOB.APPEND(
DBMS_LOB.APPEND(
DBMS_LOB.APPEND(
DBMS_LOB.APPEND(l_clob
,to_clob(crq.COl1) || ',')
,to_clob(crq.COL2) || ',')
,to_clob(crq.COL3) || ',')
,to_clob(crq.COL4) || ',' || UTL_TCP.crlf);
end loop;
for crq in (select col1,col2,col3,col4 from table where id=2 ) loop
/* Prepare Details data using Clob */
l_clob := DBMS_LOB.APPEND(
DBMS_LOB.APPEND(
DBMS_LOB.APPEND(
DBMS_LOB.APPEND(l_clob
,to_clob(crq.COl1) || ',')
,to_clob(crq.COL2) || ',')
,to_clob(crq.COL3) || ',')
,to_clob(crq.COL4) || ',' || UTL_TCP.crlf);
end loop;
dbms_output.put_line('Sending mail with attachment ');
ATTACHMENT_SEND(p_to => 'req#Email.com',
p_from => 'req#Email.com',
p_subject => ' Report ',
p_text_msg =>'Hi All',
p_attach_name =>'report_'||sysdate||'.csv',
p_attach_mime =>'text/plain',
p_attach_clob =>l_clob,
p_smtp_host =>'host.com');
dbms_lob.freetemporary(l_clob);
END RPT;
[1]: https://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_lob.htm#i997432
As part of a project, I'm working on data mapping of the peoplesoft records and fields in use at our company. There are more than 25K fields that I have to document but it gets tedious and will take a much more time than I have if I did it normal way. So, I wrote a stored procedure to reduce some of the work in documenting translate values.
Here is the code of my stored procedure:
CREATE OR REPLACE PROCEDURE SP_DATAMAPPINGINFO AS
TYPE EmpCurTyp IS REF CURSOR;
newrow_cursor EmpCurTyp;
txtable_cursor EmpCurTyp;
recname varchar2(30);
recdescr varchar2(200);
fieldnum number(3);
fieldname varchar2(30);
fieldescr varchar2(2000);
keyflag varchar2(1);
fieldtype varchar2(20);
distinctcount number(10);
query1_str varchar(300);
query2_str varchar(300);
query3_str varchar(300);
fieldvalue varchar2(200);
hyphen varchar2(5);
txvalue varchar2(200);
fielduse varchar2(500);
tablename varchar2(40);
intertxtabname varchar2(30);
txtablename varchar2(40);
CURSOR get_fields is
select A.RECNAME as "Record", A.RECDESCR as "Record Description"
, B.FIELDNUM as "FieldNum", B.FIELDNAME as "Field", C.DESCRLONG as "Field Description", CASE WHEN
EXISTS(select K.FIELDNAME FROM PSRECFLDDBKEYVW K WHERE K.RECNAME = A.RECNAME AND K.FIELDNAME=B.FIELDNAME)
THEN 'Y' ELSE 'N' END as "Key (Y/N)", DECODE (C.FIELDTYPE,
0, 'Character',
1, 'Long Char',
2, 'Number',
3, 'Signed Number',
4, 'Date',
5, 'Time',
6, 'DateTime',
8, 'Image/ Attachment',
9, 'Image Reference',
'Unknown') as "FieldType"
FROM PSRECDEFN A, PSRECFIELDDB B LEFT JOIN PSDBFIELD C ON (B.FIELDNAME = C.FIELDNAME)
WHERE B.RECNAME = A.RECNAME
AND A.RECNAME IN (select R.RECNAME from PSRECDEFN R, DBA_TABLES T
WHERE ('PS_'||R.RECNAME=T.TABLE_NAME)
AND T.NUM_ROWS > 0
AND R.RECTYPE=0)
order by A.RECNAME, B.FIELDNUM;
BEGIN
OPEN get_fields;
LOOP
FETCH get_fields INTO recname, recdescr, fieldnum, fieldname, fieldescr, keyflag, fieldtype;
fielduse := '';
tablename := 'PS_' || recname;
hyphen := ' - ';
fieldvalue := '';
txvalue := '';
intertxtabname := '';
txtablename := '';
if (fieldname <> '%EMPLID%' and fieldname <> '%DESCR%' and fieldname <> '%COMMENT%') THEN
query1_str := 'select RI.EDITTABLE FROM PSRECDEFN RD, PSRECFIELDDB RI WHERE RD.RECNAME = RI.RECNAME
AND RD.RECNAME = ' || recname || 'AND RI.FIELDNAME = ' || fieldname;
OPEN txtable_cursor FOR query1_str;
FETCH txtable_cursor INTO intertxtabname;
CLOSE txtable_cursor;
query2_str := 'select count(distinct T.' || fieldname || ') FROM ' || tablename;
IF (intertxtabname IS NOT NULL) THEN
txtablename := 'PS_' || intertxtabname;
query3_str := 'select distinct T.' || fieldname || ', TR.DESCR FROM ' || tablename || ' T left join ' || txtablename || ' TR ON T.'
|| fieldname || ' = TR.' || fieldname || ' order by T.' || fieldname;
ELSE
txtablename := '';
query3_str := 'select distinct DT.' || fieldname || ', DTR.XLATLONGNAME FROM ' || tablename || ' DT left join PSXLATITEM DTR on
(DTR.FIELDNAME = ''' || fieldname || ''' and DT.' || fieldname || ' = DTR.FIELDVALE) order by DT.' || fieldname;
END IF;
execute immediate query2_str into distinctcount;
if(distinctcount > 150) THEN
fielduse := 'More than 150';
ELSE
OPEN newrow_cursor FOR query3_str USING 'fieldname';
LOOP
FETCH newrow_cursor INTO fieldvalue, txvalue;
fielduse := fieldvalue || ' - ' || txvalue;
EXIT WHEN newrow_cursor%NOTFOUND;
END LOOP;
CLOSE newrow_cursor;
END IF;
ELSE
fielduse := 'SKIPPING';
END IF;
dbms_output.put_line(recname || ',' || recdescr || ',' || fieldnum || ',' || fieldname || ',' || fieldescr || ',' || keyflag || ',' || fieldtype || ',' || fielduse);
END LOOP;
CLOSE get_fields;
NULL;
END SP_DATAMAPPINGINFO;
The stored proc compiles without any errors but when I execute it, I get the following error:
Error starting at line : 1 in command -
BEGIN SP_DATAMAPPINGINFO; END;
Error report -
ORA-00933: SQL command not properly ended
ORA-06512: at "SYSADM.SP_DATAMAPPINGINFO", line 69
ORA-06512: at line 1
00000 - "SQL command not properly ended"
*Cause:
*Action:
Line 69 in the stored proc is OPEN txtable_cursor FOR query1_str;
I have tried using a bind variable but I still get the error and I might have used it incorrectly.
I may have other issues in the code. It'll be great if you can point those out. We are currently at Oracle 12c
Line 69 in the stored proc is OPEN txtable_cursor FOR query1_str;.
Obviously we can't run your code as we don't have your schema so we can't compile and validate your dynamic SQL. However, looking at your assembled string, the striking thing is the concatenation of the boilerplate text and the columns:
AND RD.RECNAME = ' || recname || 'AND RI.FIELDNAME = ' || fieldname
If you look closely you will spot that there is no space after the quote in 'AND RI.FIELDNAME which means the value of recname is concatenated with AND. That's your ORA-00933 right there.
Once you fix that you'll probably run into ORA-00936: missing expression. From the variable declarations we know recname and fieldname are strings but you are not treating them as strings. The code doesn't include any quotes around the variables so the executed query will treat them as columns or something. So that's something you need to fix:
AND RD.RECNAME = ''' || recname || ''' AND RI.FIELDNAME = ''' || fieldname ||''''
Later on you open the cursor like this:
OPEN newrow_cursor FOR query3_str USING 'fieldname';
So you are passing a parameter to a dynamic query which doesn't include any parameter placeholders. That should throw
ORA-01006: bind variable does not exist
Dynamic SQL is hard because it turns compilation errors into runtime errors. You need to develop a cool eye for looking at your code to spot them. One thing which will help is using logging to record the dynamic statement (DBMS_OUTPUT is better than nothing). Usually when we have the actual statement in front of our eyes it is easy to spot our bloomers. But if we can't at least we can run the query and let the compiler point them out.
How would you create a function in Oracle that has a table as an input parameter and return a string? Here is my attempt but is returning an error:
create or replace type temp_table as object (col_name varchar(100));
/
create or replace type col_table as TABLE of temp_table;
/
create or replace FUNCTION COLUMN_HEADERS
(col_name in col_table)
RETURN VARCHAR2
is
return_string VARCHAR2(4096);
BEGIN
return_string := '';
for i in 1 .. col_name.count loop
return_string := return_string || ' ''' || col_name(i) || ''' as ' || regexp_replace(col_name(i), '[ \+]', '_');
if i < col_name.count then
return_string := return_string || ',';
end if;
end loop;
RETURN return_string;
END;
I get the following:
Error(9,9): PL/SQL: Statement ignored
Error(9,26): PLS-00306: wrong number or types of arguments in call to '||'
Which points to this line:
return_string := return_string || ' ''' || col_name(i) || ''' as ' || regexp_replace(col_name(i), '[ \+]', '_');
My guess is that col_name(i) doesn't return a string but using VALUE() or TO_CHAR() gives me other errors. Does anyone know how to debug this?
When you reference an index in the table using col_name(i), you then also need to reference the object property of that table index as in col_name(i).col_name. In your case you used col_name both as the object property and as your function argument. For clarity you might change that. This compiled for me:
create or replace type temp_table is object (col_name varchar(100));
/
create or replace type col_table is TABLE of temp_table;
/
create or replace FUNCTION COLUMN_HEADERS
(col_name in col_table)
RETURN VARCHAR2
is
return_string varchar2(4096);
BEGIN
return_string := '';
for i in 1 .. col_name.count loop
return_string := return_string || ' ''' || col_name(i).col_name || ''' as ' || regexp_replace(col_name(i).col_name, '[ \+]', '_');
if i < col_name.count then
return_string := return_string || ',';
end if;
end loop;
return return_string;
END;
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How to export the result into different tabs of Excel in Toad for Data Analyst?
I have a pl/sql code where I have two procedures in it (independent of each other) and on executing the procedure I want the output to be written in one excel file but with two worksheets in it.
Ex:
for one procedure the output should be in sheet 1
for next procedure the ouput should be in sheet 2.
can any one help? i am using utl_file
If you really want to tackle this here's some code I picked up somewhere. Warning: for my purposes I abandoned this approach as I found that loading the XML data into Excel was too slow. Loading a comma-separated values file is much faster although you lose the pretty formatting (not important in my case).
To use:
Call WORKBOOK_OPEN to initialize the CLOB that represents the workbook.
Call WORKSHEET_OPEN to create a worksheet in the book.
Use ROW_OPEN to create a row.
Make a series of calls to CREATE_CELL to create and populate cells in the row.
Continue making calls to ROW_OPEN and CREATE_CELL to create rows and cells.
When you're done with a worksheet call WORKSHEET_CLOSE.
If you need another worksheet call WORKSHEET_OPEN again to open another sheet, then use ROW_OPEN and CREATE_CELL to populate it.
When you're done, call WORKBOOK_CLOSE to close the workbook.
Use EXPORT_WORKBOOK_TO_FILE to write the workbook to a file.
Note that EXPORT_WORKBOOK_TO_FILE uses UTL_FILE to write its output, satisfying your requirement to use UTL_FILE.
Share and enjoy.
PROCEDURE WORKBOOK_OPEN(pWorkbook IN OUT NOCOPY CLOB) IS
BEGIN
pWorkbook := '<?xml version="1.0" encoding="ISO-8859-9"?>' || chr(10) ||
'<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"' || chr(10) ||
'xmlns:o="urn:schemas-microsoft-com:office:office"' || chr(10) ||
'xmlns:x="urn:schemas-microsoft-com:office:excel"' || chr(10) ||
'xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"' || chr(10) ||
'xmlns:html="http://www.w3.org/TR/REC-html40">' || chr(10) ||
'<ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">' || chr(10) ||
'<WindowHeight>8580</WindowHeight>' || chr(10) ||
'<WindowWidth>15180</WindowWidth>' || chr(10) ||
'<WindowTopX>120</WindowTopX>' || chr(10) ||
'<WindowTopY>45</WindowTopY>' || chr(10) ||
'<ProtectStructure>False</ProtectStructure>' || chr(10) ||
'<ProtectWindows>False</ProtectWindows>' || chr(10) ||
'</ExcelWorkbook>' || chr(10) ||
'<Styles>' || chr(10) ||
'<Style ss:ID="Default" ss:Name="Normal">' || chr(10) ||
'<Alignment ss:Vertical="Bottom"/>' || chr(10) ||
'<Borders/>' || chr(10) ||
'<Font/>' || chr(10) ||
'<Interior/>' || chr(10) ||
'<NumberFormat/>' || chr(10) ||
'<Protection/>' || chr(10) ||
'</Style>' || chr(10) ||
'<Style ss:ID="s22">' || chr(10) ||
'<Font x:Family="Swiss" ss:Bold="1" ss:Underline="Single"/>' || chr(10) ||
'</Style>' || chr(10) ||
'</Styles>';
END WORKBOOK_OPEN;
PROCEDURE WORKBOOK_CLOSE(pWorkbook IN OUT NOCOPY CLOB) IS
BEGIN
pWorkbook := pWorkbook || '</Workbook>';
END WORKBOOK_CLOSE;
PROCEDURE WORKSHEET_OPEN(pWorkbook IN OUT NOCOPY CLOB,
pstrWorksheet_name IN VARCHAR2) IS
BEGIN
--
-- Create the worksheet
--
pWorkbook := pWorkbook || '<Worksheet ss:Name="' || pstrWorksheet_name || '"><Table>';
END WORKSHEET_OPEN;
PROCEDURE WORKSHEET_CLOSE(pWorkbook IN OUT NOCOPY CLOB) IS
BEGIN
pWorkbook := pWorkbook || '</Table></Worksheet>';
END WORKSHEET_CLOSE;
PROCEDURE ROW_OPEN(pWorkbook IN OUT NOCOPY CLOB) IS
BEGIN
pWorkbook := pWorkbook || '<Row>';
END ROW_OPEN;
PROCEDURE ROW_CLOSE(pWorkbook IN OUT NOCOPY CLOB) IS
BEGIN
pWorkbook := pWorkbook || '</Row>' || chr(10);
END row_close;
PROCEDURE CREATE_CELL(pWorkbook IN OUT NOCOPY CLOB,
pstrCell_contents IN VARCHAR2) IS
BEGIN
pWorkbook := pWorkbook || '<Cell><Data ss:Type="String"> ' ||
pstrCell_contents || ' </Data></Cell>';
END CREATE_CELL;
PROCEDURE EXPORT_WORKBOOK_TO_FILE(pstrDirectory_name IN VARCHAR2,
pstrFilename IN VARCHAR2,
pWorkbook IN CLOB)
IS
nChunk_size CONSTANT BINARY_INTEGER := 32767;
strChunk VARCHAR2(32767);
nPos_chr10 NUMBER;
nWorkbook_len NUMBER;
fHandle UTL_FILE.FILE_TYPE;
nPos NUMBER := 1;
BEGIN
nWorkbook_len := DBMS_LOB.GETLENGTH(pWorkbook);
fHandle := UTL_FILE.FOPEN(pstrDirectory_name, pstrFilename, 'W', nChunk_size);
WHILE nPos < nWorkbook_len LOOP
strChunk := dbms_lob.substr(pWorkbook, nChunk_size, nPos);
EXIT WHEN strChunk IS NULL;
nPos_chr10 := INSTR(strChunk, CHR(10), -1);
IF nPos_chr10 != 0 THEN
strChunk := SUBSTR(strChunk, 1, nPos_chr10 - 1);
END IF;
UTL_FILE.PUT_LINE(fHandle, strChunk, TRUE);
nPos := nPos + LEAST(LENGTH(strChunk)+1, nChunk_size);
END LOOP;
UTL_FILE.FCLOSE(fHandle);
EXCEPTION
WHEN OTHERS THEN
IF UTL_FILE.IS_OPEN(fHandle) THEN
UTL_FILE.FCLOSE(fHandle);
END IF;
RAISE;
END EXPORT_WORKBOOK_TO_FILE;
The common approach is to create the file with the Microsoft XML Spreadsheet (XMLSS), appending the content to a VARCHAR or CLOB.
For instance, to write a cell you would do something like this:
xmlBody := xmlBody || '<Cell><Data ss:Type="String">' || stringContent || '</Data></Cell>';
Now, to open a new worksheet you would do something like:
xmlBody := xmlBody || '<Worksheet ss:Name="' || yourWorksheetName || '"><Table>';
To close it (after appending all your content):
xmlBody := xmlBody || '</Table></Worksheet>';
There are a few utility packages that allow you to do this or you could write your own like I did. You would just have to write functions to open and close elements, for instance, openRow, closeRow, openWorkbook, closeWorkbook, openWorksheet, etc.
Refer to Oracle PL/SQL Utility Library, codename "Alexandria" for more information and examples.