HOWTO: Consume Anydata via XMLType (and back)

This was a small mind exercise on the OakTable website (OakTable Challenge)for a person regarding how to go from a relational table to anydata datatype table and back, which I, of course, approached via an “XMLType” of way thinking. Probably the whole thing is not that practical and/or can be optimized in various ways, but as mentioned it was a small exercise. It demonstrates though, that an XMLType can easily consume and represent an Anydata datatype. The following might help people to solve some problems and/or trigger a solution.

Be-aware that some of the syntax used is deprecated and not at all guaranteed or even supported on the latest database releases…

The example can also be found on the OTN XMLDB Forum, if not only it is probably more readable on that website: “Mind exercise – Anydata and XMLType Datatypes

--
-- Create a relational table with content
-- 
CREATE TABLE tab01 
( nummer NUMBER(2)
, datum  DATE
, string varchar2(40)
);
--
TABLE TAB02 created.
--
-- Insert some data
--
INSERT INTO tab01 VALUES (1, sysdate, 'a small string');
INSERT INTO tab01 VALUES (2, sysdate+1, 'a little bit bigger string');
--
commit;
--
-- Show the output when converted to an XML document while using XMLTYPE()
--
SELECT xmltype(cursor(SELECT nummer, datum, string FROM tab01)) 
FROM dual;
--
XMLTYPE(CURSOR(SELECTNUMMER,DATUM,STRINGFROMTAB01))
---------------------------------------------------
< ?xml version="1.0"?>
<rowset>
 <row>
  <nummer>1</nummer>
  <datum>26-10-11</datum>
  <string>a small string</string>
 </row>
 <row>
  <nummer>2</nummer>
  <datum>27-10-11</datum>
  <string>a little bit bigger string</string>
 </row>
</rowset>
--

It is very easy to go from an XML instance format back to a relational format via the XMLTABLE function, which supports XPath V2 and XQuery V1, if needed…

--
-- Show how you can convert back from XML content into relational while using XMLTABLE
--
SELECT t.nummer
     , t.datum
     , t.string
  FROM xmltable 
       ('/ROWSET/ROW'
        PASSING xmltype(dbms_xmlgen.getxml('select * from tab01'))
        COLUMNS 
          nummer NUMBER(2)      path 'NUMMER'
        , datum  DATE           path 'DATUM'
        , string VARCHAR2(40) path 'STRING'
      ) t;
--
NUMMER                 DATUM                     STRING
---------------------- ------------------------- -------------------------------
1                      11-10-26                  a small string
2                      11-10-27                  a little bit bigger string
--

Now create a table with can hold an ANYDATA datatype and insert XML data based on relational table content. This has the advantage that due to Oracle’s canonical format, you know the datatypes and the format of the table.

--
-- Create a second table with an ANYDATA datatype column
--
CREATE TABLE tab02 
(UNKNOWN anydata);
--
TABLE TAB02 created.
--
-- Demonstrate the output of the XML content what will be inserted in the ANYDATA column
--
SELECT xmltype(dbms_xmlgen.getxml('select * from tab01')).EXTRACT('/ROWSET').getStringVal() 
FROM dual ;
--
XMLTYPE(DBMS_XMLGEN.GETXML('SELECT*FROMTAB01')).EXTRACT('/ROWSET').GETSTRINGVAL()
--------------------------------------------------------------------------------
<rowset><row><nummer>1</nummer><datum>26-10-11</datum>
<string>a small string</string></row><row><nummer>2</nummer>
<datum>27-10-11</datum><string>a little bit bigger string</string>
</row></rowset>
--

It is a shame that it looks like that the “sys.anyData.ConvertClob()” method still isn’t supported… Therefore the following gave an error…

--
-- Is the method sys.anyData.ConvertClob() still not supported...?
-- A shame really, if not only that XML is almost always bigger than varchar2 in size
--
INSERT INTO tab02 
(UNKNOWN)
VALUES
(sys.anyData.ConvertClob(xmltype(dbms_xmlgen.getxml('select * from tab01')).EXTRACT('/ROWSET').getCLOBVal()) );
--
Error starting at line 67 IN command:
 
INSERT INTO tab02
(UNKNOWN)
VALUES
(sys.anyData.ConvertClob(xmltype(dbms_xmlgen.getxml('select * from tab01')).EXTRACT('/ROWSET').getCLOBVal()) )
 
Error report:
 
SQL Error: ORA-22370: incorrect usage OF method AnyData INSERT 
           22370. 00000 -  "incorrect usage of method %s"
*Cause:    This method OF SYS.AnyType OR SYS.AnyData OR SYS.AnyDataSet IS
           being used inappropriately.
*Action:   CHECK the documentation FOR correct usage.

So the “solution” is now limited due to the VARCHAR2 approach, but the following syntax works…

--
-- Switching back to using to ANYDATA method - sys.anyData.ConvertVarchar2()
--
INSERT INTO tab02 
(UNKNOWN)
VALUES
(sys.anyData.ConvertVarchar2(xmltype(dbms_xmlgen.getxml('select * from tab01')).EXTRACT('/ROWSET').getStringVal()) );
--
1 ROWS inserted.
-- 
commit;
--
commited.
--
-- Show sizes and anydata datatypes
--
SELECT t.UNKNOWN.gettypeName() typeName FROM tab02 t;
--
TYPENAME
--------------------------
SYS.VARCHAR2
--
SELECT vsize(UNKNOWN) FROM tab02;
--
VSIZE(UNKNOWN)
----------------------
291
--

To demonstrate how easy it is to convert back, while using the XMLType datatype, from AnyData back into relational data…

-- Converting back to relational - Whats now inserted in the ANYDATA column
-- 
SELECT xmltype(t.UNKNOWN) content FROM tab02 t;
--
--
-- Original XML brackets have been replaced by lt and gt escape codes
--
 
CONTENT
------------------------------------------------------------
<anydata>& lt;ROWSET& gt;
  & lt;ROW& gt;
    & lt;NUMMER& gt;1& lt;/NUMMER& gt;
    & lt;DATUM& gt;26-10-11& lt;/DATUM& gt;
    & lt;STRING& gt;a small string& lt;/STRING& gt;
  & lt;/ROW& gt;
  & lt;ROW& gt;
    & lt;NUMMER& gt;2& lt;/NUMMER& gt;
    & lt;DATUM& gt;27-10-11& lt;/DATUM& gt;
    & lt;STRING& gt;a little bit bigger string& lt;/STRING& gt;
  & lt;/ROW& gt;
& lt;/ROWSET& gt;
</anydata>
--

To avoid those annoying escape codes so you are able to consume the actual XML format again, you will have to use DBMS_XMLGEN, but the overload parameter doesn’t work while using SQL, so as an alternative the representative number value is used, in this case “1″…

--
-- Some possible syntax alternatives to go back to an usable XMLTYPE datatype
--
-- dbms_xmlgen.ENTITY_DECODE does NOT WORK...
-- 
-- select dbms_xmlgen.CONVERT(xmlserialize(content xmltype(t.unknown) as CLOB), dbms_xmlgen.ENTITY_DECODE ) content from tab02 t;
--
SELECT dbms_xmlgen.CONVERT(xmlserialize(content xmltype(t.UNKNOWN) AS CLOB), 1 ) content 
FROM tab02 t;
--
SELECT dbms_xmlgen.CONVERT(xmlserialize(document xmltype(t.UNKNOWN) ), 1 ) content 
FROM tab02 t;
--
SELECT dbms_xmlgen.CONVERT(to_clob(xmltype(t.UNKNOWN)), 1 ) content 
FROM tab02 t;
--
-- So conversion back to normal needed
--  
CONTENT
------------------------------------------------
<anydata><rowset>
  <row>
    <nummer>1</nummer>
    <datum>26-10-11</datum>
    <string>a small string</string>
  </row>
  <row>
    <nummer>2</nummer>
    <datum>27-10-11</datum>
    <string>a little bit bigger string</string>
  </row>
</rowset>
</anydata>
-- 
CONTENT
------------------------------------------------
<anydata><rowset>
  <row>
    <nummer>1</nummer>
    <datum>26-10-11</datum>
    <string>a small string</string>
  </row>
  <row>
    <nummer>2</nummer>
    <datum>27-10-11</datum>
    <string>a little bit bigger string</string>
  </row>
</rowset>
</anydata>
-- 
CONTENT
------------------------------------------------
<anydata><rowset>
  <row>
    <nummer>1</nummer>
    <datum>26-10-11</datum>
    <string>a small string</string>
  </row>
  <row>
    <nummer>2</nummer>
    <datum>27-10-11</datum>
    <string>a little bit bigger string</string>
  </row>
</rowset>
</anydata>
--

Knowing the outcome now is actually in an XML format, we are now able to do the AnyData datatype to Relational format in one go while using the XMLTABLE function.

--
-- Convert back to relational output while using the to xml converted ANYDATA content as input
--
SELECT t.nummer
     , t.datum
     , t.string
  FROM tab02 t
     , xmltable 
       ('/ANYDATA/ROWSET/ROW'
        PASSING xmltype(dbms_xmlgen.CONVERT(to_clob(xmltype(t.UNKNOWN)), 1 ))
        COLUMNS 
          nummer NUMBER(2)      path 'NUMMER'
        , datum  DATE           path 'DATUM'
        , string VARCHAR2(40) path 'STRING'
      ) t;
--
 
NUMMER                 DATUM                     STRING
---------------------- ------------------------- ---------------------------
1                      11-10-26                  a small string
2                      11-10-27                  a little bit bigger string
--

As mentioned, I hope this example might help people to solve some problems and/or trigger a solution. Despite that, this example shows that it is fairly easy, via embedding an Anydata datatype in an XMLType, to show the content of the Anydata datatype.

HTH.

M.