XML-Know How

Uniqueness und Keys

Das Konzept der Uniqueness (Einzigartigkeit) und der Keys (Schlüssel) ist aus XML-DTDs in Form der Attribut-Datentypen ID und IDREF bekannt. Werte von Attributen mit dem Typ ID dürfen in einem XML-Dokument nur einmal vorkommen, sie sind einzigartig (unique; im Sinne von »nur einmal vorkommend«).

Für Werte von Attributen mit dem Typ IDREF muss ein Äquivalent bei einem ID-Attribut desselben Dokuments existieren. Dieses Konstrukt wird auch als Schlüssel-System bezeichnet, wobei die ID der Primärschlüssel ist und die dazu passenden IDREFs die Sekundärschlüssel (primary key und secondary key).

Attribute vom Typ ID und IDREF stehen auch in XSD zur Verfügung. Das Konzept von ID und IDREF ist allerdings sehr begrenzt, so dass es in XSD weitere Methoden gibt, die Einzigartigkeit von Daten und ihre Beziehungen zueinander zu definieren.

IDs, wie man sie aus DTDs kennt, können nicht weiter differenziert werden. Das heißt, dass sie dokumentweit einzigartig sein müssen, gleichgültig zu welchem Element sie gehören. Wenn in einem Dokument beispielsweise Autorennummern und Beitragsnummern vorkommen, kann es sein, dass die Nummer eines Autors und die Nummer eines Beitrags gleich sind:

<dokument>
  <autoren>
    <autor nummer="Nr10"> ... </autor>
    ...
  </autoren>
  <beiträge>
  <beitrag nummer="Nr10"> ... </beitrag>
    ...
  </beiträge>
</dokument>

Das wäre sachlich völlig korrekt, würde bei Verwendung von IDs für die Nummern aber zu einem Fehler führen. Man könnte zur Lösung dieses Problems durch zusätzliche formale Regeln sicherstellen, dass Autorennummern und Beitragsnummern nie identisch sind. So können Sie z. B. die Regel vereinbaren, dass alle Autorennummern mit »A-« und alle Beitragsnummern mit
»B-« beginnen. Diese Regel ist aber nicht technisch per DTD festlegbar, sondern kann nur als »Best Practice« empfohlen werden:

<dokument>
  <autoren>
    <autor nummer="A-10"> ... </autor>
    ...
  </autoren>
  <beiträge>
    <beitrag nummer="B-10"> ... </beitrag>
    ...
  </beiträge>
</dokument>

Dieses Vorgehen ist aber unter Umständen wenig praxisgerecht, etwa wenn die Nummern mit anderen Systemen ausgetauscht werden sollen. Man könnte auch auf eine eigene Skriptlösung setzen, aber das ist mit XSD nicht nötig, wie wir weiter unten sehen werden.

Was man eigentlich braucht, ist ein System, das sicherstellt, dass keine Nummer bei Autoren doppelt vorkommt und keine bei den Beiträgen. Der Gültigkeitsbereich der Nummern müsste also jeweils auf ein Element als Kontext beschränkt werden.

Weitere Mängel bei DTDs sind, dass IDs und IDREFs nur als Attributwerte und nicht als Elementinhalte auftreten können. Hinderlich ist auch, dass sie bestimmten formalen Kriterien genügen müssen (IDs müssen u. a. immer mit einem Buchstaben beginnen und dürfen keine Leerzeichen enthalten). Weiterhin kann ein Element nur ein einziges Attribut vom Typ ID haben.

Und schließlich kann immer nur ein einzelner Wert als einzigartig definiert sein, nicht aber die Kombination von mehreren. Angenommen, Sie verwalten Kunden, Artikel und Rechnungen in XML. Jeder Kunde darf nur eine Bestellung pro Artikel aufgeben. Das heißt einerseits, bei den Rechnungen kann jede Artikelnummer und jede Kundennummer mehrfach vorkommen, denn jeder Kunde kann mehrere verschiedene Artikel bestellen und jeder Artikel kann von mehreren Kunden bestellt werden. Andererseits darf jede Artikelnummer mit einer konkreten Kundennummer jeweils nur einmal zusammen auftreten. Daten, die nur als Kombination einzigartig sind, nennt man »kombinierte Schlüssel«:

<rechnung>
  <artikelnummer>123</artikelnummer>
  <kundennummer>456</kundennummer>
    ...
</rechnung>

Um die Einzigartigkeit von Daten zu gewährleisten, stellt XSD das Element <xs:unique> mit den Kindelementen <xs:selector> und <xs:field> zur Verfügung. Mit dem <xs:selector>-Element wird angegeben, auf welches Element als Kontext sich die Einzigartigkeit beziehen soll. Im <rechnung>-Beispiel sollen die Nummern von Autoren nicht dokumentweit einzigartig sein, sondern nur im Kontext »Autor« – keine Zahl darf zweimal als Autornummer auftreten, außerhalb des Kontexts »Autor« kann sie aber beliebig verwendet werden, auch als ID anderer Elemente. Im nächsten Schritt wird mit dem <xs:field>-Element festgelegt, welche Attribute oder Kindelemente des Kontext-Elements einzigartig sein sollen.

Das <xs:unique>-Element muss ganz am Ende desjenigen <xs:element> stehen, in dessen <complexType> die Elemente deklariert sind, für die die Einzigartigkeit festgelegt werden soll.

Die Angaben erfolgen dabei in XPath-Syntax. XPath ist einer der Standards, die sich um XML herum entwickelt haben, und ermöglicht es, XML-Elemente und Attribute in Form eines Pfads anzugeben (W3C Recommendation 1999). Eine ausführliche Darstellung von XPath würde über den Rahmen dieses Artikels hinausgehen.

Mit <xs:unique> können sowohl Inhalte von Elementen als auch Werte von Attributen als auch Kombinationen von Elementinhalten und Attributwerten als einzigartig festgelegt werden. Auch der Datentyp ist beliebig.

Unser erstes Beispiel mit den Autoren- und Beitragsnummern würde mit <xs:unique> wie folgt realisiert:

<xs:unique name="Autornummern-ID">
   <xs:selector xpath=".//autor"/>
    <xs:field xpath="@nummer"/>
</xs:unique>

<xs:unique name=”Beitragsnummern-ID”>
   <xs:selector xpath=”.//beitrag”/>
   <xs:field xpath=”@nummer”/>
</xs:unique>

<xs:selector xpath=".//autor"/> gibt an, dass sich die Uniqueness der nachstehenden <xs:field>-Angaben auf das Element autor bezieht, wobei ».//« in XPath besagt, dass alle <autor>-Elemente unabhängig von ihrer Position im Element, in dem <xs:unique> steht, gemeint sind. Hier sind auch genauere XPath-Angaben möglich.

<xs:field xpath="@nummer"/> spezifiziert dann, dass die Uniqueness für die Werte des Attributs nummer gilt, wobei »@« in XPath besagt, dass ein Attribut gemeint ist. Auch hier sind genauere XPath-Angaben möglich.

Das zweite Beispiel mit der kombinierten ID aus Artikelnummer und Kundennummer in einer Rechnung kann mit <xs:unique> realisiert werden, indem je ein <xs:field> für Artikelnummer und Kundennummer verwendet wird:

<xs:unique name="Art-Kund-ID">
   <xs:selector xpath=".//rechnung"/>
   <xs:field xpath="artikelnummer „/>
   <xs:field xpath="kundennummer „/>
</xs:unique>

Das erweiterte Schlüssel-System von XSD sieht für Schlüssel-Sekundärschlüssel-Verbindungen die gleichen Möglichkeiten vor wie für die Festlegung der Uniqueness.

Daher überrascht es nicht, dass die von XSD zur Verfügung gestellten Elemente <xs:key> (für Primärschlüssel) und <xs:keyref> (für Sekundärschlüssel) genauso aufgebaut sind wie das <xs:unique>-Element:

<xs:key name="Art-Kund-ID">
   <xs:selector xpath=".//rechnung"/>
   <xs:field xpath="artikelnummer „/>
   <xs:field xpath="kundennummer „/>
</xs:key>

<xs:key> und <xs:keyref> enthalten ebenfalls ein <xs:selector>-Element und ein oder mehrere <xs:field>-Elemente. <xs:key> ist sogar identisch mit <xs:unique>. Der Unterschied liegt in der Verarbeitung durch den Parser: bei <xs:unique> achtet der Parser nur auf die Einzigartigkeit, während er bei <xs:key> den Inhalt zusätzlich für den Abgleich mit <xs:keyref>-Elementen bereithält.

Das <xs:keyref>-Element unterscheidet sich von <xs:unique> und <xs:key> nur durch das zusätzliche Attribut refer, das den Namen des <xs:key>-Elements mit dem Primärschlüssel angibt.

Angenommen, zu den Rechnungen aus unserem Beispiel gäbe es ein weiteres XML-Element <mahnung>, das wie die Rechnungen über die Kindelemente <artikelnummer> und <kundennummer> verfügt und über diese Elemente den Rechnungen zugeordnet werden soll. Es soll sichergestellt werden, dass es für jede Mahnung auch wirklich eine Rechnung gibt, die die gleiche Kombination von <artikelnummer> und <kundennummer> aufweist wie die Mahnung.

Das <xs:keyref>-Element für <mahnung> würde dann so aussehen:

<xs:keyref name="Mahn-IDREF" refer="Art-Kund-ID">
   <xs:selector xpath=".//mahnung"/>
   <xs:field xpath="artikelnummer „/>
    <xs:field xpath="kundennummer „/>
<xs:/keyref>

Der <xs:selector> gibt wieder den Kontext an, hier <mahnung>. Dann definiert <xs:field>, welche Elemente oder Attribute von <mahnung> auf Übereinstimmung mit einem Primärschlüssel überprüft werden sollen. Und das Attribut refer enthält die Information, dass der Primärschlüssel, mit dem eine Übereinstimmung vorliegen muss, »Art-Kund-ID« ist.

<xs:key> und <xs:keyref> müssen wie <xs:unique> ganz am Ende desjenigen <xs:element> stehen, in dessen <complexType> die Elemente deklariert sind, für die die Schlüssel festgelegt werden sollen. Das könnte in unserem Beispiel ein gemeinsames Container-Element für <rechnung> und <mahnung> sein.

Das folgende Schema stellt die Anforderung sicher, dass jeder Mahnung auch eine Rechnung zugeordnet ist:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="artikelnummer"type="xs:integer"/>
    <xs:element name="kundennummer"type="xs:integer"/>
    <xs:elementname="root">
       <xs:complexType>
           <xs:sequence>
               <xs:elementname=”rechnung”maxOccurs=”unbounded”>
                   <xs:complexType>
                   <xs:sequence>
                       <xs:element ref="artikelnummer"/>
                       <xs:element ref="kundennummer"/>
                   </xs:sequence>
       </xs:complexType>
    </xs:element>
    <xs:elementname=”mahnung”maxOccurs=”unbounded”>
       <xs:complexType>
       <xs:sequence>
           <xs:element ref=”artikelnummer”/>
               <xs:element ref=”kundennummer”/>
       </xs:sequence>
       </xs:complexType>
    </xs:element>
    </xs:sequence>
    </xs:complexType>
    <xs:keyname=”Art-Kund-ID”>
       <xs:selector xpath=".//rechnung"/>
       <xs:field xpath="artikelnummer"/>
       <xs:field xpath="kundennummer"/>
    </xs:key>
    <xs:keyrefname="Mahn-IDREF" refer="Art-Kund-ID">
       <xs:selector xpath=".//mahnung"/>
       <xs:field xpath="artikelnummer"/>
       <xs:field xpath=”kundennummer”/>
           </xs:keyref>
           </xs:element>
</xs:schema>