XQuery

Un linguaggio d'interrogazione per XML


Introduzione

XML è un linguaggio di markup estremamente versatile, in grado di rappresentare il contenuto di diverse sorgenti dati:database relazionali, ad oggetti, dati semistrutturati, ecc.. XQuery, è stato progettato per essere applicabile su tutti questi tipi di sorgenti di dati XML.

XQuery deriva da Quilt un linguaggio d'interrogazione per XML, il quale a sua volta prende molte delle sue caratteristiche da altri linguaggi:

  • Sintassi per navigare all'interno della struttura di un documento simile a quella di XPath e XQL.
  • Definizione di variabili globali o locali da usare all'interno di una query, preso da XML-QL.
  • Query come insieme di proposizioni come in SQL.
  • Da OQL si prende il concetto di definire funzioni.
  • Alcuni aspetti rimangono aperti, legati al legame di XQuery con altri attività di ricerca su XML:

    Il linguaggio XQuery

    In XQuery una query è rappresentata come una espressione. XQuery supporta diversi tipi di epressioni:
    1. Espressioni di percorso
    2. Costruttori di elemento
    3. Espressioni FLWR (espressioni FLoWer ).
    4. Espressioni con operatori e funzioni.
    5. Espressioni condizionali.
    6. Quantificatori.
    7. Filtraggio.
    8. Tipi di dato.
    9. Funzioni
    10. Tipi di dato definiti dall'utente
    11. Operazioni su tipi di dato

    Espressioni di percorso

    XQuery usa una sintassi simile a quella di XPath; un'espressione di percorso è un insieme di passi, ogni passo rappresenta un movimento all'interno della struttura di un documento,il risultato di ogni passo è una lista di nodi.
     
     

    the following symbols are used:
     
    . Il nodo corrente
    .. Il padre del nodo corrente
    / Il nodo radice o il separatore tra diversi passi dell'espressione di percorso.
    // I figli del nodo corrente.
    @ Gli attributi del nodo corrente.
    * Qualsiasi nodo.
    [ ] Parentesi che che contengono un'espressione booleana per limitare i nodi selezionati in un certo passo.
    [n] Seleziona un elemento figlio da una lista di elementi.

    es: document("zoo.xml")/chapter[2]//figure[caption = "Tree Frogs"]

    In aggiunta alla sintassi di XPath, XQuery ha il predicato RANGE preso da XQL:

    Trova tutti gli elementi figure nei capitoli 2,3,4 e 5 del documento zoo.xml:

    document("zoo.xml")/chapter[RANGE 2 TO 5]//figure.
    Un altro operatore aggiunto in XQuery rispetto a XPath è l'operatore di deferenza ("->"). Esso si applica ad attributi di tipo IDREF e ritorna l'elemento referenziato, l'operatore è seguito da un nome che indica il nomem dell'elemento obiettivo:

    Trovare i titoli delle immagini referenziate dagli elementi <figref> nei capitoli di "zoo.xml" e aventi titolo "Frogs":

    document("zoo.xml")/chapter[title = "Frogs"]//figref/@refid->fig/caption

    Costruttori di elemento

    Un espressione XQuery può incapsulare il risultato di un'interrogazione, in un nuovo elemento, vediamo un tipico uso:
    LET $tagname := name($e) 
    RETURN 
    <$tagname> 
    $e/@*,  
    2 * number($e) 
    </$tagname>

    Espressioni FLWR (espressioni FLoWer )

    Una espressione FLWR ("flower") è formato dalle proposizioni: FOR, LET, WHERE, e RETURN. Come in una query SQL ,queste proposizioni devono apparire in un ordine ben preciso.

    FOR/LET : servono per assegnare un valore ad una o più variabili referenziate nella query, mentre il FOR assegna un valore alla volta alla variabile, LET assegna una lista di nodi:

    FOR $x IN /library/book

    comporta tanti assegnamenti ad $x quanti sono i book in library

    LET $x := /library/book results in a single binding

    comporta il singolo assegnamento della lista dei book a $x.

    Un espressione FLWR può contenere molte proposizioni FOR e LET. Il risultato della sequenza di FOR e LET è una lista di tuple di variabili assegnate.

    WHERE: Solo le tuple per cui le condizioni della sezione WHERE è vera sono usate nella sezione RETURN.I predicati presenti in questa sezione possono essere collegati conn AND, OR, e NOT.

    RETURN: Genera l'output della query, che può essere un nodo, un insieme di nodi o un valore primitivo.

    Vediamo alcuni esempi:

    (Q9) Titoli dei libri publicati da Morgan Kaufmann nel 1998.

    FOR $b IN document("bib.xml")//book
    WHERE $b/publisher = "Morgan Kaufmann"
    AND $b/year = "1998"
    RETURN $b/title
    (Q13) Per ogni libro che ha un prezzo maggiore del prezzo medio, ritorniamo il titolo del libro e la differenza fra il suo prezzo e il prezzo medio.
    <result>
       LET $a := avg(//book/price)
       FOR $b IN /book
       WHERE $b/price > $a
       RETURN
          <expensive_book>
             $b/title ,
             <price_difference>
                $b/price - $a
             </price_difference>
          </expensive_book>
    </result>
    (Q15) Per ogni editore, visualizziamo i libri che ha pubblicato ordinati in base al prezzo dal più caro al più economico.Gli editori sono elencati in ordine alfabetico.
    <publisher_list>
       FOR $p IN distinct(document("bib.xml")//publisher)
       RETURN
          <publisher>
             <name> $p/text() </name> ,
             FOR $b IN document("bib.xml")//book[publisher = $p]
             RETURN
                <book>
                   $b/title ,
                   $b/price
                </book> SORTBY(price DESCENDING)
          </publisher> SORTBY(name)
    </publisher_list>

    Espressioni con operatori e funzioni.

    XQuery fornisce i soliti operatori aritmetici e logici e quelli insiemistici: UNION, INTERSECT, and EXCEPT; da XQL, XQuery eredita gli operatori BEFORE e AFTER.Un esempio può chiarire l'uso di questi due operatori:

    (Q16) Tutti gli elementi presenti tra l'occorrenza del primo ed il secondo elemento incisionnella prima occorrenza di procedure.

    <critical_sequence>
       LET $p := //procedure[1]
       FOR $e IN //* AFTER ($p//incision)[1] 
              BEFORE ($p//incision)[2]
       RETURN shallow($e)
    </critical_sequence>
    La funzione shallow fa una copia del nodo,includendogli attributi ma non gli elementi figli.

    Espressioni condizionali

    (Q18) Lista delle prenotazioni ordinate per titolo. Per i giornali, visualizziamo gli editori, per le altre prenotazioni l'autore.
    FOR $h IN //holding
    RETURN
       <holding>
          $h/title,
          IF $h/@type = "Journal"
          THEN $h/editor
          ELSE $h/author
       </holding> SORTBY (title)
    come ogni espressione XQuery, le espressioni condizionali si possono trovare dovunque è atteso un valore.

    Quantificatori

    XQuery fornisce i quantificatori esistenziale ed universale, vediamo un esempio nei seguenti esempi:

    (Q19) Trovare i titoli dei libri nei quali ESISTE un paragrafo che contiene le parole sailing e windsurfing.

    FOR $b IN //book
    WHERE SOME $p IN $b//para SATISFIES
       contains($p, "sailing") 
       AND contains($p, "windsurfing")
    RETURN $b/title
    (Q20) Trovare i titoli dei libri nei quali la parola sailing è menzionata in TUTTI i paragrafi.
    FOR $b IN //book
    WHERE EVERY $p IN $b//para SATISFIES
       contains($p, "sailing")
    RETURN $b/title

    Filtraggio

    La funzione filter eleimina parti del documento ritenute inutili, mantenendo i rapporti di gerarchia.. L'esempio che segue produce una TOC, mantenendo gli elementi section, title e il testo del titolo.Il primo argomento è la radice del documento, mentre il secondo è il filtro che si deve applicare:
    LET $b := document("cookbook.xml")
    RETURN
       <toc>
          filter($b, $b//section | $b//section/title | $b//section/title/text() )
       </toc>

    Tipi di dato

    XQuery supporta i tipi di dato di XML Schema.

    Alcuni tipi di dati sono automaticamente riconosciuti da XQuery:
     
    Type Example of literal
    xsd:string "Hello"
    xsd:boolean TRUE, FALSE
    xsd:integer 47, -369
    xsd:decimal -2.57
    xsd:float -3.805E-2

    mentre gli altri hanno bisogno di un costruttore: date("2000-06-25").

    N.B: Non tutti i costruttori sono stati definiti.

    Funzioni

    XQuery fornisce una libreria di funzioni da usare nelle query: document, avg, sum, count, max, and min, distinct,empty, ecc.. Inoltre XQuery permette di definire proprie funzioni.

    (Q22) Trovare la massima profondità del documento "partlist.xml."

    NAMESPACE xsd = "http://www.w3.org/2000/10/XMLSchema-datatypes"
    
    FUNCTION depth(ELEMENT $e) RETURNS xsd:integer
    {
       -- An empty element has depth 1
       -- Otherwise, add 1 to max depth of children
       IF empty($e/*) THEN 1
       ELSE max(depth($e/*)) + 1
    }
    
    depth(document("partlist.xml"))

    Tipi di dato definiti dall'utente

    Qualsiasi tipo di dato definibile in XML Schema può essere usato in XQuery

    Una query si può riferire a qualsiasi elemento o tipo definito in uno schema che:

    1. Sono referenziati dai documenti usati nella query.
    2. Sono dichiarati attraverso la parola chiave NAMESPACE, ad esempio
    NAMESPACE xsd = "http://www.w3.org/2000/10/XMLSchema-datatypes".
    1. Possibile estensione: una query potrebbe includere un preambolo nel quale dichiarare tipi di dato locali usando la sintassi di XML Schema.
    Nell'esempio che segue, definiamo uno schema con le dichiarazioni di due tipi complessi "emp_type" and "dept_type" tale schema è referenziabile attraverso il namespace "http://www.BigCompany.com/BigNames" attraverso la dichiarazione come targetNamespace.
    <?xml version="1.0">
    <schema xmlns="http://www.w3.org/2000/10/XMLSchema" <!--namespace di default-->
       targetNamespace="http://www.BigCompany.com/BigNames">
    
       <complexType name="emp_type">
          <sequence>
             <element name="name" type="string"/>
             <element name="deptno" type="string"/>
             <element name="salary" type="decimal"/>
             <element name="location" type="string"/>
          </sequence>
       </complexType>
    
       <complexType name="dept_type">
          <sequence>
             <element name="deptno" type="string"/>
             <element name="headcount" type="integer"/>
             <element name="payroll" type="decimal"/>
          </sequence>
       </complexType>
    
    </schema>
    (Q26) Usando lo schema definito, definiamo una funzione che ritorna il numero di impiegati per diupartimento e il loro costo totale.
    NAMESPACE DEFAULT = "http://www.BigCompany.com/BigNames"
    
    FUNCTION summary(LIST(emp_type) $emps) RETURNS LIST(dept_type)
       {
       FOR $d IN distinct($emps/deptno)
       LET $e := $emps[deptno = $d]
       RETURN
          <dept>
             $d,
             <headcount> count($e) </headcount>,
             <payroll> sum($e/salary) </payroll>
          </dept>
       }
    
    summary(document("acme_corp.xml")/emp[location = "Denver"] )

    Operazioni su tipi di dato

    INSTANCEOF ritorna True se il primo operando è un'istanza del tipo indicato nel secondo operando. Per esempio

    $x INSTANCEOF zoonames:animal

    è True se il tipo dinamico di $x è zoonames:animal o un tipo derivato da zoonames:animal. INSTANCEOF ha la stessa sintassi di instanceof in Java.

    Per i tipi primiti e derivati di XML Schema si può usare l'operatore CAST . Per esempio: CAST AS integer (x DIV y) converte il risultato di x DIV y nel tipo integer. L'insieme dei tipi supportati da CAST sono ancora da definire. CAST non può essere usata per i tipi definiti dall'utente.

    TREAT dice al processatore della query di trattare un espressione come se avesse come tipo uno dei tipi derivati dal suo tipo statico. Per esempio TREAT AS Cat($mypet) dice di trattare $mypet come istanza del tipo Cat, anche se il suo tipo è un supertipo di Cat ad esempio Animal.

    -- First define some functions to set the stage
    NAMESPACE xsd = "http://www.w3.org/2000/10/XMLSchema-datatypes"
    
    FUNCTION quack(duck $d) RETURNS xsd:string
       { "String depends on properties of duck" }
    
    FUNCTION woof(dog $d) RETURNS xsd:string
       { "String depends on properties of dog" }
    
    --This function illustrates simulated subtype polymorphism
    
    FUNCTION sound(animal $a) RETURNS xsd:string
       {
       IF $a INSTANCEOF duck THEN quack(TREAT AS duck($a))
       ELSE IF $a INSTANCEOF dog THEN woof(TREAT AS dog($a))
       ELSE "No sound"
       }
    
    -- This query returns the sounds made by all of Billy's pets
    
    FOR $p IN /kid[name="Billy"]/pet
    RETURN sound($p)

    Interrogare Basi di Dati Relazionali

    Il modello relazionale è molto diffuso per memorizzare informazioni, per cui è vitale che un linguaggio di interrogazione per XML sappia accedere a tali sorgenti.Le query SQL possono essere tradotte in espressioni.

    Consideriamo il seguente schema relazionale:

    S(sno,sname)

    P(pno,descrip)

    SP(sno,pno,price)

    S contiene i dati sui fornitori (suppliers); P contiene le informazioni su le parti fornite e SP rappresenta la relazzione fra le prime due tabelle.

    Una possibile tarduzione in XML è la seguente:

    <s>
    <s_tuple>
    <sno>
    <sname>
    <p>
    <p_tuple>
    <pno>
    <descrip>
    <sp>
    <sp_tuple>
    <sno>
    <pno>
    <price>
    (Q29) Esempio di Selezione

    SQL:

    SELECT pno
    FROM p
    WHERE descrip LIKE 'Gear'
    ORDER BY pno;
    XQuery:
    FOR $p IN document("p.xml")//p_tuple
    WHERE contains($p/descrip, "Gear")
    RETURN $p/pno SORTBY(.)
    "SORTBY(.)", significa ordina gli elementi <pno> in base al loro contenuto.

    Raggruppamento

    (Q30) Trovare il codice e il prezzo medio dei componenti che hanno almeno 3 fornitori.

    SQL:

    SELECT pno, avg(price) AS avgprice
    FROM sp
    GROUP BY pno
    HAVING count(*) >= 3
    ORDER BY pno;
    XQuery:
    FOR $pn IN distinct(document("sp.xml")//pno)
    LET $sp := document("sp.xml")//sp_tuple[pno = $pn]
    WHERE count($sp) >= 3
    RETURN 
       <well_supplied_item>
          $pn,
          <avgprice> avg($sp/price) </avgprice>
       </well_supplied_item> SORTBY(pno)

    Join

    (Q31) Inner Join
    FOR $sp IN document("sp.xml")//sp_tuple,
        $p IN document("p.xml")//p_tuple[pno = $sp/pno],
        $s IN document("s.xml")//s_tuple[sno = $sp/sno]
    RETURN
       <sp_pair>
          $s/sname ,
          $p/descrip
       </sp_pair> SORTBY (sname, descrip)
    Q31 ritorna informazioni solo sui pezzi  che hanno un fornitore e dei fornitori che forniscono almeno un pezzo.

    (Q32) Left Outer Join,  mostriamo anche i fornitori che non forniscono alcun pezzo.

    FOR $s IN document("s.xml")//s_tuple
    RETURN
       <supplier>
          $s/sname,
          FOR $sp IN document("sp.xml")//sp_tuple[sno = $s/sno],
              $p IN document("p.xml")//p_tuple[pno = $sp/pno]
          RETURN $p/descrip SORTBY(.)
       </supplier> SORTBY(sname)
    In sostituzione dei dati mancanti SQL usa il valore NULL, mentre una query XML potrebbe rappresentare la mancanza di informazioni con un elemento vuoto o l'assenza dell'elemento.

    (Q33) Full Outer Join

    <master_list>
        (FOR $s IN document("s.xml")//s_tuple
         RETURN
            <supplier>
               $s/sname,
               FOR $sp IN document("sp.xml")//sp_tuple[sno = $s/sno],
                   $p IN document("p.xml")//p_tuple[pno = $sp/pno]
               RETURN
                  <part>
                     $p/descrip,
                     $sp/price
                  </part> SORTBY (descrip)
            </supplier> SORTBY(sname) 
        )
     UNION
        -- parts that have no supplier
        <orphan_parts>
           FOR $p IN document("p.xml")//p_tuple
           WHERE empty(document("sp.xml")//sp_tuple[pno = $p/pno])
           RETURN $p/descrip SORTBY(.)
        </orphan_parts>
    </master_list>

    Conclusioni

    Con l'affermarsi di XML, la distinzione fra le diverse forme di memorizzazione delle informazioni andrà scomparendo e XQuery è flinalizzato ad interrogare tutte queste sorgenti, dato che è ha le caratteristiche di tanti linguaggi.

    Le future versioni di XQuery potrebbero includere ulteriori funzionalità, tra le quali:

    1. Overloading e polimorfismo di funzioni.
    2. Update di dati in XML.