Hi, Tom.
I recently saw an answer you gave with respect to creating XML from a SQL query.
Here is the code that I copied from your answer.
==========================================================
create or replace package xml_handler_pack is
procedure xml_data( p_sql in varchar2,
p_max_rows in number );
end xml_handler_pack;
/
create or replace package body xml_handler_pack is
procedure print_xml(p_xml in out nocopy clob) is
l_xml varchar2(30000);
l_line varchar2(2000);
l_amt pls_integer := 30000;
l_idx pls_integer := 1;
begin
for i in 1 .. ceil(dbms_lob.getlength(p_xml)/l_amt) loop
l_xml := dbms_lob.substr(p_xml, l_amt, l_idx);
loop
exit when l_xml is null;
l_line := substr(l_xml, 1, instr(l_xml, chr(10))-1);
dbms_output.put_line('|' || l_line);
l_xml := substr(l_xml, instr(l_xml, chr(10))+1);
end loop;
end loop;
end print_xml;
procedure xml_data( p_sql in varchar2,
p_max_rows in number ) is
l_ctx dbms_xmlquery.ctxType;
l_xml clob;
begin
dbms_lob.createtemporary( l_xml, true, dbms_lob.session );
l_ctx := dbms_xmlquery.newContext( p_sql );
-- setup the context
dbms_xmlquery.setRaiseNoRowsException( l_ctx,true );
dbms_xmlquery.setMaxRows( l_ctx, p_max_rows );
-- get the xml fm the context
l_xml := dbms_xmlquery.getXML( l_ctx );
-- print out the xml result set
print_xml( l_xml );
-- determine the no of rows processed
dbms_output.put_line( '' );
dbms_output.put_line( 'No. of rows processed: ' ||
dbms_xmlquery.getnumrowsprocessed( l_ctx ) );
dbms_xmlquery.closeContext(l_ctx);
exception
when others then
dbms_xmlquery.closeContext(l_ctx);
end xml_data;
end xml_handler_pack;
/
==========================================================
I then ran this code as in the following:
set serveroutput on size 1000000
begin
xml_handler_pack.xml_data('&query', &num_rows_desired);
end;
/
This code displayed an XML listing of my SQL result set.
However, I've noticed that only non-null columns are being
displayed. Any columns that are NULL are not shown.
Here's an example of what I mean.
==========================================================
SQL> create table t (c1 number, c2 number);
SQL> Table created
SQL> insert into t values (1,1);
SQL> insert into t values (2,2);
SQL> insert into t values (3,3);
SQL> insert into t values (NULL,NULL);
SQL> insert into t values (5,NULL);
SQL> insert into t values (NULL,6);
SQL> commit;
SQL> select * from t;
C1 C2
---------- ----------
1 1
2 2
3 3
5
6
6 rows selected.
SQL> @run_xml_handler_pack
Enter value for query: select * from t
Enter value for num_rows_desired: 1000
|<?xml version = '1.0'?>
|<ROWSET>
| <ROW num="1">
| <C1>1</C1>
| <C2>1</C2>
| </ROW>
| <ROW num="2">
| <C1>2</C1>
| <C2>2</C2>
| </ROW>
| <ROW num="3">
| <C1>3</C1>
| <C2>3</C2>
| </ROW>
| <ROW num="4"/>
| <ROW num="5">
| <C1>5</C1>
| </ROW>
| <ROW num="6">
| <C2>6</C2>
| </ROW>
|</ROWSET>
No. of rows processed: 6
PL/SQL procedure successfully completed.
==========================================================
ROW num="4" shows no values at all because both columns in this row are NULL.
And ROW num="5" shows only the non-NULL C1 column whereas ROW num="6" shows
only the non-NULL C2 column.
1) Is there some way to have Oracle display the NULL columns as well as the non-NULL columns
in the generated XML listing?
2) Is there a way to re-name the titles "ROWSET" and "ROW" and "num=" in the XML listing?
3) I saw on your website how to create an ASCII file from this generated XML listing.
I have a need to automatically FTP such an XML file to some address. I realize
that I can do this via a Bourne-Shell script (on UNIX) but I was wondering if
Oracle is able to FTP this XML file directly?
Thank you in advance for your help.
I asked Sean Dillon, our local XML Technologist, to take a look at this and here's what he had to say:
---------------------------
Most of what you are asking is INCREDIBLY easy to do. Not only can you do what you asked for but there are a variety of other features available to DBMS_XMLQuery, just check out Chapter 20 in the XML API Reference for Oracle9i.
1) Is there some way to have Oracle display the NULL columns as well as the non-NULL columns in the generated XML listing? Yes, there is, although the XDK places a boolean attribute inside the element that has the null value (i.e., <ROW num="4"><C1 NULL="TRUE"><C2 NULL="TRUE"></ROW>)
2) Is there a way to re-name the titles "ROWSET" and/or "ROW" and "num=" in the generated XML? Here's a modified example of your code from above:
SQL> create or replace package xml_handler_pack is
2 procedure xml_data( p_sql in varchar2,
3 p_max_rows in number,
4 p_num_val in varchar2 default 'num',
5 p_show_null_cols in boolean default false,
6 p_rowsettag in varchar2 default 'ROWSET',
7 p_rowtag in varchar2 default 'ROW' );
8 end xml_handler_pack;
9 /
Package created.
SQL> create or replace package body xml_handler_pack is
2
3 procedure print_xml(p_xml in out nocopy clob) is
4 l_xml varchar2(30000);
5 l_line varchar2(2000);
6 l_amt pls_integer := 30000;
7 l_idx pls_integer := 1;
8 begin
9 for i in 1 .. ceil(dbms_lob.getlength(p_xml)/l_amt) loop
10 l_xml := dbms_lob.substr(p_xml, l_amt, l_idx);
11 loop
12 exit when l_xml is null;
13 l_line := substr(l_xml, 1, instr(l_xml, chr(10))-1);
14 dbms_output.put_line('|' || l_line);
15 l_xml := substr(l_xml, instr(l_xml, chr(10))+1);
16 end loop;
17 end loop;
18 end print_xml;
19
20
21 procedure xml_data( p_sql in varchar2,
22 p_max_rows in number,
23 p_num_val in varchar2 default 'num',
24 p_show_null_cols in boolean default false,
25 p_rowsettag in varchar2 default 'ROWSET',
26 p_rowtag in varchar2 default 'ROW' ) is
27 l_ctx dbms_xmlquery.ctxType;
28 l_xml clob;
29 begin
30 dbms_lob.createtemporary( l_xml, true, dbms_lob.session );
31 l_ctx := dbms_xmlquery.newContext( p_sql );
32
33 -- setup the context
34 dbms_xmlquery.setRaiseNoRowsException( l_ctx,true );
35 dbms_xmlquery.setMaxRows( l_ctx, p_max_rows );
36 dbms_xmlquery.setRowsetTag( l_ctx, p_rowsettag );
37 dbms_xmlquery.setRowTag( l_ctx, p_rowtag );
38 dbms_xmlquery.useNullAttributeIndicator( l_ctx, p_show_null_cols );
39 if p_num_val is null then
40 dbms_xmlquery.useNullAttributeIndicator( l_ctx, p_show_null_cols );
41 elsif p_num_val = 'num' then
42 null;
43 else
44 dbms_xmlquery.setRowIdAttrName( l_ctx, p_num_val );
45 end if;
46
47 -- get the xml fm the context
48 l_xml := dbms_xmlquery.getXML( l_ctx );
49
50 -- print out the xml result set
51 print_xml( l_xml );
52
53 -- determine the no of rows processed
54 dbms_output.put_line( '' );
55 dbms_output.put_line( 'No. of rows processed: ' ||
56 dbms_xmlquery.getnumrowsprocessed( l_ctx ) );
57
58 dbms_xmlquery.closeContext(l_ctx);
59 exception
60 when others then
61 dbms_xmlquery.closeContext(l_ctx);
62 end xml_data;
63
64 end xml_handler_pack;
65 /
Package body created.
SQL> drop table t;
Table dropped.
SQL> create table t (c1 number, c2 number);
Table created.
SQL> insert into t values (1,1);
1 row created.
SQL> insert into t values (2,2);
1 row created.
SQL> insert into t values (3,3);
1 row created.
SQL> insert into t values (NULL,NULL);
1 row created.
SQL> insert into t values (5,NULL);
1 row created.
SQL> insert into t values (NULL,6);
1 row created.
SQL> commit;
Commit complete.
SQL> select * from t;
C1 C2
---------- ----------
1 1
2 2
3 3
5
6
6 rows selected.
SQL> -- two examples, first your original then a new one w/ some of your requested functionality
SQL> begin
2 xml_handler_pack.xml_data( 'select * from t', 1000 );
3 xml_handler_pack.xml_data( p_sql => 'select * from t',
4 p_max_rows => 1000,
5 p_num_val => null,
6 p_show_null_cols => true,
7 p_rowsettag => 'VALUESET',
8 p_rowtag => 'VALUES' );
9 end;
10 /
|<?xml version = '1.0'?>
|<ROWSET>
| <ROW num="1">
| <C1>1</C1>
| <C2>1</C2>
| </ROW>
| <ROW num="2">
| <C1>2</C1>
| <C2>2</C2>
| </ROW>
| <ROW num="3">
| <C1>3</C1>
| <C2>3</C2>
| </ROW>
| <ROW num="4"/>
| <ROW num="5">
| <C1>5</C1>
| </ROW>
| <ROW num="6">
| <C2>6</C2>
| </ROW>
|</ROWSET>
No. of rows processed: 6
|<?xml version = '1.0'?>
|<VALUESET>
| <VALUES num="1">
| <C1>1</C1>
| <C2>1</C2>
| </VALUES>
| <VALUES num="2">
| <C1>2</C1>
| <C2>2</C2>
| </VALUES>
| <VALUES num="3">
| <C1>3</C1>
| <C2>3</C2>
| </VALUES>
| <VALUES num="4">
| <C1 NULL="TRUE"/>
| <C2 NULL="TRUE"/>
| </VALUES>
| <VALUES num="5">
| <C1>5</C1>
| <C2 NULL="TRUE"/>
| </VALUES>
| <VALUES num="6">
| <C1 NULL="TRUE"/>
| <C2>6</C2>
| </VALUES>
|</VALUESET>
No. of rows processed: 6
3) Can Oracle FTP files automatically? Absolutely, you can use a Java Stored Procedure to do this for you. Mark Piermarini, a teammate of mine, co-authored a book entitled "Oracle9i Java Programming" by Wrox Press Ltd. In this book, there's an example of FTP'ing from the database using a Java stored procedure. You could simply write a PL/SQL wrapper to this Java stored procedure, and then you can FTP any time you need to. You could also mix this with DBMS_JOB to automate the sending of files to another FTP site right out of the database with no user intervention whatsoever!
One other thing I wanted to mention, since you are generating XML from the database, and you are using Oracle9iR2, you can also use SQLX to generate instead of the XML SQL Utility (XSU). SQLX is an emerging standard which consists of SQL operators for generating XML from relational databases. The XSU will work just fine, but SQLX gives you more granular control of what is generated.
SQL> select xmlelement("ROWSET",
2 xmlagg(xmlelement("ROW",
3 xmlattributes(rownum as "num"),
4 xmlforest(c1, c2)))) as "RESULT"
5 from t;
RESULT
----------------------------------------------------------------------------------------------------
<ROWSET>
<ROW num="1">
<C1>1</C1>
<C2>1</C2>
</ROW>
<ROW num="2">
<C1>2</C1>
<C2>2</C2>
</ROW>
<ROW num="3">
<C1>3</C1>
<C2>3</C2>
</ROW>
<ROW num="4"/>
<ROW num="5">
<C1>5</C1>
</ROW>
<ROW num="6">
<C2>6</C2>
</ROW>
</ROWSET>
You can read more about SQLX and SQLX operators here: </code>
http://otn.oracle.com/docs/products/oracle9i/doc_library/release2/appdev.920/a96620/xdb12gen.htm#1027862
Hope that helps!
_smd_
_____________________________________________________________________
Sean Dillon - Principal Technologist, Oracle Corporation
Author "Beginning Oracle Programming"
http://www.amazon.com/exec/obidos/ASIN/186100690X <code>