26. September 2014

XML Namespaces, XMLTABLE und die Oracle Datenbank

Dealing with XML namespaces in the Oracle database
Heute geht es um einen Aspekt beim Umgang mit XML in der Oracle-Datenbank, der recht häufig zu Fragen und scheinbar unverständlichem Verhalten der Datenbank führt: die XML Namespaces. Dazu (wie immer) ein einführendes Beispiel.
create table xml_tab (
  id  number,
  xml xmltype
)
/

insert into xml_tab values (
  1, 
  '<document xmlns="a">
     <blog>SQL und PL/SQL</blog>
     <thema>Oracle Datenbank</thema>
   </document>'
);

commit
/
Aus diesem XML-Dokument sollen nun, mit der SQL-Funktion XMLTABLE, die Inhalte extrahiert werden - wie das geht, findet man recht schnell heraus:
select 
  blog, 
  thema
from xml_tab t, xmltable(
  '/document'
  passing xml
  columns 
    blog  varchar2(30) path 'blog',
    thema varchar2(30) path 'thema'
) x
/

No rows selected.
Wie man sieht, funktioniert das nicht - man überprüft die Angaben der XML-Tags noch einmal und stellt fest, dass alles richtig ist. Der Grund für das Verhalten liegt an der Namespace Deklaration im XML-Dokument - und zwar hier.
<document xmlns="a">...</document>
XML kennt das Konzept der Namespaces: Ein XML-Tag wird nicht nur durch den Namen selbst bestimmt, in diesem Fall also document, sondern auch durch den Namespace. Damit wird es möglich, in ein- und demselben XML-Dokument mehrere Tags des scheinbar gleichen Namens zu haben - durch unterschiedliche Namespaces ist es aber nicht mehr dasselbe Tag. Namespaces werden in der Praxis meist mit mit URLs (bspw. http://www.meinefirma.de/xmlnamespace1) benannt - damit sie global möglichst eindeutig sind (die Webadressen müssen natürlich nicht tatsächlich funktionieren - es sind nur Namen). Man kann aber auch, wie oben, jede beliebige Zeichenkette hernehmen. Gerade wenn der Namespace mit einer längeren URL benannt ist, wäre es aber recht umständlich, die gesamte URL zu jedem XML-Tag dazuzuschreiben - die ohnehin schon sehr großen XML-Dokumente würden noch größer und schwieriger zu verarbeiten. Daher gibt es zusätzlich ...
  • Namespace-Präfixe
  • Default Namespace
Beide werden mit dem Attribut xmlns definiert. Im Beispiel oben wird der Namespace "a" als Default-Namespace deklariert. Alle XML-Tags ohne ein Namespace-Präfix werden also dem Namespace "a" zugeordnet. Alternativ könnte man auch einen Namespace-Präfix deklarieren - dann sieht das XML-Dokument so aus.
insert into xml_tab values (
  2, 
  '<pr:document xmlns:pr="a">
     <pr:blog>SQL und PL/SQL</pr:blog>
     <pr:thema>Oracle Datenbank</pr:thema>
   </pr:document>'
);
Inhaltlich sind beide XML-Dokumente exakt identisch - ein XML-Parser macht keinen Unterschied. Die XML-Tags gehören in beiden Fällen zum Namespace "a", nur ist das einmal der Default-Namspace, im zweiten Fall werden explizite Präfixe verwendet.
Und weil das alles noch nicht genug ist, gibt es auch XML-Dokumente, in denen das Attribut xmlns fehlt oder in denen es XML-Tags ohne Präfix, aber keinen Default-Namespace gibt. Diese Tags haben dann keinen Namespace, manche sprechen auch vom Null-Namespace. Ein XML-Tag ohne Namespace ist nach dem XML-Standard ein anderes Tag als eines mit Namespace. Und das alles kann in ein- und demselben XML-Dokument auch gemischt werden.
Diese Dinge müssen beim Formulieren der SQL-Abfrage mit XMLTABLE berücksichtigt werden; im Eingangsbeispiel gehören alle XML-Tags zum Namespace "a", die XMLTABLE-Abfrage enthält aber keinerlei Namespace-Definition. Also hat sie nach XML-Tags ohne Namespace gesucht, davon (natürlich) keine gefunden, daher das No rows selected.. Die Abhilfe kann so aussehen ...
select
  blog,
  thema
from xml_tab t, xmltable(
  xmlnamespaces('a' as "p"),
  '/p:document'
  passing xml
  columns
    blog  varchar2(30) path 'p:blog',
    thema varchar2(30) path 'p:thema'
) x
/

BLOG                           THEMA
------------------------------ ------------------------------
SQL und PL/SQL                 Oracle Datenbank
SQL und PL/SQL                 Oracle Datenbank

2 rows selected.
... oder so:
select
  blog,
  thema
from xml_tab t, xmltable(
  xmlnamespaces(default 'a'),
  '/document'
  passing xml
  columns
    blog  varchar2(30) path 'blog',
    thema varchar2(30) path 'thema'
) x
/

BLOG                           THEMA
------------------------------ ------------------------------
SQL und PL/SQL                 Oracle Datenbank
SQL und PL/SQL                 Oracle Datenbank

2 rows selected.
Man sieht, dass die XMLNAMESPACES-Klausel für die XMLTABLE-Funktion die gleiche Bedeutung hat, wie das Attribut xmlns im XML-Dokument. In beiden Fällen wird der Namespace auf ein Präfix abgebildet oder als Default festgelegt. In den folgenden XQuery oder XPath Ausdrücken muss dann der deklarierte Präfix verwendet werden. Es muss aber keinesfalls der gleiche Präfix wie im Dokument verwendet werden - der Präfix oder die Tatsache, dass ein Namespace als Default deklariert ist, ist völlig bedeutungslos. Wichtig ist allein der Name des Namespace, also das "a". Und natürlich kann man sich nun auch XML-Dokument mit mehreren Namespaces vorstellen ...
insert into xml_tab values (
  3, 
  '<ns1:document xmlns:ns1="a" xmlns:ns2="b" xmlns:ns3="c">
     <ns2:blog>SQL und PL/SQL</ns2:blog>
     <ns3:thema>Oracle Datenbank</ns3:thema>
   </ns1:document>'
);
Nun gehört jedes XML-Tag tatsächlich zu einem anderen Namespace. Dieses Dokument ist auch für einen XML-Parser inhaltlich ein anderes Dokument als die ersten beiden. Es werden drei Namespaces deklariert: a, b und c. Während a als Default-Namespace deklariert wird (also kein Präfix für das XML-Tag), erhalten b und c die Präfixe ns1 und ns2. In der XMLTABLE-Abfrage muss das berücksichtigen.
select 
  blog, 
  thema
from xml_tab t, xmltable(
  xmlnamespaces(default 'a', 'b' as "p1", 'c' as "p2"),
  '/document'
  passing xml
  columns 
    blog  varchar2(30) path 'p1:blog',
    thema varchar2(30) path 'p2:thema'
) x
/
Natürlich muss man in der XMLTABLE-Abfrage nicht mit einem Default-Namespace arbeiten, man kann auch alle drei auf Präfixe abbilden. Wichtig ist nur, dass die eigentlichen Namen der Namespaces, nämlich a, b und c, korrekt angesprochen werden. Achtet man darauf, so sind auch XML-Dokumente mit Namespaces kein Problem mehr.
This blog posting is about XML namespaces, and how to deal with them when processing XML with SQL functions. This often leads to confusion and questions - the following sections will try to shed some light into this. We'll start with a simple example ...
create table xml_tab (
  id  number,
  xml xmltype
)
/

insert into xml_tab values (
  1, 
  '<document xmlns="a">
     <blog>SQL und PL/SQL</blog>
     <thema>Oracle Datenbank</thema>
   </document>'
);

commit
/
From this XML document, we want to extract data using the XMLTABLE function. After reading the documentation, the SQL query is authored rather quickly.
select 
  blog, 
  thema
from xml_tab t, xmltable(
  '/document'
  passing xml
  columns 
    blog  varchar2(30) path 'blog',
    thema varchar2(30) path 'thema'
) x
/

No rows selected.
But, although all tag names are correct, it does not work. The reason is the XML namespace declaration at the beginning of the document.
<document xmlns="a">...</document>
XML has the concept of namespaces. An XML tag is being identified not only by its name (here: document), but also by its namespace. Using this, XML allows tags having the same name, but different semantics. By considering both name and namespace, an XML engine is able to differentiate between the tags. In practice, namespaces are being named with URLs like http://mycompany.com/myproject/mynamespace (these URLs don't have to exist physically - it's just a name). Instead of a URL, we can also use any character string for a namespace, like in the example above: "a" (which is not likely to be globally unqiue, of course). If a URL, or a long string is being used for a namespace, we seem to have a problem: We need to add this string to each and every XML Tag, don't we ...?
  • Namespace prefixes map long namespaces to short prefixes
  • The default namespace is being used for tags without a prefix
Both are declared with the xmlns attribute: xmlns="a" declares "a" as the default namespace wheres xmlns:pr="a" maps "a" to the namespace prefix "pr". In our example, a default namespace is being used. As an alternative, we can declare a namespace prefix. Then we need to add it to the XML tags - like in the following XML document.
insert into xml_tab values (
  2, 
  '<pr:document xmlns:pr="a">
     <pr:blog>SQL und PL/SQL</pr:blog>
     <pr:thema>Oracle Datenbank</pr:thema>
   </pr:document>'
);
Both XML documents have exactly the same semantics - for an XML parser there is no difference. And that's not all: XML tags can also have no namespace at all. Some refer to this as the null or empty namespace. This is the case, when the xmlns attribute is either not present or when the XML document has no default namespace, but contains XML tags without a namespace prefix. Therefore we can have XML tags with, and without a namespace and - of course - mutliple different namespaces - all within the same XML document.
We need to take care about this when authoring XMLTABLE queries. In the above example, all XML tags were part of the "a" namespace. But the XMLTABLE query did not contain a namespace declaration - so it looked for tags without a namespace. Since the XML document does not contain these, the query result is correct. To get the query working - we need to add the XMLNAMESPACES clause. A working solution can look like this ...
select
  blog,
  thema
from xml_tab t, xmltable(
  xmlnamespaces('a' as "p"),
  '/p:document'
  passing xml
  columns
    blog  varchar2(30) path 'p:blog',
    thema varchar2(30) path 'p:thema'
) x
/

BLOG                           THEMA
------------------------------ ------------------------------
SQL und PL/SQL                 Oracle Datenbank
SQL und PL/SQL                 Oracle Datenbank

2 rows selected.
... or like this ...
select
  blog,
  thema
from xml_tab t, xmltable(
  xmlnamespaces(default 'a'),
  '/document'
  passing xml
  columns
    blog  varchar2(30) path 'blog',
    thema varchar2(30) path 'thema'
) x
/

BLOG                           THEMA
------------------------------ ------------------------------
SQL und PL/SQL                 Oracle Datenbank
SQL und PL/SQL                 Oracle Datenbank

2 rows selected.
The XMLNAMESPACES clause within an XMLTABLE query has the same meaning as the xmlns attribute within an XML document: a namespace is mapped to a prefix or declared as default. The prefixes and defaults within a query are independent from the prefixes and defaults in the document - only the namespaces themselves (here "a") are important. Having this in mind, we can now also imagine documents with tags from multiple namespaces.
insert into xml_tab values (
  3, 
  '<ns1:document xmlns:ns1="a" xmlns:ns2="b" xmlns:ns3="c">
     <ns2:blog>SQL und PL/SQL</ns2:blog>
     <ns3:thema>Oracle Datenbank</ns3:thema>
   </ns1:document>'
);
This XML document is semantically different to the previous ones, since each XML tag belongs to another namespace: a, b and c. a is being declared as the default (XML tag has no prefix); b and c are mapped to prefixes ns1 and ns2. A working XMLTABLE query can look as follows.
select 
  blog, 
  thema
from xml_tab t, xmltable(
  xmlnamespaces(default 'a', 'b' as "p1", 'c' as "p2"),
  '/document'
  passing xml
  columns 
    blog  varchar2(30) path 'p1:blog',
    thema varchar2(30) path 'p2:thema'
) x
/
As already said, the prefixes used in the XMLTABLE query can be different from the prefixes in the document: the namespace itself is important - not the prefix. We do also not need to use a default namespace - another possibile solution would be to have three different namespace prefixes. Taking care about this, XML documents with namespaces are no problem at all.

Keine Kommentare:

Beliebte Postings