Skip to Main Content
  • Questions
  • Extracting attribute values from repeating elements using XMLType member functions.

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Pavel.

Asked: February 02, 2021 - 6:00 pm UTC

Last updated: February 03, 2021 - 4:26 pm UTC

Version: 12.1.0.2

Viewed 10K+ times! This question is

You Asked

Hi,

I'm trying to improve the performance of a piece of code where extracting data
from an XML document is involved.
Here is a simplified code with the XML document defined in it.
I've already achieved about 30% performance boost by rewriting SELECTs using EXTRACTVALUE
into a form using the XMLTYPE member functions EXTRACT and GETSTRINGVAL.
(see the commented lines).
declare
 ivkX XMLType;
 nperiodsX XMLType;
 suminsX NUMBER(10);
nperiods_tab nperiods_T;
begin
ivkX:=xmltype(
'<ivk>
  <insurance>
   <sumins>20596</sumins>
   <yearsins>56</yearsins>
   <daysins>156</daysins>
  </insurance>
 <nperiods>
  <nperiod datefrom="1965-10-03" dateto="1965-10-03" days="1" />
  <nperiod datefrom="2005-09-01" dateto="2009-12-31" days="1583" />
  <nperiod datefrom="2013-01-01" dateto="2013-12-31" days="8" />
 </nperiods>
</ivk>');

-- usual
SELECT extractvalue(ivkX, '//ivk/insurance/sumins') INTO suminsX FROM DUAL;
DBMS_OUTPUT.PUT_LINE(suminsX);
-- preferred, faster, avoids context switch PLSQL <--> SQL
suminsX:=ivkX.EXTRACT('//ivk/insurance/sumins/text()').GETSTRINGVAL();
DBMS_OUTPUT.PUT_LINE(suminsX);

nperiodsX:=ivkX.EXTRACT('//ivk/nperiods');
nperiodsX.ToObject(nperiods_tab);
end;
/

ERROR at line 1:
ORA-19031: XML element or attribute nperiod does not match any in type IDB_MIG.NPERIODS_T
ORA-06512: at "SYS.XMLTYPE", line 196
ORA-06512: at line 33


I'd like to do the same or similar with attributes of the repeating elements, like the nperiod elements here.
But I got stuck with the definition of an appropriate object type.
Here is one of my attempts of defining the object types to extract the values of the repeating
attributes datefrom, dateto and days in the <nperiods> element into an object table.

CREATE OR REPLACE TYPE nperiod_T AS OBJECT("@datefrom" varchar2(10),"@dateto" varchar2(10),"@days" varchar2(10));
/
CREATE OR REPLACE TYPE nperiods_T AS OBJECT("nperiod" nperiod_T);
/

I've tried various other definitions of the nperiods_T object type, but I keep getting
either the ORA-19031 error or type inconsistency.
Could you please tell me how I should construct the object type to be able to do that?
The database I use is Oracle 12.1.0.2.

Thank you,
Pavel


with LiveSQL Test Case:

and Chris said...

I don't know how to do exactly what you're looking for; I've tried various things with no luck.

That said, some pointers which may help:

The object will have an array of nperiod items, so you'll need to declare a nested table type. The current objects only allow one nperiod element under nperiods.

You can loop through the nperiod elements, assigning each to an object as you go:

create or replace type "nperiod" force as object(
  "@datefrom" varchar2(10),"@dateto" varchar2(10),"@days" varchar2(10)
); 
/

declare 
 ivkX XMLType; 
 nperiodsX XMLType; 
 x xmltype;
 n number;
 p "nperiod";
begin 
ivkX:=xmltype( 
'<ivk> 
  <insurance> 
   <sumins>20596</sumins> 
   <yearsins>56</yearsins> 
   <daysins>156</daysins> 
  </insurance> 
 <nperiods> 
  <nperiod datefrom="1965-10-03" dateto="1965-10-03" days="1" /> 
  <nperiod datefrom="2005-09-01" dateto="2009-12-31" days="1583" /> 
  <nperiod datefrom="2013-01-01" dateto="2013-12-31" days="8" /> 
 </nperiods> 
</ivk>'); 
  
  nperiodsX:=ivkX.EXTRACT('//ivk/nperiods'); 

  select xmlcast ( 
    xmlquery ( 
      'count(//nperiods/*)' passing nperiodsX returning content 
    ) as number 
  ) 
  into   n 
  from   dual;

  for i in 1 .. n loop
    x := nperiodsX.EXTRACT('//nperiod['||i||']'); 
    x.toObject ( p );
    dbms_output.put_line ( p."@datefrom" );
  end loop;
end; 
/

1965-10-03
2005-09-01
2013-01-01


The XMLCast ... XMLQuery select is to get the number of elements under nperiods; there may be a way to do this in PL/SQL, but I can't find it right now!

It may also be worth looking into XMLTable. This converts XML to relational rows-and-columns using SQL. This will have a context switch, but it may enable you to reduce the number of extract calls your making, giving a good win:

select * from xmltable (
  'ivk/nperiods/nperiod'
  passing xmltype ( '<ivk>
  <insurance>
   <sumins>20596</sumins>
   <yearsins>56</yearsins>
   <daysins>156</daysins>
  </insurance>
 <nperiods>
  <nperiod datefrom="1965-10-03" dateto="1965-10-03" days="1" />
  <nperiod datefrom="2005-09-01" dateto="2009-12-31" days="1583" />
  <nperiod datefrom="2013-01-01" dateto="2013-12-31" days="8" />
 </nperiods>
</ivk>' )
  columns 
    datefrom varchar2(10) path '@datefrom',
    dateto varchar2(10) path '@dateto',
    days integer path '@days'
);

DATEFROM      DATETO         DAYS   
1965-10-03    1965-10-03          1 
2005-09-01    2009-12-31       1583 
2013-01-01    2013-12-31          8 

Rating

  (1 rating)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Thanks

Pavel, February 04, 2021 - 8:13 am UTC

Hi Chris,
many thanks for your tips. I'll try to implement them and see if they bring some performance improvement.

More to Explore

Design

New to good database design? Check out Chris Saxon's full fundamentals class.