Introducción
XQuery, también conocido como XML Query, es un lenguaje creado para buscar y extraer elementos y atributos de documentos XML. La mejor forma de entender este lenguaje es diciendo que XQuery es paraXML lo que SQL es para las bases de datos. XQuery es un lenguaje de consulta diseñado para escribir consultas sobre colecciones de datos expresadas en XML. Abarca desde archivos XML hasta bases de datos relacionales con funciones de conversión de registros a XML.
Su principal función es extraer información de un conjunto de datos organizados como un árbol n-ario de etiquetas XML. En este sentido XQuery es independiente del origen de los datos.
Una consulta XQuery podría resolver, por ejemplo, la siguiente pregunta: “Seleccionar todos los libros con un precio menor a 20 euros de la colección de libros almacenada en un documento XML llamado catalogo.xml”.
XQuery es un lenguaje funcional, lo que significa que en vez de ejecutar una lista de comandos como un lenguaje procedimental clásico, cada consulta es una expresión que es evaluada y devuelve un resultado, al igual que en SQL. Diversas expresiones pueden combinarse de una manera muy flexible con otras expresiones para crear nuevas expresiones más complejas y de mayor potencia semántica.
XQuery hace uso de XPath, un lenguaje utilizado para seleccionar partes de XML. De hecho, XQuery 1.0 y XPath 2.0 comparten el mismo modelo de datos y soportan las mismas funciones y operadores.
El lenguaje XQuery es muy amplio y complejo. Aquí sólo pretendemos hacer una introducción a su uso para hacernos una idea de su potencial junto a documentos XML.
Para todos los ejemplos siguiente vamos a tomar como referencia el siguiente documento XML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<bib>
<book id="1">
<title>TCP/IP Illustrated</title>
<author>Stevens</author>
<publisher>Addison-Wesley</publisher>
<year>2002</year>
</book>
<book id="2">
<title>Advanced Programming in the Unix Environment</title>
<author>Stevens</author>
<publisher>Addison-Wesley</publisher>
<year>2004</year>
</book>
<book id="3">
<title>Data on the Web</title>
<author>Abiteboul</author>
<author>Buneman</author>
<author>Suciu</author>
<year>2006</year>
</book>
</bib>
Herramientas para el manejo de consultas XQuery
Para probar XQuery podemos utilizar el software libre BaseX.
Consultas FLWOR
Las consultas XQuery se componen de cinco cláusulas, que debido a sus iniciales se las conoce como FLWOR. Definimos cada una de ellas:
FOR
: Indica qué nodos se van a seleccionar desde la base de datos XML o desde un documento XML.LET
: Permite declarar variables a las que se le asignan valores.WHERE
: Permite introducir condiciones que deben cumplir los nodos seleccionados por la cláusulafor
.ORDER BY
: Permite ordenar los nodos antes de su visualización.RETURN
: Devuelve los resultados. Es la única cláusula obligatoria.
Aunque también se añade a éstas la siguiente cláusula:
GROUP BY
: Permite agrupar por nodos similares.
Cláusula for y return
Con la cláusula for
recuperaremos una serie de nodos mediante una consulta XPath y los introduciremos en una variable para poder utilizarla en la cláusula return
. Hay que señalar que la cláusula return
se ejecutará una vez por cada nodo que devuelva la cláusula for
.
1
2
for $book in /bib/book
return $book/title
En este caso el nodo title
se imprime junto con las etiquetas:
1
2
3
<title>TCP/IP Illustrated</title>
<title>Advanced Programming in the Unix Environment</title>
<title>Data on the Web</title>
Como no hemos indicado ningún documento tras in
la consulta se lanzará contra la base de datos que tengamos abierta en nuestro programa. El resto de ejemplos del manual se realizarán de esta manera, pero si quisiéramos lanzar la consulta contra un documento XML que no es una base de datos podemos hacerlo usando doc
:
1
2
for $book in doc("bib.xml")/bib/book
return $book/title
Si queremos imprimir nuestras propias etiquetas en la cláusula return
, tendremos que encerrar la variable entre llaves { }
:
1
2
for $book in /bib/book
return <titulo>{$book/title/text()}</titulo>
Obteniendo como resultado:
1
2
3
<titulo>TCP/IP Illustrated</titulo>
<titulo>Advanced Programming in the Unix Environment</titulo>
<titulo>Data on the Web</titulo>
Podemos utilizar at
dentro de la cláusula for
para obtener una variable con la numeración de los nodos que se van a recorrer:
1
2
for $book at $i in /bib/book
return <titulo>({$i}) {$book/title/text()}</titulo>
Lo hemos utilizado para incluirlo dentro de la etiqueta titulo
:
1
2
3
<titulo>(1) TCP/IP Illustrated</titulo>
<titulo>(2) Advanced Programming in the Unix Environment</titulo>
<titulo>(3) Data on the Web</titulo>
Si quisiéramos englobar todas las etiquetas anteriores en una superior, tendríamos que encerrar la consulta completa entre llaves { }
como vemos en este ejemplo:
1
2
3
4
5
6
<biblioteca>
{
for $book in /bib/book
return <titulo>{$book/title/text()}</titulo>
}
</biblioteca>
Obteniendo la salida:
1
2
3
4
5
<biblioteca>
<titulo>TCP/IP Illustrated</titulo>
<titulo>Advanced Programming in the Unix Environment</titulo>
<titulo>Data on the Web</titulo>
</biblioteca>
También podemos utilizar la estructura condicional if
dentro de la cláusula return
para modificar el resultado en función de alguna condición:
1
2
3
4
5
6
7
8
9
10
for $book in /bib/book
return
<book>
{$book/title}
{if ($book/year > 2003) then (
<new />
) else (
<out-dated />
)}
</book>
Obteniendo la salida:
1
2
3
4
5
6
7
8
9
10
11
<book>
<title>TCP/IP Illustrated</title>
<out-dated/></book>
<book>
<title>Advanced Programming in the Unix Environment</title>
<new/>
</book>
<book>
<title>Data on the Web</title>
<new/>
</book>
Cláusula let
La cláusula let
nos va a permitir crear variables con cierto contenido. La diferencia con for
es que ésta sólo se ejecutaría una sola vez con la cláusula return
. La cláusula let
asigna las variables mediante los caracteres :=
. Si el ejemplo anterior lo realizáramos con let
:
1
2
let $books := /bib/book
return <titulo>{$books/title}</titulo>
Podemos observar como la etiqueta titulo
sólo aparece una vez, es decir, no se repite para cada nodo como en el caso de la cláusula for
.
1
2
3
4
5
<titulo>
<title>TCP/IP Illustrated</title>
<title>Advanced Programming in the Unix Environment</title>
<title>Data on the Web</title>
</titulo>
La clausula let
nos va a permitir utilizar funciones de agrupación, como calcular la media, la suma, contar, etc. Estas son las mismas funciones que las que se utilizan en el lenguaje XPath y que podéis repasar aquí. Podemos por ejemplo buscar el año más alto que exista mediante la función max
para ver el último libro que se ha escrito:
1
2
let $books := /bib/book
return <last_year>{max($books/year)}</last_year>
Y la salida sería:
1
<last_year>2006</last_year>
También podemos crear varias variables seguidas con coma, por ejemplo para buscar el libro más antiguo y más nuevo:
1
2
3
4
5
6
let $older := min(/bib/book/year), $younger := max(/bib/book/year)
return
<books>
<older>{$older}</older>
<younger>{$younger}</younger>
</books>
Y la salida sería:
1
2
3
4
<books>
<older>2002</older>
<younger>2006</younger>
</books>
La cláusula let
también nos permite asignar el valor de una variable a partir de otro consulta:
1
2
3
4
let $titles := (
for $book in /bib/book
return $book/title)
return $titles
Y la salida sería:
1
2
3
<title>TCP/IP Illustrated</title>
<title>Advanced Programming in the Unix Environment</title>
<title>Data on the Web</title>
Cláusula for y let
Podemos combinar las cláusulas for
y let
. De esta manera conseguimos que la clasula let
se ejecute una vez por cada nodo, al igual que hace la clausula return
. Por ejemplo, si queremos contar el número de autores que tiene cada libro podemos utilizar la siguiente consulta:
1
2
3
4
5
6
7
for $book in /bib/book
let $autores := count($book/author)
return
<libro>
<titulo>{$book/title/text()}</titulo>
<autores>{$autores}</autores>
</libro>
Lo que conseguimos es que para cada nodo que pasa por la cláusula for
utilicemos let
para incluir en la variable $autores
la cuenta de nodos author
de dicho libro que tenemos en la variable $book
, consiguiendo el siguiente resultado:
1
2
3
4
5
6
7
8
9
10
11
12
<libro>
<titulo>TCP/IP Illustrated</titulo>
<autores>1</autores>
</libro>
<libro>
<titulo>Advanced Programming in the Unix Environment</titulo>
<autores>1</autores>
</libro>
<libro>
<titulo>Data on the Web</titulo>
<autores>3</autores>
</libro>
Aunque este mismo caso también lo podríamos realizar sin utilizar la clásula let
:
1
2
3
4
5
6
for $book in /bib/book
return
<libro>
<titulo>{$book/title/text()}</titulo>
<autores>{count($book/author)}</autores>
</libro>
Cláusula where
Con la cláusula where
podemos filtrar los nodos que se seleccionan en la cláusula for
, para ello también podemos utilizar los mismos operadores y funciones que en el leguaje XPath y que podéis repasar aquí. MUY IMPORTANTE, la cláusula where
NO filtraría los nodos si los estamos obteniendo con let
. Por ejemplo podemos buscar los títulos de un determinado autor:
1
2
3
for $book in /bib/book
where $book/author = "Stevens"
return $book/title
Y el resultado sería:
1
2
<title>TCP/IP Illustrated</title>
<title>Advanced Programming in the Unix Environment</title>
La misma consulta anterior se podría realizar de igual manera filtrando los nodos en la consulta XPath sin tener que utilizar la cláusula where
:
1
2
for $book in /bib/book[author = "Stevens"]
return $book/title
Un ejemplo más con la cláusula where
utilizando una función:
1
2
3
for $book in /bib/book
where starts-with($book/author, "S")
return $book/title
Otro ejemplo más con la cláusula where
en la que se unen varias condiciones:
1
2
3
for $book in /bib/book
where starts-with($book/author, "S") or starts-with($book/author, "T")
return $book/title
Cláusula order by
Con la cláusula order by
podemos ordenar los nodos antes de que empiece a ejecutarse la cláusula return
, ya que como sabemos, la salida será la misma que el orden que tengan los nodos en el documento o base de datos XML.
1
2
3
for $book in /bib/book
order by $book/title
return $book/title
Obteniendo la salida:
1
2
3
<title>Advanced Programming in the Unix Environment</title>
<title>Data on the Web</title>
<title>TCP/IP Illustrated</title>
Podemos ordenar por diferentes campos separándolos por coma.
1
2
3
for $book in /bib/book
order by $book/title, $book/year
return $book/title
Podemos ordenar de manera descendente (descending), ya que por defecto se ordena de manera ascendente (ascending).
1
2
3
for $book in /bib/book
order by $book/title descending
return $book/title
Cláusula group by
Con la cláusula group by
podemos agrupar los nodos en función de un valor de nodo o de atributo:
1
2
3
for $book in /bib/book
group by $publisher := $book/publisher
return <group dept="{$publisher}" count="{count($book)}"/>
Obteniendo la salida:
1
2
<group dept="Addison-Wesley" count="2"/>
<group dept="" count="1"/>
Otros ejemplos de funciones y operadores
La función distinct-values
nos permite dentro de la cláusula for
o let
seleccionar sólo los nodos que tengan valores diferentes. Pero hay que tener en cuenta que los devuelve sin sus etiquetas, como si usáramos /text()
:
1
2
for $autor in distinct-values(/bib/book/author)
where $autor
El operador except
nos permite eliminar nodos de la salida de la consulta, pero para ello es obligatorio utilizar /\*
en el nodo donde vayamos a utilizarlo como vemos en el ejemplo. En este caso también se devuelve el libro sin etiquetas, de ahí que le hayamos incluido nosotros nuestras propias etiquetas en el return
:
1
2
for $book in /bib/book
return <libro>{$book/* except $book/year except $book/author}</libro>
Bibliografía
- https://www.w3schools.com/xml/xquery_intro.asp
- https://www.w3schools.com/xml/xml_xquery.asp
- https://www.ibm.com/docs/es/db2/11.1?topic=data-introduction-xquery
- https://es.wikipedia.org/wiki/XQuery
- https://www.discoduroderoer.es/tutorial-sobre-como-usar-base-x/
- https://www.discoduroderoer.es/formas-de-mostrar-nodos-en-xquery/
- https://www.discoduroderoer.es/ordenacion-en-xquery/
- https://www.ticarte.com/contenido/que-es-el-lenguaje-xquery
- https://sites.google.com/a/elorrieta-errekamari.com/marcas/u7-almacenamiento-de-informacion-en-formato-xml/3-xquery
- https://oscarmaestre.github.io/lenguajes_marcas/tema6.html
- http://www.lsi.us.es/docs/informes/LSI-2005-02.pdf