Penúltima part del curs Curs BBDD i Java centrada en la construcció de consultes mitjançant XPath/XQuery.
1 Introducció
Les bases de dades natives XML (XML native database) ofereixen un mecanisme adient per a la consulta sobre dades que resideixin en documents XML.
En aquest sentit, les dades estan estructurades en la base de dades (que ara és un o més documents XML) en la forma d’una estructura en arbre invertit, on el seu creador haurà definit els noms dels nodes i la seva distribució:
Fragment de mondial.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mondial SYSTEM "mondial.dtd">
<mondial>
<country car_code="AND" area="450" capital="cty-Andorra-Andorra-la-Vella" memberships="org-CE org-ECE org-Interpol org-IFRCS org-IOC org-ITU org-UN org-UNESCO org-WIPO">
<name>Andorra</name>
<population>72766</population>
...
<border country="F" length="60"/>
<border country="E" length="65"/>
<city id="cty-Andorra-Andorra-la-Vella" is_country_cap="yes" country="AND">
<name>Andorra la Vella</name>
<longitude>1.3</longitude>
<latitude>42.3</latitude>
<population year="87">15600</population>
</city>
</country>
...
<desert id="desert-Atacama" country="RCH">
<name>Atacama</name>
...
<area>181300</area>
<longitude>-69.25</longitude>
<latitude>-24.5</latitude>
</desert>
</mondial>
El fragment mostrat correspon a la base de dades MondialDB, que pot descarregar-se lliurament aquí i que serà utilitzada aquí per als exemples i exercicis.
Les consultes sobre XND són de natura diferents respecte de les conegudes fins ara arran de l’estructura interna d’aquests documents. Per tant, els llenguatges existents per fer-hi consultes també són diferents.
Trobem així que ara no podem utilitzar llenguatges com SQL, perquè aquí no tenen sentit. Pel contrari, existeix XPath i XQuery com els llenguatges estrella per a l’extracció de dades d’arxius XML i XND.
En concret, XPath és un llenguatge centrat en el recorregut descendent a través del XML, realitzant filtrat i agrupació, mitjançant un llenguatge senzill amb una col·lecció de funcions i operacions limitada.
Per altra banda, XQuery permet construir estructures més complexes basades en un iterador (bucle) de tipus for
en el que es coneix com construcció FLWOR (for
-let
-where
-order
-result
).
A més, existeixen diferents API disponibles per als diversos llenguatges de programació que permeten connectar amb aquest tipus de bases de dades i arxius XML per tal d’executar consultes amb aquests dos llenguatges de programació.
2. Instal·lació de BaseX
El primer que hem de fer és disposar d’una instal·lació d’alguna aplicació que permeti treballar en aquest entorn.
De sistemes XND n’hi ha un grapat, però nosaltres ens centrarem en el producte BaseX.
Aquest producte està disponible en el mateix dipòsit estàndard d’Ubuntu, de manera que només necessitem treballar des de apt-get
per gestionar la seva instal·lació.
Així doncs, els passos seran els següents:
-
Actualitzar primer el sistema operatiu:
sudo aptitude update && sudo aptitude upgrade
-
Instal·lar JDK6 (si no estava instal·lat prèviament:
sudo aptitude install openjdk-6-jdk
-
Configurar JDK6 o JDK7 com a versió per defecte, i així disposar de les dues versions instal·lades en el sistema, deixant la desitjada com a versió per defecte de línia d’ordres:
sudo update-alternatives --config java sudo update-alternatives --config javac sudo update-alternatives --config javadoc sudo update-alternatives --config javap sudo update-alternatives --config javah sudo update-alternatives --config javaws
Si vostè desitja deixar JDK6 com a versió per defecte per compilar programes en línia d’ordres, només cal que així ho indiqui. Podrà comprovar quina és la versió configurada fent:
java -version
-
Instal·lar BaseX:
sudo aptitude install basex
-
Descarregar la base de dades de mostra MONDIAL (The MONDIAL Database) en format XML:
wget http://www.dbis.informatik.uni-goettingen.de/Mondial/mondial.xml
-
Obrir BaseX en mode gràfic:
basexgui
-
Crear una base de dades nova a partir de l’arxiu
mondial.xml
descarregat i amb nom “mondial
”
En aquest moment ja disposa de les eines necessàries per poder aprendre XPath/XQuery
Ara pot veure com és la base de dades amb què farem proves, tot recorrent-la gràficament amb l’aplicació i observant el document associat. Observi que quan se selecciona una branca de l’arbre XML, només aquella part serà mostrada en el visor de text i quedarà marcat en el visor gràfic.
3. Consultes XPath
XPath és, com s’ha dit, un llenguatge de consultes senzill que permet recórrer l’arbre XML de manera descendent i filtrar els elements (nodes) pels quals passem en aquest recorregut amb diversos criteris.
En aquest sentit, el resultat d’una consulta XPath és, en termes generals, un document XML. Aquest resultat és la col·lecció de nodes i elements que hagin “sobreviscut” a aquest recorregut i filtrat descendent.
3.1. Recorregut de l’arbre XML
La forma bàsica d’una sentència XPath s’assembla molt a la d’un camí de disc, on els directoris són els nodes presents al XML.
El node arrel es representa com a //
. A partir d’aquí, podem incloure un camí concret descendent per l’arbre XML, tot filtrant què volem que quedi en el resultat.
Vegem alguns exemples:
-
Tot l’arbre del document:
//mondial
El resultat serà tota la base de dades sencera.
-
Tots els nodes del document:
//*
El resultat, tot i semblar el mateix, conceptualment és un altre: tots els nodes que es trobin penjant de l’arrel (que en aquest cas coincideix).
-
Tots l’arbre del document si el document és un altre:
doc('/Users/felix/Desktop/curset/biblio.xml')//biblioteca
Observi que és possible especificar un arxiu determinat mitjançant la funció
doc()
. XPath presenta algunes funcions interessants que anirem descobrint al llarg dels exemples.Observi també com s’ha indicat el node arrel i el node
biblioteca
que és l’element inicial del XML utilitzat a l’exemple. -
Informació dels diferents continents:
//mondial/continent
-
Extreure només els noms dels continents:
//mondial/continent/name
-
El nom de tots els països que apareixen a la base de dades:
//mondial/country/name
Vegi com el resultat ha estat l’extracció de tots els nodes
name
que pengin d’un nodecountry
, que pengin directament demondial
. Entengui que el resultat és un XML i que hem eliminat altres camins de l’arbre en baixar per aquesta branca, però que en cap cas hem dit que ens quedem en un o un altre país (no s’ha fet filtrat). És per això que veurà els noms de tots els països. És com si baixéssim en paral·lel per totes les branques que compleixin el camí indicat. -
Només els noms en format text (entrant en el text del tag):
//mondial/continent/name/text()
Aquesta funció fa que només quedi el text que és contingut dins dels nodes que han arribat a aquell punt (nodes
name
). Com que BaseX no gestiona el formatat, veurem el text tot seguit, però en realitat el que tenim és una col·lecció (llista) de cadenes de text. -
Informació millor presentada:
string-join(//mondial/continent/name/text(), ', ')
Aquesta funció uneix la llista de texts obtinguda posant caràcters de coma entre cada parell, retornant una única cadena de text de resultat.
-
Llistat de conceptes presents a la base de dades:
distinct-values(//mondial/*/node-name())
Observem diversos punts a comentar aquí:
-
Per una banda, el caràcter
*
farà de comodí per indicar que en aquest punt estem baixant un nivell dins del XML, però no ens importa com es digui en node concret per on baixem aquest nivell. I pensi que estem indicant que s’ha de baixar un i només un nivell. No representa una quantitat arbitrària de nivells, sinó que no ens importa com es digui el nivell. -
Tot i que no ens importa quin node baixem, igualment volem aplicar-hi una funció. En concret, la funció
node-name()
retorna el nom del node del node en què estiguem situats. Així doncs, si hem baixat percountry
, el nom serà “country”, i si hem baixat perriver
, serà “river”. -
El
distinct-values()
és necessari per eliminar duplicats en el resultat obtingut, fent que només es mostri una vegada cadascun dels conceptes trobats.
-
3.2. Filtrat de nodes
Quan volem filtrar nodes en l’arbre, ho farem indicant entre claudàtors []
una expressió que representi un criteri de filtrat. Quan l’expressió indicada es compleixi, aquell node “passarà” el filtre i romandrà al resultat de la consulta.
Els filtres podem posar-los en qualsevol node d’un recorregut descendent com els vistos fins ara, de tal manera que podem combinar filtres diferents en nivells diferents del nostre recorregut.
Observi el següent exemple:
//mondial/country[name='Spain']/province[name='Catalonia']/city/name
En aquest cas, el recorregut que fem és, en realitat, //mondial/country/province/city/name
. És a dir, baixem pels nodes de l’arbre XML fins al nom de les ciutats de les províncies dels països.
Però no volem tots els països, així que afegim el filtre sobre el nom del país en el nivell de country
. El criteri és clar: Només volem aquell país que es diu “Spain”. Un cop aquest filtrat s’hagi efectuat, només quedarà un node país per baixar i continuarà el processament del camí.
En el nivell de província, tornem a aplicar un filtre sobre el nom de la província, de manera que només quedi aquella amb nom “Catalonia”.
Ara bé, observi que, en realitat, el filtre ha fet un descens per l’arbre XML paral·lel al que s’estava fent per obtenir la informació resultant. Quan indiquem que miri el name
en els dos casos, s’està baixant a aquell node per comprovar si es compleix o no. I, de fet, podríem baixar més d’un nivell si volguéssim.
A més, podem també filtrar pel valor d’un atribut d’un node, posant el signe @
davant del nom de l’atribut.
Els operadors que podem utilitzar per escriure aquests filtres són els següents:
Operador | Descripció | Exemple |
---|---|---|
| |
Calcula la unió de dos conjunts | //llibres | //cds |
+ |
Suma | 6 + 4 |
- |
Resta | 6 - 4 |
* |
Multiplicació | 6 * 4 |
div |
Divisió | 8 div 4 |
= |
Igualtat | preu=9.80 |
!= |
Diferent | preu!=9.80 |
< |
Menor que | preu<9.80 |
<= |
Menor o igual | preu<=9.80 |
> |
Major que | preu>9.80 |
>= |
Major o igual | preu>=9.80 |
or |
o | preu=9.80 or preu=9.70 |
and |
i | preu>9.00 and preu<9.90 |
mod |
Mòdul (residu) | 5 mod 2 |
Vegem ara uns quants exemples:
-
Informació específica d’un país, cercada pel valor d’un node fill:
//mondial/country[name='Spain']
Això funciona perquè dins del tag
<country>
hi ha un altre anomenat<name>
amb el text corresponent al nom del país. -
Informació específica d’un país, cercada a través d’un atribut:
//mondial/country[@car_code='E']
-
Nom dels rius que consten a Espanya
//mondial/river[@country="E"]/name
-
Llistat dels països amb més de 50 milions d’habitants:
//mondial/country[population>50000000]/name
-
Diferents formes de govern dels països amb més de 50 milions d’habitants:
distinct-values(//mondial/country[population>50000000]/government)
-
Quantitat de països a la base de dades:
count(//mondial/country)
Aquí veu que existeixen funcions d’agregació com
count()
,sum()
,max()
, etc. Vegi la llista de funcions al final de l’article. -
Quantitat de ciutats espanyoles:
count(//mondial/country[name='Spain']/province/city)
-
Quantitat de diferents formes de govern dels països de més de 50 milions d’habitants:
count(distinct-values(//mondial/country[population>50000000]/government))
-
Nom de les ciutats espanyoles:
//mondial/country[name='Spain']/province/city/name
-
Ciutats espanyoles amb més de 200.000 habitants:
//mondial/country[name='Spain']/province/city[population>200000]/name
-
Noms de província espanyoles:
//mondial/country[name='Spain']/province/name
-
Països d’Europa:
//mondial/country[encompassed/@continent='europe']/name
O també:
//mondial/country[encompassed[@continent='europe']]/name
I és que podem aplicar filtres sobre filtres!
-
Rius de països europeus:
//mondial/river[@country=//mondial/country[encompassed/@continent='europe']/@car_code]/name
I és que podem fer també subconsultes!
-
Rius europeus amb una longitud de, com a mínim, la meitat de longitud que el riu més llarg de tot el mon:
//mondial/river[ @country=//mondial/country[encompassed/@continent='europe']/@car_code and length>=max(//mondial/river/length) div 2 ]/name
Observi com, per filtrar els rius europeus, s’ha fet que el codi de país sigui un dels presents a la llista de la subconsulta de països europeus. La igualtat com a condició és, en realitat, com un
IN
de SQL. És a dir, si un dels dos elements és una llista, el que fa és buscar el valor dins de la llista. -
La quantitat d’habitants de la ciutat espanyola amb més habitants:
max(//mondial/country[name='Spain']/province/city/population)
-
La ciutat espanyola amb més habitants:
//mondial/country[name='Spain']/province/city[population=max( //mondial/country[name='Spain']/province/city/population)]/name
O també:
//mondial/country[name='Spain']/province/city[population=max( ../../province/city/population)]/name
Observi’s que estem fent una subconsulta i que la subconsulta pot ser relativa al punt on s’aplica el filtrat (segon cas).
-
Rius amb nom començat amb la lletra ‘G’):
//mondial/river[matches(name,'^G.*')]/name
La funció
matches(text, expressió_regular)
permet verificar expressions contra expressions regulars estàndard (tipusgrep
). -
Nom de totes les ciutats de més d’un milió habitants (independentment de si el país està subdividit en províncies o no):
//mondial/country/descendant::city[population>1000000]/name
L’operador
descendant::node
indica explorar tots i cadascun dels camins inferiors que continguin el node indicat, independentment del seu nivell dins de l’arbre. Així doncs, ens permet buscar un node fill per sota d’un punt de l’arbre quan no sabem en quin nivell de profunditat es troba.
Amb tot, pot comprovar que les consultes que es poden arribar a escriure cobriran gran part dels requeriments d’extracció d’informació que necessitarem per construir vistes d’usuari. Això ja ens està bé, però de tant en tant ens trobem amb casos específics que requereixen d’alguna cosa més que el recorregut descendent amb filtrat.
3.3. Resum de funcions XPath/XQuery agrupades per concepte
Quant a les funcions presentades ara, tingui present que algunes poden aplicar-se sobre el node actual, mentre que d’altres només accepten el pas per argument. No s’ha indicat aquí quin cas és cadascun per reduir la complexitat del llistat, tot i que els casos en què s’aplica un o l’altre cas són en general força evidents. Per exemple: //biblioteca/llibre[position()>3]/text()
Funcions sobre els accessors:
Nom | Descripció |
---|---|
fn:node-name(node) |
Retorna el nom del node indicat |
fn:nilled(node) |
Retorna cert si el node és nul |
fn:data(item.item,...) |
Pren una seqüència i retorna la seqüència de valors atòmics |
fn:base-uri(node) |
Retorna el valor de la propietat base-uri del node indicat |
fn:document-uri(node) |
Retorna el valor de la propietat document-uri del node indicat |
Funcions d’error i traça:
Nom | Descripció |
---|---|
fn:error([error[,descripció[,objecte-error]]]) |
Genera i retorna un error |
fn:trace(valor,etiqueta) |
Usat per depurar consultes |
Funcions sobre valors numèrics:
Nom | Descripció |
---|---|
fn:number(arg) |
Retorna el valor numèric de l’argument |
fn:abs(num) |
Retorna el valor absolut |
fn:ceiling(num) |
Retorna l’enter més petit que és major o igual a l’argument |
fn:floor(num) |
Retorna l’enter més gran que és menor o igual a l’argument |
fn:round(num) |
Arrodoneix a l’enter més proper |
fn:round-half-to-even(num) |
Arrodoneix a l’enter més proper que sigui parell |
Funcions sobre valors de text:
Nom | Descripció |
---|---|
fn:string(arg) |
Retorna el valor de text de l’argument |
fn:codepoints-to-string(int,int,...) |
Retorna una cadena a partit d’una seqüència de codis numèrics de caràcter |
fn:string-to-codepoints(string) |
Retorna la seqüència de codis de caràcter corresponent a la cadena |
fn:codepoint-equal(comp1,comp2) |
Cert si els texts són iguals segons Unicode |
fn:compare(comp1,comp2[,joc_caracters]) |
Compara les cadenes seguint el joc de caràcters indicat. Retorna -1, 0 o 1 |
fn:concat(string,string) |
Retorna la concatenació de les cadenes |
fn:string-join(seqüència,sep) |
Retorna una cadena que conté els ítems indicats amb el separador indicat |
fn:substring(string,inici[,long]) |
Treu una subcadena d’una cadena |
fn:string-length(string) |
Retorna la quantitat de caràcters de la cadena |
fn:normalize-space(string) |
Elimina espais sobrants de la cadena (inicial, final i repetits) |
fn:normalize-unicode() |
Normalitza seguint les normes Unicode |
fn:upper-case(string) |
Converteix la cadena a majúscules |
fn:lower-case(string) |
Converteix la cadena a minúscules |
fn:translate(string1,string2,string3) |
Substitueix en string1 cada caràcter de string2 pel corresponent de string3 |
fn:escape-uri(string,esc-res) |
Escapa la cadena seguint les normes per a URIs |
fn:contains(string1,string2) |
Retorna cert si la primera cadena conté el text de la segona |
fn:starts-with(string1,string2) |
Retorna cert si la primera cadena comença amb el text de la segona |
fn:ends-with(string1,string2) |
Retorna cert si la primera cadena acaba amb el text de la segona |
fn:substring-before(string1,string2) |
Extreu la part prèvia de la primera cadena respecte on apareix la segona |
fn:substring-after(string1,string2) |
Extreu la part posterior de la primera cadena respecte on apareix la segona |
fn:matches(string,regexp) |
Retorna cert si la cadena compleix l’expressió regular indicada |
fn:replace(string,regexp,replace) |
Substitueix les troballes de l’expressió regular per la cadena de substitució |
fn:tokenize(string,regexp) |
Retalla la cadena utilitzant l’expressió regular com a recerca de separador |
Funcions sobre URIs:
Nom | Descripció |
---|---|
fn:resolve-uri(relative,base) |
Resol la URI a partir de la base i la part relativa |
Funcions sobre valors booleans:
Nom | Descripció |
---|---|
fn:boolean(arg) |
Retorna el valor booleà de l’argument indicat |
fn:not(arg) |
Avalua el contrari de l’argument |
fn:true() |
És el valor cert |
fn:false() |
És el valor fals |
Funcions sobre valors data-hora i intervals:
Nom | Descripció |
---|---|
fn:dateTime(date,time) |
Converteix els arguments en una data i hora |
fn:years-from-duration(dur) |
Retorna l’enter que representa els anys de l’interval |
fn:months-from-duration(dur) |
Retorna l’enter que representa els mesos de l’interval |
fn:days-from-duration(dur) |
Retorna l’enter que representa els dies de l’interval |
fn:hours-from-duration(dur) |
Retorna l’enter que representa les hores de l’interval |
fn:minutes-from-duration(dur) |
Retorna l’enter que representa els minuts de l’interval |
fn:seconds-from-duration(dur) |
Retorna l’enter que representa els segons de l’interval |
fn:years-from-dateTime(datetime) |
Retorna l’enter que representa els anys de la data/hora |
fn:months-from-dateTime(datetime) |
Retorna l’enter que representa els mesos de la data/hora |
fn:days-from-dateTime(datetime) |
Retorna l’enter que representa els dies de la data/hora |
fn:hours-from-dateTime(datetime) |
Retorna l’enter que representa les hores de la data/hora |
fn:minutes-from-dateTime(datetime) |
Retorna l’enter que representa els minuts de la data/hora |
fn:seconds-from-dateTime(datetime) |
Retorna l’enter que representa els segons de la data/hora |
fn:timezone-from-dateTime(datetime) |
Retorna el fus horari de la data/hora |
fn:years-from-date(date) |
Retorna l’enter que representa els anys de la data |
fn:months-from-date(date) |
Retorna l’enter que representa els mesos de la data |
fn:days-from-date(date) |
Retorna l’enter que representa els dies de la data |
fn:timezone-from-date(date) |
Retorna el fus horari de la data |
fn:hours-from-time(time) |
Retorna l’enter que representa les hores de la hora |
fn:minutes-from-time(time) |
Retorna l’enter que representa els minuts de la hora |
fn:seconds-from-time(time) |
Retorna l’enter que representa els segons de la hora |
fn:timezone-from-time(time) |
Retorna el fus horari de la hora |
fn:adjust-dateTime-to-timezone(datetime,timezone) |
Associa el fus horari a la data/hora |
fn:adjust-date-to-timezone(datetime,timezone) |
Associa el fus horari a la data |
fn:adjust-time-to-timezone(datetime,timezone) |
Associa el fus horari a la hora |
Funcions sobre nodes:
Nom | Descripció |
---|---|
fn:name(nodeset) |
Retorna el nom del primer node del conjunt |
fn:local-name(nodeset) |
Retorna el nom del primer node del conjunt sense el prefix del namespace |
fn:namespace-uri(nodeset) |
Retorna el URI del namespace del primer node del conjunt |
fn:lang(lang) |
Retorna cert si l’idioma del node actual correspon a l’especificat. S’indica com a text ("en" ) |
fn:root(node) |
Retorna el node arrel de l’arbre a què pertany el node indicat |
Funcions sobre seqüències:
Nom | Descripció |
---|---|
fn:index-of(seqüència, item) |
Retorna la posició de l’element cercat a la seqüència de recerca |
fn:remove(seqüència, pos) |
Elimina l’element situat a la posició indicada |
fn:empty(seqüència) |
Retorna cert si l’argument és una seqüència buida |
fn:exists(seqüència) |
Retorna cert si l’argument no és una seqüència buida |
fn:distinct-values(seqüència) |
Retorna la seqüència neta de duplicats |
fn:insert-before(seqüència,pos,item) |
Insereix l’element indicat just abans de la posició especificada |
fn:reverse(seqüència) |
Inverteix l’ordre dels elements de la seqüència |
fn:subsequence(seqüència,pos,len) |
Extreu part d’una seqüència |
fn:unordered(seqüència) |
Retorna els elements en un ordre dependent de la implementació |
Funcions sobre cardinalitat de seqüències:
Nom | Descripció |
---|---|
fn:zero-or-one(seqüència) |
Retorna l’argument si conté zero o un element |
fn:one-or-more(seqüència) |
Retorna l’argument si conté un element com a mínim |
fn:exactly-one(seqüència) |
Retorna l’argument si conté exactament un sol element |
Funcions d’igualtat de seqüències:
Nom | Descripció |
---|---|
fn:deep-equal(param1,param2) |
Retorna cert si els arguments són exactament iguals dins de les seves jerarquies |
Funcions d’agregació:
Nom | Descripció |
---|---|
fn:count(seqüència) |
Retorna la quantitat d’elements |
fn:avg(seqüència) |
Retorna el valor mitjà dels elements de la seqüència |
fn:max(seqüència) |
Retorna el valor màxim dels elements de la seqüència |
fn:min(seqüència) |
Retorna el valor mínim dels elements de la seqüència |
fn:sum(seqüència) |
Retorna el total de sumar els elements de la seqüència |
Funcions sobre contexts:
Nom | Descripció |
---|---|
fn:position() |
Retorna l’índex de l’element en el seu entorn |
fn:last() |
Retorna l’índex de l’últim element en el seu entorn |
fn:current-dateTime() |
Retorna la data/hora actual |
fn:current-date() |
Retorna la data actual |
fn:current-time() |
Retorna la hora actual |
fn:implicit-timezone() |
Retorna el fus horari implícit |
fn:default-collation() |
Retorna el joc de caràcters actual |
fn:static-base-uri() |
Retorna el valor de base-uri actual |
Les funcions matemàtiques científiques estan definides en el namespace math
, i per tant, quan són invocades, cal esmentar-ho explícitament. Per exemple: math:sqrt(42)
.
Funcions matemàtiques científiques:
Nom | Descripció |
---|---|
math:pi() |
Representa una aproximació al valor de $\pi$ |
math:exp(x) |
Retorna el valor de $e^x$ |
math:exp10(x) |
Retorna el valor de $10^x$ |
math:log(x) |
Retorna el logaritme natural de l’argument |
math:log10(x) |
Retorna el logaritme en base 10 de l’argument |
math:pow(x,y) |
Retorna el valor de $x^y$ |
math:sqrt(x) |
Retorna l’arrel quadrada de l’argument |
math:sin(x) |
Retorna el sinus de l’angle subministrat |
math:cos(x) |
Retorna el cosinus de l’angle subministrat |
math:tan(x) |
Retorna la tangent de l’angle subministrat |
math:asin(x) |
Retorna l’angle aplicant la funció inversa del sinus |
math:acos(x) |
Retorna l’angle aplicant la funció inversa del cosinus |
math:atan(x) |
Retorna l’angle aplicant la funció inversa de la tangent |
math:atan2(y,x) |
Retorna l’angle aplicant la funció inversa de la tangent sobre (y/x) |
Els tipus de dades presents a XPath/XQuery són: xs:float
, xs:double
, xs:decimal
, xs:integer
, xs:long
, xs:int
, xs:short
, xs:byte
, xs:unsignedLong
, xs:unsignedInt
, xs:unsignedShort
, xs:unsignedByte
, xs:duration
, xs:dateTime
, xs:date
, xs:time
, entre d’altres
Per a l’especificació d’arguments de les funcions, poden utilitzar-se els elements addicionals a més dels tipus de dades presentats:
Especificació d’elements en arguments de funcions:
Tipus d’element | Permet |
---|---|
node() |
Qualsevol node |
element() |
Qualsevol element de node |
element(nom) |
L’element de node amb el nom indicat |
attribute() |
Qualsevol atribut de node |
I pot indicar-se també la cardinalitat dels arguments:
Indicadors de cardinalitat per als arguments:
Indicador | Significat |
---|---|
cap (blanc) | exactament un element |
? |
Zero o un element |
+ |
Un o més elements |
* |
Qualsevol quantitat d’elements (zero o més) |
4. Consultes FLWOR (XQuery)
XQuery ofereix un mecanisme de consultes més avançat que es munta sobre XPath. En concret, quan treballem amb XQuery, treballem amb la construcció FLWOR
:
FLWOR (XQuery)
FLWOR
=for
-let
-where
-order by
-return
Aquestes construccions permeten fer el recorregut per un arbre XML i executar alguna tasca sobre cadascun dels elements trobats, incloent l’aplicació de filtrat i càlcul d’expressions. A més, incorpora la possibilitat d’aplicar ordenació dels resultats.
Un FLWOR, com a mínim, ha de contenir el iterador de bucle for
i un return
per obtenir algun resultat. Els altres tres elements (let
, where
i order by
són opcionals).
Analitzem les diferents parts:
-
Amb la clàusula
for
especifiquem el conjunt d’elements que volem recórrer. La notació és:for $variable in //...consulta...XPath...
Per tant, indiquem quina variable volem utilitzar per emmagatzemar els diferents elements trobats i la consulta XPath que ens generi la llista d’elements. Aquí podem posar una consulta XPath tant complexa com les vistes fins ara.
-
Amb la clàusula
let
podem indicar assignacions i càlculs en forma d’expressions avaluades. Normalment s’utilitza sintaxi XPath i podem utilitzar la variable de bucle o variables definides amblet
. Podem disposar més d’unlet
si així ho volem.Per exemple:
for $pais in //mondial/country let $num_ciutats := count($pais/descendant::city) let $tot_pobl := sum($pais/descendant::city/population) let $percent := $tot_pobl div $pais/population ...
-
Amb la clàusula
where
afegirem filtres sobre la llista d’iteració, de manera que els elements delfor
que no compleixin la condició delwhere
seran omesos en el bucle:for $pais in //mondial/country let $num_ciutats := count($pais/descendant::city) let $tot_pobl := sum($pais/descendant::city/population) let $percent := $tot_pobl div $pais/population where $pais/encompassed/@continent='europe' ...
-
Amb la clàusula
order by
ordenem el resultat delfor
. Per tant, especifiquem l’ordre de recorregut del bucle:for $pais in //mondial/country let $nom := $pais/name/text() let $num_ciutats := count($pais/descendant::city) let $tot_pobl := sum($pais/descendant::city/population) let $percent := $tot_pobl div $pais/population * 100 where $pais/encompassed/@continent='europe' order by $percent ascending ...
-
Amb la clàusula
return
indicarem què s’ha de mostrar com a resultat com a resposta a cada iteració (no a tot el bucle). El resultat ha de ser un únic node XML o una primitiva bàsica (com una cadena o un enter) per a cada iteració executada. Elreturn
s’executarà, per tant, n vegades:for $pais in //mondial/country let $nom := $pais/name/text() let $num_ciutats := count($pais/descendant::city) let $tot_pobl := sum($pais/descendant::city/population) let $percent := $tot_pobl div $pais/population * 100 where $pais/encompassed/@continent='europe' order by $percent ascending return <pais> {$nom} : {$percent} </pais>
Vegem ara tot un seguit d’exemples de XQuery que demostren algunes particularitats destacables:
-
Nom de les províncies espanyoles:
for $p in //mondial/country[name='Spain']/province return $p/name
-
Extracció de la llista de ciutats de cada província espanyola:
for $p in //mondial/country[name='Spain']/province let $nom := $p/name/text() let $ciutats := string-join($p/city/name, ', ') return <provincia> {$nom}: {$ciutats} </provincia>
-
Llista dels països que tenen més d’un 1% de la població mundial total, ordenats per població descendent i mostrant els resultats en format XML:
for $c in //mondial/country let $n := $c/name/text() let $p := $c/population/text() where $p>=sum(//mondial/country/population)*0.01 order by number($p) descending return <pais nom='{$n}'>{$p}</pais>
-
Nom de les províncies espanyoles, amb població i quantitat de ciutats, ordenats per quantitat de ciutats en ordre descendent, amb resultat en format XML:
for $p in //mondial/country[name='Spain']/province let $nom := $p/name let $pop := $p/population let $num := <count> {count($p/city)} </count> order by $num descending return <provincia> {$nom} {$pop} {$num} </provincia>
-
Redreçament de la informació de les províncies, amb cada província amb atributs de nom i població i llista de ciutats interna, amb una entrada per a cada ciutat:
for $p in //mondial/country[name='Spain']/province let $nom := $p/name/text() let $pop := $p/population/text() let $cities := <cities> { for $c in $p/city/name/text() return <city> {$c} </city> } </cities> return <provincia nom='{$nom}' population='{$pop}'> {$cities} </provincia>
5. Declaració de funcions en XQuery
És possible definir funcions pròpies per a ser utilitzades en les nostres consultes XQuery. La forma de fer-ho és mitjançant la següent sintaxi:
declare function nom_de_la_funció($var1 as tipus, $var2 as tipus, ...) as tipus {
...
codi de la funció
...
}
XQuery accepta construccions diverses en el bloc de codi, com let
, for
i fins i tot estructures if ... then ... else ...
.
A més, les funcions solen definir-se en un espai de funcions concret. Normalment l’espai serà el local a la consulta (espai de l’usuari). Això es fa indicant el nom de la funció amb una especificació local:
prèvia al nom de la funció.
Vegem ara alguns exemples que demostren diferents situacions útils en la construcció de funcions XQuery:
-
Funció que calcula arrel d’índex N a partir de l’índex i el radicand:
declare function local:sqrtN($i as xs:double, $r as xs:double) as xs:double { let $x := math:pow($r, 1 div $i) return $x }; local:sqrtN(3, 125)
-
Funció que retorna la primera paraula de la cadena indicada
declare function local:primera-paraula($cad as xs:string) as xs:string { let $paraula := substring-before($cad, ' ') return $paraula }; local:primera-paraula('Ola k ase')
-
Funció recursiva per al factorial (i exemple de
if
)declare function local:factorial($n as xs:integer) as xs:integer { if ($n<=1) then $n else $n*local:factorial($n - 1) }; local:factorial(7)
-
Marge de població (diferència de població entre el país més habitat i el menys)
declare function local:marge($seq as element()*) as xs:double { let $a := min($seq) let $b := max($seq) return ($b - $a) }; local:marge(//mondial/country/population)
-
Quantitat de ciutats per país
declare function local:numciutats($c as element(country)*) as xs:double { let $n := count($c/descendant::city) return $n }; local:numciutats(//mondial/country[name='Spain'])