Joins

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie wollen Elemente in einem Dokument anderen Elementen im gleichen oder in einem anderen Dokument zuordnen.

Lösung

Eine Join (Zusammenfügung) ist ein Vorgang, bei dem man alle Paare von Elementen als zueinander zugehörig betrachtet (d.h. als ein kartesisches Produkt) und nur solche Paare bewahrt, die der Join-Beziehung entsprechen (normalerweise Gleichheit).

Zur Demonstration habe ich die Lieferantendatenbank nach XML überführt, die in Dates An Introduction to Database Systems (Addison-Wesley, 1986) zu finden ist:

<database>
  <suppliers>
    <supplier id="S1" name="Smith" status="20" city="London"/>
    <supplier id="S2" name="Jones" status="10" city="Paris"/>
    <supplier id="S3" name="Blake" status="30" city="Paris"/>
    <supplier id="S4" name="Clark" status="20" city="London"/>
    <supplier id="S5" name="Adams" status="30" city="Athens"/>
  </suppliers>
  <parts>
    <part id="P1" name="Nut" color="Red" weight="12" city="London"/>
    <part id="P2" name="Bult" color="Green" weight="17" city="Paris"/>
    <part id="P3" name="Screw" color="Blue" weight="17" city="Rome"/>
    <part id="P4" name="Screw" color="Red" weight="14" city="London"/>
    <part id="P5" name="Cam" color="Blue" weight="12" city="Paris"/>
    <part id="P6" name="Cog" color="Red" weight="19" city="London"/>
  </parts>
  <inventory>
    <invrec sid="S1" pid="P1" qty="300"/>
    <invrec sid="S1" pid="P2" qty="200"/>
    <invrec sid="S1" pid="P3" qty="400"/>
    <invrec sid="S1" pid="P4" qty="200"/>
    <invrec sid="S1" pid="P5" qty="100"/>
    <invrec sid="S1" pid="P6" qty="100"/>
    <invrec sid="S2" pid="P1" qty="300"/>
    <invrec sid="S2" pid="P2" qty="400"/>
    <invrec sid="S3" pid="P2" qty="200"/>
    <invrec sid="S4" pid="P2" qty="200"/>
    <invrec sid="S4" pid="P4" qty="300"/>
    <invrec sid="S4" pid="P5" qty="400"/>
  </inventory>
</database>

Der auszuführende Join wird folgende Frage beantworten: »Welche Lieferanten und Teile befinden sich in der gleichen Stadt (sind »colocated«)?«

Sie können zwei einfache Techniken einsetzen, um sich diesem Problem in XSLT zu nähern. Die erste verwendet geschachtelte for-each-Schleifen:

<xsl:template match="/">
  <result>
    <xsl:for-each select="database/suppliers/*">
      <xsl:variable name="supplier" select="."/>
      <xsl:for-each select="/database/parts/*[@city=current( )/@city]">
        <colocated>
          <xsl:copy-of select="$supplier"/>
          <xsl:copy-of select="."/>
        </colocated>
      </xsl:for-each>
    </xsl:for-each>
  </result>
</xsl:template>

Der zweite Ansatz verwendet apply-templates:

<xsl:template match="/">
  <result>
    <xsl:apply-templates select="database/suppliers/supplier" />
  </result>
</xsl:template>
<xsl:template match="supplier">
  <xsl:apply-templates select="/database/parts/part[@city = current( )/@city]">
    <xsl:with-param name="supplier" select="." />
  </xsl:apply-templates>
</xsl:template>
<xsl:template match="part">
  <xsl:param name="supplier" select="/.." />
  <colocated>
    <xsl:copy-of select="$supplier" />
    <xsl:copy-of select="." />
  </colocated>
</xsl:template>

Wenn eine der zusammenzuführenden Elementmengen eine große Anzahl Mitglieder enthält, dann sollten Sie vielleicht xsl:key verwenden, um die Leistungsfähigkeit zu verbessern:

<xsl:key name="part-city" match="part" use="@city"/>
<xsl:template match="/">
  <result>
    <xsl:for-each select="database/suppliers/*">
      <xsl:variable name="supplier" select="."/>
      <xsl:for-each select="key('part-city',$supplier/@city)">
        <colocated>
          <xsl:copy-of select="$supplier"/>
          <xsl:copy-of select="."/>
        </colocated>
      </xsl:for-each>
    </xsl:for-each>
  </result>
</xsl:template>

Jedes Stylesheet erzeugt das gleiche Ergebnis:

<result>
  <colocated>
    <supplier id="S1" name="Smith" status="20" city="London"/>
    <part id="P1" name="Nut" color="Red" weight="12" city="London"/>
  </colocated>
  <colocated>
    <supplier id="S1" name="Smith" status="20" city="London"/>
    <part id="P4" name="Screw" color="Red" weight="14" city="London"/>
  </colocated>
  <colocated>
    <supplier id="S1" name="Smith" status="20" city="London"/>
    <part id="P6" name="Cog" color="Red" weight="19" city="London"/>
  </colocated>
  <colocated>
    <supplier id="S2" name="Jones" status="10" city="Paris"/>
    <part id="P2" name="Bult" color="Green" weight="17" city="Paris"/>
  </colocated>
  <colocated>
    <supplier id="S2" name="Jones" status="10" city="Paris"/>
    <part id="P5" name="Cam" color="Blue" weight="12" city="Paris"/>
  </colocated>
  <colocated>
    <supplier id="S3" name="Blake" status="30" city="Paris"/>
    <part id="P2" name="Bult" color="Green" weight="17" city="Paris"/>
  </colocated>
  <colocated>
    <supplier id="S3" name="Blake" status="30" city="Paris"/>
    <part id="P5" name="Cam" color="Blue" weight="12" city="Paris"/>
  </colocated>
  <colocated>
    <supplier id="S4" name="Clark" status="20" city="London"/>
    <part id="P1" name="Nut" color="Red" weight="12" city="London"/>
  </colocated>
  <colocated>
    <supplier id="S4" name="Clark" status="20" city="London"/>
    <part id="P4" name="Screw" color="Red" weight="14" city="London"/>
  </colocated>
  <colocated>
    <supplier id="S4" name="Clark" status="20" city="London"/>
    <part id="P6" name="Cog" color="Red" weight="19" city="London"/>
  </colocated>
</result>

Diskussion

XSLT 1.0

Der Join, den Sie ausgeführt haben, wird als Equi-Join bezeichnet, weil die Elemente durch Gleichheit miteinander verbunden sind. Allgemeiner können Joins auch mit Hilfe anderer Beziehungen geformt werden. Betrachten Sie zum Beispiel die Abfrage »Wähle alle Kombinationen aus Lieferanten und Teileinformationen, bei denen die Lieferantenstadt der Teilestadt in alphabetischer Reihenfolge folgt«.

Es wäre schön, wenn Sie einfach das folgende Stylesheet schreiben könnten, allerdings definiert XSLT 1.0 keine relationalen Operationen auf Stringtypen:

<xsl:template match="/">
  <result>
    <xsl:for-each select="database/suppliers/*">
      <xsl:variable name="supplier" select="."/>
      <!-- Das funktioniert nicht! -->
      <xsl:for-each select="/database/parts/*[current( )/@city &gt; @city]">
        <colocated>
          <xsl:copy-of select="$supplier"/>
          <xsl:copy-of select="."/>
        </colocated>
      </xsl:for-each>
    </xsl:for-each>
  </result>
</xsl:template>

Stattdessen müssen Sie mittels xsl:sort eine Tabelle anlegen, die Städtenamen auf Integer-Werte abbilden kann, die die Reihenfolge widerspiegeln. Hier verlassen Sie sich auf die Fähigkeit von Saxon, Variablen, die Ergebnisbaumfragmente enthalten, als Knotenmengen zu behandeln, wenn die Version auf 1.1 gesetzt ist. Sie können aber auch die node-set-Funktion des jeweiligen XSLT 1.0-Prozessors benutzen oder einen XSLT 2.0-Prozessor verwenden:

<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  <xsl:variable name="unique-cities" select="//@city[not(. = ../preceding::*/@city)]"/>
  <xsl:variable name="city-ordering">
    <xsl:for-each select="$unique-cities">
      <xsl:sort select="."/>
      <city name="{.}" order="{position( )}"/>
    </xsl:for-each>
  </xsl:variable>
  <xsl:template match="/">
    <result>
      <xsl:for-each select="database/suppliers/*">
        <xsl:variable name="s" select="."/>
        <xsl:for-each select="/database/parts/*">
          <xsl:variable name="p" select="."/>
          <xsl:if test="$city-ordering/*[@name = $s/@city]/@order &gt; $city-ordering/*[@name = $p/@city]/@order">
            <supplier-city-follows-part-city>
              <xsl:copy-of select="$s"/>
              <xsl:copy-of select="$p"/>
            </supplier-city-follows-part-city>
          </xsl:if>
        </xsl:for-each>
      </xsl:for-each>
    </result>
  </xsl:template>
</xsl:stylesheet>

Diese Abfrage liefert als Ergebnis die folgende Ausgabe:

<result>
  <supplier-city-follows-part-city>
    <supplier id="S2" name="Jones" status="10" city="Paris"/>
    <part id="P1" name="Nut" color="Red" weight="12" city="London"/>
  </supplier-city-follows-part-city>
  <supplier-city-follows-part-city>
    <supplier id="S2" name="Jones" status="10" city="Paris"/>
    <part id="P4" name="Screw" color="Red" weight="14" city="London"/>
  </supplier-city-follows-part-city>
  <supplier-city-follows-part-city>
    <supplier id="S2" name="Jones" status="10" city="Paris"/>
    <part id="P6" name="Cog" color="Red" weight="19" city="London"/>
  </supplier-city-follows-part-city>
  <supplier-city-follows-part-city>
    <supplier id="S3" name="Blake" status="30" city="Paris"/>
    <part id="P1" name="Nut" color="Red" weight="12" city="London"/>
  </supplier-city-follows-part-city>
  <supplier-city-follows-part-city>
    <supplier id="S3" name="Blake" status="30" city="Paris"/>
    <part id="P4" name="Screw" color="Red" weight="14" city="London"/>
  </supplier-city-follows-part-city>
  <supplier-city-follows-part-city>
    <supplier id="S3" name="Blake" status="30" city="Paris"/>
    <part id="P6" name="Cog" color="Red" weight="19" city="London"/>
  </supplier-city-follows-part-city>
</result>

XSLT 2.0

In XSLT 2.0 funktionieren Vergleichsoperatoren auf Stringwerten richtig, sodass die einfachere Form möglich ist:

<xsl:template match="/">
  <result>
    <xsl:for-each select="database/suppliers/*">
      <xsl:variable name="supplier" select="."/>
      <!-- In 2.0 ist das in Ordnung -->
      <xsl:for-each select="/database/parts/*[current( )/@city &gt; @city]">
        <colocated>
          <xsl:copy-of select="$supplier"/>
          <xsl:copy-of select="."/>
        </colocated>
      </xsl:for-each>
    </xsl:for-each>
  </result>
</xsl:template>

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2006 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT Kochbuch" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O'Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de