Oracle XML capabilities in Oracle Database 11gR2, part 3

This third part of a series on Oracle XML capabilities explains how to ingest, shred and publish XML data in Oracle Database.

This is part 3 of a three-part series on Oracle XML capabilities in Oracle Database 11gR2. In part 1, McCormick introduced the reader to XML capabilities in Oracle Database and set up a scenario in which a business wants to pull XML documents into its database. Part 2 detailed how that XML data could be used and queried to gain business information. In part 3, McCormick will explain how to shred XML data into relational tables and publish...

relational data as XML.

Oracle database professionals must often expose data for relational applications, whether it be document-centric for unstructured XML or data-centric for structured XML. Fortunately, a single instance of data can be stored as XML and then either exposed as rows in relational tables or stored relationally and exposed as nodes in an XML document. In this article, we will learn how to shred XML data into relational tables and publish relational data as XML.

Shredding Oracle XML data into relational tables  

The ABC Gum company’s data integration hub receives source system purchase orders as XML documents (Listing 1).  The company is looking for a simple solution within the database to accept well-formed XML documents and store the data relationally in order for its SQL-based hub maintenance application to perform data manipulation operations before the data is published as XML to downstream applications.

Listing 1. Purchase Order XML Document

 
 

  <PurchaseOrder>

    <OrderNumber>11111</OrderNumber>

    <OrderDate>2010-01-15</OrderDate>

    <CustomerName>Joe Smith</CustomerName>

    <User>JSMIT</User>

    <SpecialInstructions>Air Mail</SpecialInstructions>

    <LineItems>

      <LineItem ItemNumber="1">

        <Description>Bubble Gum</Description>

        <Part Id="7155" UnitPrice="19.95" Quantity="5">

        </Part>

      </LineItem>

      <LineItem ItemNumber="2">

        <Description>Blow Pops</Description>

        <Part Id="3742" UnitPrice="15.95" Quantity="2">

        </Part>

      </LineItem>

    </LineItems>

  </PurchaseOrder>

 

 

Notice the XML document contains a LineItem collection (repeating elements) analogous to a master-detail model.

We’ll begin by creating a normalized data model with two relational tables (Listing 2) to match the complex purchase order structure. The first table will contain master purchase order information and the second will contain detailed line item data. Notice there are no XML constructs in the table definitions.

Listing 2. Create Tables

create table abc_purchase_order_table (order_number         number(5) primary key

                                      ,order_date           date

                                      ,customer_name        varchar2(30)

                                      ,userid               varchar2(10)

                                      ,special_instructions varchar2(80)

                                      );

 

create table abc_lineitem_table  (order_number         number(5)

                                 ,foreign key (order_number)

                                    references abc_purchase_order_table (order_number) on delete cascade

                                 ,itemno               number(10)

                                    ,primary key (order_number, itemno)

                                 ,description          varchar2(25)

                                 ,partno               varchar2(14)

                                 ,unitprice            number(8,4)

                                 ,quantity             number(12,2)

                                 );

 

Next, we’ll create a procedure (Listing 3) to ingest and shred XML data into our newly created relational tables. Here is where we introduce XML constructs. We’ll define an input parameter as XMLType, an abstract native SQL data type for XML data. Insert statements can now reference the input as an XML object and use an XMLTable function to map XML data constructs, such as elements, to relational constructs, such as columns. 

In our use case, in which we have a well-formed XML document, we have chosen a non-schema based XMLType view that is not constrained to a registered XML schema and stronger typing. However, if performance is the primary design goal and complete XML-to-relational mapping is required, a schema-based approach will offer the best query performance. 

Listing 3. Create Procedure

create or replace procedure shred_abc_purchase_order(p_abc_purchase_order xmltype) as

begin

  insert into abc_purchase_order_table (order_number ,order_date ,customer_name ,userid ,special_instructions)

    select * from xmltable('$po/PurchaseOrder' passing p_abc_purchase_order AS "po"

                  columns  order_number         number  (5)    path 'OrderNumber'

                          ,order_date           date           path 'OrderDate'

                          ,customer_name        varchar2(30)   path 'CustomerName'

                          ,userid               varchar2(10)   path 'User'

                          ,special_instructions varchar2(80)   path 'SpecialInstructions'

                          );

  insert into abc_lineitem_table (order_number, itemno, description, partno, quantity, unitprice)

    select t.order_number, li.itemno, li.description, li.partno, li.quantity, li.unitprice

    from xmltable('$po/PurchaseOrder' passing p_abc_purchase_order as "po"

                  columns  order_number number(5) path 'OrderNumber'

                          ,lineitem xmltype path 'LineItems/LineItem'

                 ) t

        ,xmltable('LineItem' passing t.lineitem

                  columns  itemno      number(10)   path '@ItemNumber'

                          ,description varchar2(25) path 'Description'

                          ,partno      varchar2(14) path 'Part/@Id'

                          ,unitprice   number(12,2) path 'Part/@UnitPrice'

                          ,quantity    number(12,2) path 'Part/@Quantity'

                 ) li;

  commit;

end;

 

Continuing on, we’re ready to load our XML data (Listing 4). For simplicity’s sake, we’ll pass a single XML document defined within an anonymous block to our newly created procedure. The procedure call is no different from passing non-XML data. 

Listing 4. Load XML

declare

  v_abc_purchase_order xmltype := xmltype('<PurchaseOrder>

                                             <OrderNumber>11111</OrderNumber>

                                             <OrderDate>2010-01-15</OrderDate>

                                             <CustomerName>Joe Smith</CustomerName>

                                             <User>JSMIT</User>

                                             <SpecialInstructions>Air Mail</SpecialInstructions>

                                             <LineItems>

                                               <LineItem ItemNumber="1">

                                                 <Description>Bubble Gum</Description>

                                                 <Part Id="7155" UnitPrice="19.95" Quantity="5">

                                                 </Part>

                                               </LineItem>

                                               <LineItem ItemNumber="2">

                                                 <Description>Blow Pops</Description>

                                                 <Part Id="3742" UnitPrice="15.95" Quantity="2">

                                                 </Part>

                                               </LineItem>

                                             </LineItems>

                                           </PurchaseOrder>');

begin

  shred_abc_purchase_order(p_abc_purchase_order => v_abc_purchase_order);

end;

 

Now that our XML data has been loaded, the SQL-based data hub application can query our relational tables (Listing 5). Notice that with one procedure call our XML document has been shredded into two tables: a single row of purchase order information and two line item rows.      

Listing 5. Relational Queries

select * from abc_purchase_order_table;

 

ORDER_NUMBER  ORDER_DATE  CUSTOMER_NAME   USERID  SPECIAL_INSTRUCTIONS

------------- ----------- --------------- ------- --------------------

11111         15-JAN-10   Joe Smith       JSMIT   Air Mail

 

select * from abc_lineitem_table;

 

ORDER_NUMBER ITEMNO DESCRIPTION PARTNO UNITPRICE QUANTITY

------------ ------ ----------- ------ --------- --------

11111        1      Bubble Gum  7155   19.95     5

11111        2      Blow Pops   3742   15.95     2

 

Depending on the size and complexity of XML documents, ingesting and shredding can be expensive. In situations in which you only need a subset of data within a large, complex XML document, you can perform a partial ingestion, thereby minimizing the cost of translation. This is accomplished using the DBMS_XMLSTORE package, which enables DML operations to be performed on relational tables using XML input. Notice the DBMS_XMLSTORE.insertXML procedure accepts an XML object, a style sheet and a target relational table. The following code demonstrates how this is done (Listing 6). 

Listing 6. Partial Ingestion

-- Step 1: Create table with subsetted columns

 

create table abc_partial_po_table (ordernumber number, specialinstructions varchar2 (100));

 

-- Step 2: Create procedure

 

create or replace procedure abc_insert_xml2relational(p_xml_in xmltype,

                                                      p_xsl_in xmltype,

                                                      p_table in varchar2 ) as

  v_context dbms_xmlstore.ctxtype;

  v_rows number;

begin

  -- open a new context

  v_context := dbms_xmlstore.newcontext(p_table);

  -- Perform the ingestion

  v_rows := dbms_xmlstore.insertxml(v_context, xmltype.transform(p_xml_in, p_xsl_in));

  -- close the context

  dbms_xmlstore.closecontext(v_context);

end;

/

 

-- Step 3: Execute procedure

 

declare

  v_xml xmltype := xmltype('<PurchaseOrder>

                               <OrderNumber>11111</OrderNumber>

                               <OrderDate>2010-01-15</OrderDate>

                               <CustomerName>Joe Smith</CustomerName>

                               <User>SBELL</User>

                               <SpecialInstructions>Air Mail</SpecialInstructions>

                               <LineItems>

                                 <LineItem ItemNumber="1">

                                   <Description>Bubble Gum</Description>

                                   <Part Id="7155" UnitPrice="19.95" Quantity="5">

                                   </Part>

                                 </LineItem>

                                 <LineItem ItemNumber="2">

                                   <Description>Blow Pops</Description>

                                   <Part Id="3742" UnitPrice="15.95" Quantity="2">

                                   </Part>

                                 </LineItem>

                               </LineItems>

                             </PurchaseOrder>'

                          );

  v_xsl xmltype := xmltype('<?xml version="1.0"?>

                            <xsl:stylesheet

                                 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

                            version="1.0">

                            <xsl:template match="/">

                                <ROWSET>

                                  <ROW>

                                    <ORDERNUMBER>

                                      <xsl:value-of select="PurchaseOrder/OrderNumber" />

                                    </ORDERNUMBER>

                                    <SPECIALINSTRUCTIONS>

                                      <xsl:value-of select="PurchaseOrder/SpecialInstructions" />

                                    </SPECIALINSTRUCTIONS>

                                  </ROW>

                                </ROWSET>

                              </xsl:template>

                            </xsl:stylesheet>'

                          );

begin

  abc_insert_xml2relational(v_xml, v_xsl, 'ABC_PARTIAL_PO_TABLE');

end;

/

 

-- Step 4: Query table

 

SELECT * FROM abc_partial_po_table;

 

ORDERNUMBER            SPECIALINSTRUCTIONS                                                                                 

---------------------- --------------------                                                                                                

11111                  Air Mail 

 

Publishing relational data as XML

The final requirement of our use case is to abstract and generate XML data from relational tables. 

Abstraction is achieved through the use of XMLType views, essentially a stored query with results materialized at SQL execution. XMLType views allow document-centric applications to query underlying relational structures as virtual XML documents. You have several choices for generating XML data. A common approach is to use object types, object views, and the Oracle SQL function sys_XMLGen. I prefer to use SQL/XML constructs (Listing 7) or XQuery in combination with XMLTable or XMLQuery functions or both. Both allow me to show the query plan with an optimal execution path. 

Listing 7. Create XMLType View

create or replace view abc_purchase_order_xml_view of XMLType

with OBJECT ID (XMLCast(XMLQuery('/PurchaseOrder/OrderNumber'Passing OBJECT_VALUE Returning Content)as BINARY_DOUBLE))

as

Select XMLElement(NOENTITYESCAPING "PurchaseOrder"

                 ,XMLForest(po.order_number         as "OrderNumber"

                           ,po.order_date           as "OrderDate"

                           ,po.customer_name        as "CustomerName"

                           ,po.userid               as "User"

                           ,po.special_instructions as "SpecialInstructions"

                           )

                 ,XMLElement(NOENTITYESCAPING "LineItems"

                            ,(SELECT XMLAgg(XMLElement(NOENTITYESCAPING "LineItem"

                                                      ,XMLAttributes(NOENTITYESCAPING li.itemno  as "ItemNumber")

                                                      ,XMLElement(NOENTITYESCAPING "Description", li.description)

                                                      ,XMLElement(NOENTITYESCAPING "Part"

                                                                 ,XMLAttributes(li.partno    as "Id"
                                                                               ,li.unitprice as "UnitPrice"
                                                                               ,li.quantity  as "Quantity" 
                                                                               )

                                                                 )

                                                      )

                                           )

                                from abc_lineitem_table li

                                where li.order_number = po.order_number

                             )

                            )

                 )

  as "POXML"

  from abc_purchase_order_table po;

 

Now it’s time to generate our XML data (Listing 8). For consumption by XML applications, we can publish the XML data as an unformatted string. For times when you require structured output, the XMLSerialize function can present XML in a format suitable for viewing or printing. Notice in our example the XMLSerialize function has formatted the end tag of the “Part” element as “/>” rather than “</Part>”. Both are considered well-formed XML, so there isn’t any technical difference.

Listing 8. Generate XML Data

 

-- Unformatted

 

select OBJECT_VALUE as "Purchase Order" from abc_purchase_order_xml_view;

 

Purchase Order

<PurchaseOrder><OrderNumber>11111</OrderNumber><OrderDate>2010-01-15</OrderDate><CustomerName>Joe Smith</CustomerName>

<User>JSMIT</User><SpecialInstructions>Air Mail</SpecialInstructions><LineItems><LineItem ItemNumber="1">

<Description>Bubble Gum</Description><Part Id="7155" UnitPrice="19.95" Quantity="5"></Part></LineItem>

<LineItem ItemNumber="2"><Description>Blow Pops</Description><Part Id="3742" UnitPrice="15.95" Quantity="2">

</Part></LineItem></LineItems></PurchaseOrder>

 

-- Formatted

 

select XMLSerialize(Content OBJECT_VALUE as CLOB indent) as "Purchase Order"

  from abc_purchase_order_xml_view;

 

Purchase Order

-----------------------------------------------------

<PurchaseOrder>

  <OrderNumber>11111</OrderNumber>

  <OrderDate>2010-01-15</OrderDate>

  <CustomerName>Joe Smith</CustomerName>

  <User>JSMIT</User>

  <SpecialInstructions>Air Mail</SpecialInstructions>

  <LineItems>

    <LineItem ItemNumber="1">

      <Description>Bubble Gum</Description>

      <Part Id="7155" UnitPrice="19.95" Quantity="5"/>

    </LineItem>

    <LineItem ItemNumber="2">

      <Description>Blow Pops</Description>

      <Part Id="3742" UnitPrice="15.95" Quantity="2"/>

    </LineItem>

  </LineItems>

</PurchaseOrder>

 

 

 

Jeff McCormick is an architecture director at a major health service company and president of the Connecticut Oracle User Group. McCormick has worked in IT for more than 20 years as a data and infrastructure architect/administrator. He holds several Oracle, Microsoft and Sybase professional certifications.

This was first published in May 2011

Dig deeper on Oracle XML

Pro+

Features

Enjoy the benefits of Pro+ membership, learn more and join.

0 comments

Oldest 

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

-ADS BY GOOGLE

SearchDataManagement

SearchBusinessAnalytics

SearchSAP

SearchSQLServer

TheServerSide

SearchDataCenter

SearchContentManagement

SearchFinancialApplications

Close