/sparql-course

Elementi di SPARQL per il corso "Open data nella pubblica amministrazione" offerto dal Politecnico di Torino - Corsi di Formazione Permanente 2015-2016 accreditato dall'INPS

Primary LanguageJavaScriptGNU General Public License v3.0GPL-3.0

SPARQL

All'interno di questo repository viene fornita un'introduzione a SPARQL, protocollo e linguaggio di query per dati pubblicati ed esposti secondo il modello RDF.

Per proporre modifiche ed arricchire questo repository potete utilizzare il meccanismo delle pull-request offerto da GitHub.

Architettura ed endpoint SPARQL

  • Le query SPARQL vengono eseguite su dataset pubblicati secondo il modello RDF.
  • Un endpoint SPARQL è in grado di accettare ed eseguire query i cui risultati sono disponibili via HTTP.
    • A seconda delle impostazioni definite sugli endpoint, è possibile interrogare e combinare tra loro dati appartenenti a dataset differenti.
  • I risultati di una query SPARQL possono essere "renderizzati" secondo diversi formati.
    • XML. SPARQL prevede uno specifico vocabolario per ottenere i risultati sotto forma di tabelle.
    • JSON. Questo formato consiste in un porting del vocabolario XML definito in SPARQL. Recentemente si sta affermando un formato chiamato JSON-LD, che risulta molto più leggibile per gli esseri umani e si presta ad essere facilmente utilizzabile nell'ambito di servizi REST e per importare dati in NoSQL database.
    • RDF. Attraverso query di tipo CONSTRUCT si ottengo dati in RDF, serializzabili in diversi formati (RDF/XML, N-Triples, Turtle, ecc.).
    • HTML. Utilizzato in particolar modo nel caso in cui le query SPARQL vengano gestite tramite un form. In genere la risposta in formato HTML viene implementata applicando un XLS per trasformare i risultatconi dal formato XML.

Struttura base di una query SPARQL

Una query SPARQL prevede nell'ordine:

  • Dichiarazione dei prefissi per poter abbreviare gli URI all'interno della query.
  • Specificazione del grafo RDF sul quale eseguire la query (non strettamente necessario nel caso in cui si volessero interrogare tutti i dati pubblicati sull'endpoint).
  • Definizione dei risultati che voglio ottenere con una query SPARQL.
  • Costruzione della query per individuare informazioni specifiche contenute all'interno del dataset.
  • Inserimento di modificatori per riorganizzare, suddividere e riordinare il risultato della query.
# dichiarazione dei prefissi
PREFIX foo: <http://example.com/resources/>
...
# specificazione del dataset
FROM ...
# definizione dei risultati
SELECT ...
# query
WHERE {
    ...
}
# modificatori
ORDER BY ...

Dataset di riferimento

DBpedia è un progetto che ha l'obiettivo di pubblicare secondo il modello RDF le informazioni provenienti da Wikipedia. DBpedia include dati estratti in automatico dalle infobox, dalla gerarchia di categorie, dagli abstract degli articoli e da fonti esterne.

Identificazioni di pattern all'interno di dati RDF

Clausola SELECT, variabili e pattern di triple

All'interno del dataset DBPedia identifica la label (ovvero la stringa "human readable") che si riferisce alla risorsa http://dbpedia.org/page/Stanley_Kubrick. Generalmente le label sono associate alle risorse tramite il predicato rdfs:label

PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?label
WHERE {
   <http://dbpedia.org/resource/Stanley_Kubrick> rdfs:label ?label
}

Tips

  • In SPARQL le variabili vengono definite con un punto interrogativo (?) e includono qualsiasi tipo di nodo (risorsa o valore letterale) all'interno del dataset RDF.
  • I pattern definiti nella query sono triple in cui uno o più elementi vengono rimpiazzati da una variable.
  • La clausola SELECT consente di ottenere una tabella con i valori che soddisfano le richieste della query.

Proposta di esercizio

Pattern multipli e attraversamento del grafo

SELECT ?movie ?distributor
WHERE {
    ?movie <http://dbpedia.org/ontology/director> <http://dbpedia.org/resource/Stanley_Kubrick> .
    ?movie <http://dbpedia.org/property/distributor> ?distributor .
}

Tips

  • In questo caso la risorsa http://dbpedia.org/resource/Stanley_Kubrick non è il soggetto, ma è l'oggetto dell'asserzione. Cosa sarebbe accaduto se avessi invertito il soggetto e l'oggetto?
  • All'interno del dataset non esiste un collegamento esplicito tra Stanley Kubrick e le case di distribuzione cinematografica con cui ha lavorato, ma lo posso ricavare dall'attraversamento del grafo.
  • Quando vi sono più asserzioni occorre inserire un punto alla fine di ogni tripla.

Proposta di esercizio

  • Provate ad aggiungere alla query precedente gli elementi necessari per individuare i fondatori e la data di fondazione della casa di distribuzione.

Soluzioni

Una possibile soluzione è quella proposta da uno studente del corso, Marco Micotti. Utilizzando la clausola UNION è possibile estrarre le informazioni richieste a partire dalle case di distribuzione in DBpedia, che sono collegate a predicati diversi per esprimere le informazioni riguardanti i fondatori e le date di fondazione.

SELECT ?movie ?distributor ?founder ?founddate
WHERE {
    ?movie <http://dbpedia.org/ontology/director> <http://dbpedia.org/resource/Stanley_Kubrick> .
    ?movie <http://dbpedia.org/property/distributor> ?distributor .
    {?distributor <http://dbpedia.org/ontology/foundedBy> ?founder.
     ?distributor <http://dbpedia.org/ontology/foundingDate> ?founddate.}
    UNION 
    {{?distributor <http://dbpedia.org/property/founders> ?founder .}
      UNION
      {?distributor <http://dbpedia.org/property/founder> ?founder .
      }
     ?distributor <http://dbpedia.org/property/foundation> ?founddate .}
}

Modificatori e filtri in SPARQL

Modificatori: riorganizzare la risposta di una query

Tra i modificatori che possono essere utilizzati in SPARQL per riorganizzare le risposte di una query vi sono:

  • DISTINCT. Elimina le occorrenze duplicate di uno o più parametri ottenute tramite una specifica query.
  • LIMIT. Limita il numero di righe che costituiscono la risposta ad una query.
  • OFFSET. Consente di recuperare una "fetta" (slice) della risposta a partire da una riga specifica. E' utile soprattutto per il "paging" e per gestire il comportamento di default degli endpoint SPARQL che erogano al massimo 10.000 righe per ogni risposta.
  • ORDER BY. Riordina le righe della risposta ad una query sulla base di una o più variabili. L'ordinamento può essere ascendente o discendente.
SELECT DISTINCT ?director ?directorLabel
WHERE {
    ?movie <http://dbpedia.org/ontology/director> ?director .
    ?director rdfs:label ?directorLabel .
} ORDER BY ASC(?directorLabel) LIMIT 50 OFFSET 200

Proposta di esercizio

Provate ad eseguire la query sull'endpoint http://dbpedia.org/sparql e modificarla così come proposto di seguito, verificando se la risposta si modifica secondo ciò che vi aspettate.

  1. Che cosa accade se rimuovo il modificatore DISTINCT? Perché secondo voi la risposta viene replicata? Per capirlo meglio, provate ad aggiungere la variabile ?movie nella clausola SELECT della query: SELECT ?movie ?director ?directorLabel.
  2. Che cosa accade cambiando il valore di LIMIT?
  3. Osservate che cosa accade definendo un OFFSET pari a 210.
  4. Inserite nuovamente il modificatore DISTINCT e rimuovete dalla query la clausola ORDER BY ASC(?directorLabel). Cosa notate? Perché sembra che i concetti vengano replicati su più righe nonostante la parola chiave DISTINCT.

Per ovviare al problema che si verifica nel punto 4, è necessario introdurre il concetto dei filtri.

Filtri: individuare sottoinsiemi di risultati

Attraverso la parola chiave FILTER è possibile stabilire una condizione booleana tramite la quale filtrare i risultati presenti all'interno della risposta.

Riprendiamo una query simile alla precedente:

SELECT DISTINCT ?director ?directorLabel
WHERE {
    ?movie <http://dbpedia.org/ontology/director> ?director .
    ?director rdfs:label ?directorLabel .
} LIMIT 50

Proviamo ad aggiungere un filtro sulla lingua e verificare il risultato:

SELECT DISTINCT ?director ?directorLabel
WHERE {
    ?movie <http://dbpedia.org/ontology/director> ?director .
    ?director rdfs:label ?directorLabel .
    FILTER (langMatches(lang(?directorLabel), "EN")) .
} LIMIT 50

I filtri che sono a disposizione per effettuare le query SPARQL possono essere di diversa natura:

  • Logici: !, &&, ||
  • Matematici: +, -, *, /
  • Comparazione: =, !=, >, <, ...
  • Test: isURI, isBlank, isLiteral, bound
  • Di accesso: str, lang, datatype
  • Altri: sameTerm, langMatches, regex

Requisiti opzionali all'interno delle query

Il risultato di una query SPARQL deve soddisfare tutti i pattern di triple che vengono definiti dal costrutto WHERE. Tuttavia, alcuni pattern possono essere resi opzionali e dunque non devono essere necessariamente soddisfatti all'interno del risultato della query.

Osserva il risultato che si ottiene con le seguenti query:

SELECT DISTINCT ?director ?directorLabel ?quote
WHERE {
    ?movie <http://dbpedia.org/ontology/director> ?director .
    ?director rdfs:label ?directorLabel .
    ?director <http://dbpedia.org/property/quote> ?quote . 
    FILTER (langMatches(lang(?directorLabel), "EN")) .
}
SELECT DISTINCT ?director ?directorLabel ?quote
WHERE {
    ?movie <http://dbpedia.org/ontology/director> ?director .
    ?director rdfs:label ?directorLabel .
    OPTIONAL {?director <http://dbpedia.org/property/quote> ?quote} . 
    FILTER (langMatches(lang(?directorLabel), "EN")) .
}

Proposta di esercizio

  • Per ogni regista, individuare anche i film di cui è stato produttore come parametro opzionale.

ASK, DESCRIBE, CONSTRUCT

Oltre alla clausola SELECT è possibile specificare altre parole chiave a seconda del risultato che vogliamo ottenere con la nostra query.

ASK

Consente di ottenere una risposta booleana a partire dalla query specificata.

ASK
WHERE {
   <http://dbpedia.org/resource/Quentin_Tarantino> <http://dbpedia.org/ontology/birthDate> ?qtBirthDate .
   <http://dbpedia.org/resource/Stanley_Kubrick> <http://dbpedia.org/ontology/birthDate> ?skBirthDate .
   FILTER(?skBirthDate < ?qtBirthDate) .
}

DESCRIBE

Consente di ottenere come risultato un RDF che descrive le risorse specificate. Il server è libero di interpretare una DESCRIBE secondo la propria implementazione, per questi motivi query eseguite su endpoint differenti non sono necessariamente interoperabili.

DESCRIBE ?movie {
   ?movie <http://dbpedia.org/ontology/director> <http://dbpedia.org/resource/Stanley_Kubrick> .
}

Tips

  • Nel caso in cui non venga definito un prefisso in maniera esplicita, l'endpoint stesso lo crea in automatico utilizzando l'espressione ns*(namespace + un numero).

CONSTRUCT

A differenza della clausola SELECT che consente di ottenere una tabella con all'interno dei risultati, attraverso una query CONSTRUCT si ottiene un grafo RDF. Tale grafo viene costruito prelevando i dati di interesse tramite una query "tradizionale" ed utilizzando i risultati ottenuti per completare il template definito dalla query CONSTRUCT.

Negli esempi precedenti abbiamo visto come la risorsa http://dbpedia.org/resource/Stanley_Kubrick non fosse direttamente collegata alle case di distribuzione dei suoi film. Per questi motivi, potrei creare un grafo in cui rendo esplicito questo collegamento tramite una query CONSTRUCT.

PREFIX mo: <http://myontology.org/>

CONSTRUCT { 
  <http://dbpedia.org/resource/Stanley_Kubrick> mo:workWithDistributor ?distributor .
}
WHERE { 
    ?movie <http://dbpedia.org/ontology/director> <http://dbpedia.org/resource/Stanley_Kubrick> .
    ?movie <http://dbpedia.org/property/distributor> ?distributor .
}

Tips

  • In questo caso ho costruito una proprietà che non esisteva in precedenza per definire una nuova asserzione.
  • Il grafo ottenuto tramite la query CONSTRUCT è creato in locale e dunque non vengono effettivamente pubblicate nuove triple sull'endpoint.

Proposta di esercizio

Una query CONSTRUCT può essere utilizzata per modificare il vocabolario attraverso cui definire i predicati. Nel caso della risorsa http://dbpedia.org/resource/Stanley_Kubrick il nome e il cognome del regista vengono definiti in DBpedia tramite la proprietà http://xmlns.com/foaf/0.1/name. Provate ad utilizzare una query CONSTRUCT per costruire un nuovo grafo, scegliendo il predicato corretto definito nell'ontologia https://www.w3.org/TR/vcard-rdf/.

Query avanzate

Combinando gli elementi affrontati sino ad ora è possibile costruire delle query avanzate in grado di sfruttare tutte le potenzialità di SPARQL e dei Linked Data.

Negazione in una query SPARQL

La negazione consente di sfruttare triple esistente per filtrare il risultato. In questo caso voglio ottenere tutti i film diretti da Stanley Kubrick che hanno una casa di distribuzione diversa da quella che ha distribuito il film Blade Runner.

SELECT DISTINCT ?movie
WHERE {
    ?movie <http://dbpedia.org/ontology/director> <http://dbpedia.org/resource/Stanley_Kubrick> .
    ?movie <http://dbpedia.org/property/distributor> ?distributor .
    OPTIONAL {<http://dbpedia.org/resource/Blade_Runner> <http://dbpedia.org/property/distributor> ?badDistributor . FILTER (?distributor = ?badDistributor) .} .
    FILTER ( !BOUND(?badDistributor) )
}

Proposta di esercizio

SUM

Per mostrare le potenzialità di SPARQL per la gestione delle somme viene proposta una query SPARQL sul repository RDF costruito a partire dai dati dei contratti pubblici italiani (http://public-contracts.nexacenter.org/). L'enpoint SPQARQL è disponibile all'indirizzo: http://public-contracts.nexacenter.org/sparql.

La query seguente mostra l'ammontare ricevuto nell'ambito di contratti pubblici dall'azienda identificata con la partita IVA 04145300010.

PREFIX pc: <http://purl.org/procurement/public-contracts#>
PREFIX payment: <http://reference.data.gov.uk/def/payment#>

SELECT SUM(?amount) as ?paidTotal ?company
WHERE {
    SELECT DISTINCT ?contract ?amount ?company
    WHERE {
        ?company <http://purl.org/goodrelations/v1#vatID> "04145300010".
        ?bid pc:bidder ?company .
        ?contract pc:awardedTender ?bid .
        ?contract payment:payment ?payment . 
        ?payment payment:netAmount ?amount .
    }
}

Query federate: combinare dati provenienti da diversi endpoint

La potenzialità dei Linked Data risiede nel fatto che è possibile combinare tra loro dati provenienti da endpoint differenti. Ad esempio è possibile individuare la PEC di una pubblica amministrazione presente del dataset dei contratti pubblici interrogando il repository SPCDATA.

PREFIX gr:<http://purl.org/goodrelations/v1#>
PREFIX pc: <http://purl.org/procurement/public-contracts#>
PREFIX payment: <http://reference.data.gov.uk/def/payment#>
PREFIX spc: <http://spcdata.digitpa.gov.it/>

SELECT ?label SUM(?amount) as ?paidAmounts ?officialEmail
WHERE {
   SELECT DISTINCT *
   WHERE {
      ?contractingAutority <http://purl.org/goodrelations/v1#vatID> "00518460019".
      ?contractingAutority rdfs:label ?label.
      ?contractingAutority owl:sameAs ?uriSpc.
      SERVICE <http://spcdata.digitpa.gov.it:8899/sparql> { 
         OPTIONAL { ?uriSpc spc:PEC ?officialEmail.} 
         }
         ?contract pc:contractingAutority  ?contractingAutority.
         ?contract payment:payment ?payment.
         ?payment payment:netAmount ?amount.
      } ORDER BY ?contract
   } 

Step per la generazione di dati in RDF

In questa sezione vengono riportati alcuni suggerimenti per poter procedere con la trasformazione in RDF.

Scegliere un identificativo univoco

Occorre scegliere l'identificativo tra tutti i campi presenti all'interno del dataset, che verrà utilizzato per costruire l'URI dell'entità. Consideriamo ad esempio la seguente entry all'interno di un CSV che include informazioni relative ai toponimi: https://github.com/giuseppefutia/sparql-course/blob/master/samples/toponimi.csv.

In questo specifico caso gli esperti di dominio sanno che l'identificativo univoco è inserito nella voce "PROGR_NAZI". Il valore "370976" verrà utilizzato per costruire l'URI dell'entità che costituirà il soggetto principale all'interno del file RDF da generare.

In certi casi non è sempre possibile trovare un identificativo univoco: consideriamo ad esempio una singola entry del dataset relativo al patrimonio immobiliare piemontese: https://github.com/giuseppefutia/sparql-course/blob/master/samples/patrimonio-immobiliare.csv.

In questo specifico caso è conveniente utilizzare un identificativo incrementale.

Definizione dell'URI di base

Come ben sappiamo, le entità in RDF vengono espresse secondo URI. Per questo motivo occorre scegliere la forma dell'URI che, da un lato, costituirà la base per definire le entità del mio dataset, dall'altro, consentirà di costruire i predicati dell'ontologia interna.

Riprendendo ad esempio il caso dei toponimi, potremmo modellare i dati come segue:

Scelta delle ontologie

Oltre a definire predicati specifici adatti a descrivere il dominio dei propri dati, una buona pratica per la creazione di RDF è andare alla ricerca di ontologie standard che rendano interoperabile il proprio dataset con la Linked Data cloud.

Riprendendo il caso dei toponimi, è possibile esprimere l'identificativo del toponimo stesso come segue:

<http://toponomastica.piemonte.it/id/toponimi/370976>  <dcterms:identifier> '370976' 

In questo caso utilizzo l'ontologia Dublin Core.

Interlinking tra entità appartenenti a dataset differenti

L'interlinking tra entità appartenenti a dataset differenti può rivelarsi un task molto complesso. In alcuni casi, qualora ci siano degli identificativi ben definiti, il processo risulta piuttosto semplice. Considerate ad esempio la seguente query SPARQL che consente di individuare una pubblica amministrazione presente nel dataset dei contratti pubblici all'interno del dataset di SPCDATA:

SELECT DISTINCT ?entity
WHERE { ?entity <http://www.w3.org/ns/org#identifier> '80057930150'}

In questo caso, l'identificativo della risorsa su SPCDATA è il medesimo identificativo della risorsa sul grafo dei contratti pubblici: http://public-contracts.nexacenter.org/pc/businessEntities/80057930150/html.

In altri casi occorre fare dei "guess" a partire dalle label. Provate la differenza tra queste due query:

SELECT DISTINCT ?museum WHERE {
   ?museum rdfs:label ?label .
   FILTER regex( str(?label), "Museo egizio", "i" ) .
} LIMIT 10
SELECT DISTINCT ?museum WHERE {
   ?museum rdfs:label ?label .
   ?museum rdf:type <http://dbpedia.org/class/yago/MuseumsInTurin> .
   FILTER regex( str(?label), "Museo egizio", "i" ) .
} LIMIT 10

GeoSPARQL e dati spaziali

Per poter modellare e serializzare dati espressi in WKT secondo il modello RDF, potete utilizzare il seguente esempio:

@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix geo: <http://www.opengis.net/ont/geosparql#> .
@prefix ex: <http://www.example.org/POI#> .
@prefix sf: <http://www.opengis.net/ont/sf#> .
ex:WashingtonMonument a ex:Monument;
 rdfs:label "Washington Monument";
 geo:hasGeometry ex:WMPoint .
ex:WMPoint a sf:Point;
 geo:asWKT "POINT(-77.03524 38.889468)"^^geo:wktLiteral.
ex:NationalMall a ex:Park;
 rdfs:label "National Mall";
 geo:hasGeometry ex:NMPoly .
ex:NMPoly a sf:Polygon;
 geo:asWKT "POLYGON((-77.050125 38.892086, -77.039482 38.892036, -77.039482 38.895393,
-77.033669 38.895508, -77.033585 38.892052, -77.031906 38.892086, -77.031883 38.887474, -
77.050232 38.887142, -77.050125 38.892086 ))"^^geo:wktLiteral.

Un esempio di query SPARQL potrebbe essere: quali monumenti si trovano all'interno di quali parchi?

PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX ex: <http://www.example.org/POI#>
SELECT ?m ?p
WHERE {
   ?m a ex:Monument ;
   geo:hasGeometry ?mgeo .
   ?p a ex:Park ;
   geo:hasGeometry ?pgeo .
   ?mgeo geo:sfWithin ?pgeo .
}

Contatti