XQuery
Door Michiel van Otegem
10 december 2007
Tot op heden ontbreekt in het standaardenpakket van XML nog één ding: een taal waarmee gegevens
opgevraagd kunnen worden uit één of meerdere XML documenten. XQuery, dat nog steeds onder
ontwikkeling is, moet dit ontbrekende stukje van de puzzel worden.
XML Query of XQuery zou wel eens een van de belangrijkste XML technologieën kunnen worden. Het is
bedoeld om gegevens mee op te vragen uit XML documenten. In feite moet XQuery worden voor XML wat
Structured Query Language (SQL) is voor (relationele) databases. Het belang van XQuery wordt
duidelijk als je kijkt naar de ontwikkeling ervan door het W3 Consortium (W3C). De XQuery
specificatie had namelijk al tijden af moeten zijn, maar bevindt zich nog steeds in het Working
Draft stadium. Wanneer de uiteindelijke specificatie klaar zal zijn is ook nog steeds niet
duidelijk. Wel is het duidelijk dat deze specificatie enorm groot is, omdat het onder andere
bestaat uit een puur wiskundige beschrijving van hoe het moet werken, maar ook uit voorbeelden
(Use Cases) die dit laten zien op een praktische manier.
Gegevensbronnen
Met XQuery maak je op basis van één of meerdere gegevensbronnen een nieuw XML document, of een
XML fragment. Deze bronnen kunnen gewone XML bestanden zijn, maar ook een virtueel XML document
uit andere typen gegevensbronnen. Je kunt daarbij denken aan een technologie als SQLXML, waarmee
een XML document gemaakt wordt vanuit gegevens in een SQL Server database, en waarbij dat document
via HTTP beschikbaar wordt gesteld. Voor andere databases, zoals Oracle, bestaat vergelijkbare
technologie om gegevens te ontsluiten als XML. Omdat XQuery zowel met fysieke bestanden als
virtuele XML documenten overweg kan, is het uitermate geschikt om gegevens mee op te vragen uit
meerdere systemen die mogelijk verschillend van aard zijn, en niets met elkaar te maken hebben.
Dit komt onder andere voor in situaties waarin meerdere organisaties gegevens kunnen aanleveren.
Overigens is in een dergelijke situatie interoperabiliteit het toverwoord. Van de snelheid waarmee
een query vanuit verschillende systemen gedaan wordt, hoef je voorlopig niet veel te verwachten.
Pas als een XQuery document (een query dus) bepaalde onderdelen van de query uit kan besteden bij
de gegevensbron waar de betreffende gegevens vandaan komen, kan de snelheid verbeterd worden. In
de eerste versie(s) van XQuery ligt het accent echter op het uitvoeren van XQuery, want dat is al
complex genoeg.
1: <?xml version="1.0" encoding="UTF-8"?>
2: <menu>
3: <voorgerechten>
4: <gerecht id="1" prijs="8,95">Krab cocktail</gerecht>
5: <gerecht id="2" prijs="9,95">Carpaccio</gerecht>
6: <gerecht id="3" prijs="6,95">Groene salade</gerecht>
7: </voorgerechten>
8: <hoofdgerechten>
9: <gerecht id="4" prijs="19,95">Gegrilde ossehaas</gerecht>
10: <gerecht id="5" prijs="16,95">Linguini al Pesto</gerecht>
11: <gerecht id="6" prijs="18,95">Gepocheerde zalm</gerecht>
12: </hoofdgerechten>
13: <desserts>
14: <gerecht id="7" prijs="6,95">Dame Blanche</gerecht>
15: <gerecht id="8" prijs="5,95">Sorbet</gerecht>
16: <gerecht id="9" prijs="6,95">Banana Split</gerecht>
17: </desserts>
18: </menu>
Afbeelding 1, Voorbeeld XML
XQuery syntax
De basis van XQuery bestaat uit vier kernwoorden: FOR, LET, WHERE en RETURN. Een expressie die
deze kernwoorden gebruikt wordt wel een FLWR-expressie (of FLoWeR-expressie) genoemd. Binnen
XQuery zijn ze ook wel bekend als Element Constructors, omdat ze gebruikt worden om elementen of
een sequentie van elementen te maken. Dit wordt duidelijker aan de hand van een aantal voorbeelden.
Voor de voorbeelden gebruiken we het XML document in Afbeelding 1, waarin een aantal gerechten
staan van een menu. Eerst gaan we al deze gerechten opvragen. Dat doen we als volgt:
1: FOR $x IN document('menu.xml')//gerecht
2: RETURN
3: <gerecht>
4: {$x/text()}
5: </gerecht<
De basis van de query is FOR. FOR is een zogenaamde iterator, een opdracht die door een verzameling
elementen loopt en op ieder van de elementen een actie uitvoert. In dit geval alle <gerecht<
elementen uit Afbeelding 1, die telkens "opgeslagen" worden in de variabele $x. Merk op dat na IN
een XPath expressie staat om de <gerecht< elementen uit menu.xml te halen. XPath heeft, net als
bij XSLT, een centrale rol in XQuery bij het aanwijzen van gegevens. Met RETURN wordt aangegeven
wat het resultaat zal zijn van (een gedeelte van de query). Voor ieder element dat met FOR in $x
wordt gezet, wordt de code na RETURN uitgevoerd. Wat na RETURN staat is eigenlijk een sjabloon
waarbij alles buiten de accolades letterlijk overgenomen wordt. Binnen de accolades staat een
XPath-expressie waarmee wordt aangegeven welke gegevens precies uit het element gehaald moeten
worden. Het resultaat van de bovenstaande query is te zien in Afbeelding 2.
1: <gerecht>Krab cocktail</gerecht>
2: <gerecht>Carpaccio</gerecht>
3: <gerecht>Groene salade</gerecht>
4: <gerecht>Gegrilde ossehaas</gerecht>
5: <gerecht>Linguini al Pesto</gerecht>
6: <gerecht>Gepocheerde zalm</gerecht>
7: <gerecht>Dame Blanche</gerecht>
8: <gerecht>Sorbet</gerecht>
9: <gerecht>Banana Split</gerecht>
Afbeelding 2, Resultaat van de eerst voorbeeld query
Het resultaat in Afbeelding 2 is een
sequentie van elementen. Het resultaat is daardoor ook niet well-formed XML, want het heeft geen
root-element. Daar komt bij dat ook de query zelf ook geen XML is. Dit is op z'n minst opmerkelijk
te noemen, aangezien vrijwel alle andere XML talen die het W3C ontwikkeld heeft zelf ook XML zijn.
Er zijn verschillende redenen waarom dit niet voor XQuery geldt, en de belangrijkste daarvan is
ongetwijfeld simplisme. De taal zou er niet leesbaarder op geworden zijn als het zelf XML moest
zijn. Wat betreft het resultaat valt te verdedigen dat het geen XML hoeft te zijn, maar een sequentie,
omdat de meeste databases een verzameling van records retourneren. Om XQuery zo dicht mogelijk bij
SQL te houden, kan XQuery sequenties van XML retourneren. Een sequentie verschilt in principe
alleen van een verzameling doordat elementen in een sequentie een volgorde hebben, en een
verzameling per definitie niet. Verder is het handig dat een query als het voorbeeld een sequentie
retourneert, omdat je meerdere queries kunt koppelen, zoals in Afbeelding 3.
1: <html>
2: <body>
3: <h1>Voorgerechten</h1>>
4: <p>
5: {
6: LET $gerechten = document("menu.xml")/menu
7:
8: FOR $x IN $gerechten/voorgerechten/gerecht
9: RETURN
10: <b>{$x/text()}</b>
11: Euro {$x/@prijs}
12: <br />
13: }
14: </p>
15: <h1>Hoofdgerechten</h1>
16: <p>
17: {
18: FOR $x IN $gerechten/entrees/dish
19: RETURN
20: <b>{$d/text()}</b>
21: Euro {$x/@prijs}
22: <br />
23: }
24: </p>
25: <h1>Desserts</h1>
26: <p>
27: {
28: FOR $d IN $gerechten/desserts/dish
29: RETURN
30: <b>{$d/text()}</b>
31: Euro {$x/@prijs}
32: <br />
33: }
34: </p>
35: </body>
36: </html>
Afbeelding 3: Query met meerdere FLoWeR-expressies
Een query die alle elementen oplevert is over het algemeen niet echt handig. Je wilt meestal alleen
elementen hebben die aan een bepaalde voorwaarde voldoen. In SQL gebruik je daarvoor een WHERE
statement, en dat is in XQuery is dat net zo. De elementen die het FOR statement oplevert kunnen
we daarmee beperken, zoals in de onderstaande query.
1: FOR $x IN document("menu.xml")//gerecht
2: WHERE $x/@id>'6'
3: RETURN
4: <gerecht>
5: {$d/text()}
6: </gerecht>
Deze query geeft bijna hetzelfde resultaat als de eerdere query, maar beperkt zich tot de elementen
waarvan het id-attribuut een waarde groter is dan 6. In feite dus alleen alle desserts, zodat er
ook had kunnen staan
WHERE $x/../name()='desserts'
Je kunt verder filteren met WHERE door meerdere expressies toe te voegen met AND en/of OR. Ook dit
is vrijwel gelijk aan SQL, dus als je daar al redelijke kennis van hebt, hoef je over dit soort
dingen niet echt veel na te denken. Dat is ook een van de doelen van XQuery, zorgen dat men
makkelijk over kan stappen. Een nadeel van een WHERE statement is dat het in de praktijk zal
betekenen dat het filter toegepast wordt nadat de gegevens uit een document opgevraagd zijn.
Tenzij je vaker van bepaalde gegevens gebruik maakt kan dat erg inefficiënt zijn. Het is handiger
om bij het opvragen van gegevens al te filteren met behulp van de XPath expressie. Dit kan door
een predikaat toe te voegen zoals hieronder.
FOR $x IN document("menu.xml")//gerecht[$x/@id>'6']
Als je bepaalde gegevens vaker nodig hebt, is het niet verstandig deze telkens weer uit een
document op te halen. Om deze reden kun je aan een variabele ook gegevens uit een document toekennen,
hetgeen vaak meer is dan alleen een element, maar een structuur van elementen. Een variabele kan
echter ook een complexere structuur toegekend krijgen die je opbouwt aan de hand van een genest
FOR statement. Het toekennen van gegevens aan een variabele doe je met LET, als volgt:
1: LET $x := document("menu.xml")/menu/desserts/gerecht
2: FOR $e IN $x
3: RETURN
4: <dish>
5: {$e/text()}
6: </dish>
Het bovenstaande voorbeeld geeft weer een rijtje met alle desserts, maar maakt een tussenstap door
eerst de gerechten op te slaan in een variabele. Daarna wordt et FOR een iteratie gedaan op deze
elementen. Omdat de elementen nu in een variabele staan, kunnen ze later hergebruikt worden,
bijvoorbeeld door nog een FOR statement. De uitgebreide query in Afbeelding 3 laat dit zien.
Daarin wordt eerst alles onder het menu element in het document in de variabelen $gerechten geplaatst.
Die variabele wordt bij elk FOR statement gebruikt om gegevens in te voegen. Het resultaat van de
query in Afbeelding 3 is een volledig HTML bestand (XHTML eigenlijk, want het resultaat is
well-formed XML), met daarin de verschillende soorten gerechten onder een kop. Je kunt de HTML
zien in Afbeelding 4, en de weergave daarvan in Afbeelding 5.
1: <html>
2: <body>
3: <h1>Voorgerechten</h1>
4: <p>
5: <b>Krab cocktail</b> Euro 8,95</br />
6: <b>Carpaccio</b> Euro 9,95</br />
7: <b>Groene salade</b> Euro 6,95</br />
8: </p>
9: <h1>Hoofdgerechten</h1>
10: <p>
11: <b>Gegrilde ossehaa</b> Euro 19,85</br />
12: <b>Linguini al Pesto</b> Euro 16,95</br />
13: <b>Gepocheerde zalm</b> Euro 18,95</br />
14: </p>
15: <h1>Desserts</h1>
16: <p>
17: <b>Dame Blanche</b> Euro 6,95</br />
18: <b>Sorbet</b> Euro 5,95</br />
19: <b>Banana Split</b> Euro 6,95</br />
20: </p>
21: </body>
22: </html>
Afbeelding 4, HTML resultaat van Afbeelding 3

Afbeelding 5, Weergave van Afbeelding 4 in een browser
Tot slot: XQuery vs. XSLT
XQuery en XSLT hebben op het eerste gezicht veel met elkaar gemeen. Je kunt ze beiden gebruiken om
gegevens uit één of meerdere XML documenten te halen, en een nieuw XML document te construeren.
Er zijn zelfs mensen die vinden dat XQuery daarom totaal overbodig is. XQuery en XSLT 2.0 maken
ook allebei gebruik van dezelfde basis: XPath 2.0, en ontlenen daar beide veel kracht aan. Er is
echter een fundamenteel verschil tussen XQuery en XSLT door de manier waarop ze werken. Met XQuery
vraag je bepaalde gegevens op uit één of meerdere XML documenten, en hiervan maak je een document
in de structuur die jij opgeeft. We noemen dit wel "pull-processing", omdat de data uit de
gegevensbron wordt opgevraagd. XSLT werkt echter andersom, omdat de structuur en de volgorde van
de gegevens bepaalt welk onderdeel van een XSLT transformatie uitgevoerd wordt. We noemen die
daarom ook wel "push-processing", omdat de gegevensbron door de XSLT "geduwd" wordt. De structuur
en volgorde spelen dus een bepalende rol in de structuur en volgorde van resultaat. Bij XQuery
bepaalt de query dat. XQuery en XSLT vereisen daarom totaal verschillende denkwijzen. XQuery is
qua opzet gelijk aan SQL, en zal dus voor bijvoorbeeld database beheerders eenvoudig te leren zijn.
XSLT heeft meer weg van een opmaaktaal en patroonherkenning, en is dus ook voor een andere groep
mensen interessant. Omdat de opzet van XQuery makkelijker te begrijpen is, is het aannemelijk dat
meer mensen gebruik zullen maken van XQuery.
Handige links
http://www.w3.org/XML/Query
http://www.w3schools.com/xquery/default.asp
http://www.brics.dk/~amoeller/XML/querying/
Dit artikel is eerder verschenen in NetOpus, december 2004.
|