Wikidata:tutorial d'SPARQL
WDQS, el Servei de Consultes, és una eina per respondre preguntes que pugueu tenir, i aquesta guia us ajudarà a aprendre com utilitzar-lo. Vegeu també el tutorial interactiu de Wikimedia Israel.
Abans d'escriure la vostra pròpia consulta d'SPARQL, reviseu {{Item documentation}}
o qualsevol altra plàntilla de consulta SPARQL genèrica per veure si la vostra consulta ja hi ha estat inclosa.
Abans de començar
Aquesta guia pot semblar llarga, tant que potser intimida. No us espanteu! L'SPARQL és complicat, però només amb els conceptes bàsics ja es pot anar força lluny. Si voleu, podeu deixar de llegir després de la nostra primera consulta i ja podreu escriure moltes consultes interessants. Les seccions que venen a continuació d'això només afegeixen informació sobre altres elements que podeu utilitzar per escriure consultes diferents. Cadascuna d'aquestes seccions us capacitarà per escriure consultes encara més potents, però cap d'elles és necessària: podeu deixar de llegir en qualsevol moment i, tot i així, disposareu de molts coneixements útils.
Si no heu sentit mai a parlar de Wikidata, SPARQL o WDQS, aquí en teniu una explicació breu:
- Wikidata és una base de dades de coneixement. Conté molts fets, com ara que «la capital de Canadà és Ottawa», o que «la Mona Lisa fou pintada a l'oli en una fusta de pollancre», o que «l'or té una conductivitat tèrmica de 25.418 joules per mol kelvin».
- L'SPARQL és un llenguatge per formular consultes a bases de dades de coneixement. Amb la base de dades correcta, una consulta SPARQL podria preguntar qüestions com ara “quina és la tonalitat musical més popular?” o “quin personatge ha estat interpretat per més actors?” o “quina és la distribució de tipus de sang?” o “quines obres d'autor han entrat en el domini públic enguany?”.
- WDQS, el Servei de Consulta de Wikidata ajunta les dues coses: en definir una consulta SPARQL, el servei l'executa usant les dades de Wikidata, mostrant-ne el resultat.
Conceptes bàsics d'SPARQL
Una consulta senzilla en SPARQL s'assembla a això:
SELECT ?a ?b ?c
WHERE
{
x y ?a.
m n ?b.
?b f ?c.
}
La clàusula SELECT
llista les variables que volem consultar (les variables comencen amb un signe d'interrogació) i la clàusula WHERE
conté les restriccions que hi volem aplicar, en forma de ternes.
Tota la informació de Wikidata (i d'altres bases de dades de coneixement similars) està desada en forma de ternes;
quan executem la consulta, el servei de consultes intenta emplenar les variables amb valors reals existents a la base de dades, tornant un resultat per a cada combinació de variables que troba.
Una terna es pot considerar com dos vèrtexs (és a dir, 2 nodes, 2 recursos) connectats per una aresta (un arc, una propietat) dins del vast multigràfic de propietats directe (orientat) que forma Wikidata. Es pot llegir com una frase (per això s'acaba amb un punt), amb un «subjecte», un «predicat» i un «complement directe».
SELECT ?fruita
WHERE
{
?fruita ésDeColor groc.
?fruita téGust agre.
}
El resultat d'aquesta consulta pot incloure, per exemple, «llimona». A Wikidata, la majoria de propietats assumeixen que els ítems «tenen» propietats, pel què la consulta podria ser:
SELECT ?fruita
WHERE
{
?fruita color groc.
?fruita gust agre.
}
el que es pot llegir com “?fruita
té color ‘groc’” (no “?fruita
és de color ‘groc’” – tinguem això present per a propietats com “pare”/“fill”!).
Tot i això, aquest no és un bon exemple de WDQS. El gust és subjectiu, així que Wikidata no en té una propietat. En canvi, observem la relació pare/fill, que és menys ambigua.
La nostra primera consulta
Suposem que volem llistar tots els fills del compositor barroc Johann Sebastian Bach. Utilitzant psèudo-elements com a les consultes anteriors, com escriuríem la consulta?
Amb sort, aconseguirem una cosa així:
SELECT ?fill
WHERE
{
# fill «té pare» Bach
?fill pare Bach.
# (Nota: tot el que hi ha darrere d'un ‘#' és un comentari i WDQS ho ignora.)
}
o això,
SELECT ?fill
WHERE
{
# fill «té pare» Bach
?fill pare Bach.
}
o això,
SELECT ?fill
WHERE
{
# Bach «té fill» fill
Bach fill ?fill.
}
Les dues primeres ternes diuen que ?fill
ha de tenir Bach com a pare; la tercera diu que Bach ha de tenir el fill ?fill
. Per ara i tant, quedem-nos amb la segona.
Aleshores, què falta per a convertir això en una consulta vàlida de WDQS? A Wikidata, els ítems i les propietats no s'identifiquen amb noms llegibles-pels-humans com «pare» (una propietat) o «Bach» (un ítem). (I per una bona raó: «Johann Sebastian Bach» també és el nom d'un pintor alemany, i «Bach» també es podria referir al cognom, la comuna francesa, el cràter de Mercuri, etc). En canvi, els ítems i propietats de Wikidata tenen assignades un identificador. Per a trobar l'identificador d'un ítem, fem una cerca de l'ítem i en copien el número Q de l'ítem que coincideixi amb el que estem buscant (per exemple, basant-nos en la descripció). Per trobar l'identificador d'una propietat fem el mateix, però cercant amb «P:terme» amb el que ampliem la cerca a les propietats. Això ens diu que el famós compositor Johann Sebastian Bach és Q1339 i que la propietat per a designar el pare d'un ítem és la P:P22.
Finalment, però no per això menys important, necessitem incloure prefixos. Per ternes senzilles de WD
QS, els elements s'haurien de prefixar amb wd:, i les propietats amb wdt
:. (Però això només s'aplica als valors fixos, les variables no porten prefix!)
I, així, arribem a la nostra primera consulta WDQS:
SELECT ?fill
WHERE
{
# ?fill pare Bach
?fill wdt:P22 wd:Q1339.
}
Feu clic a l'enllaç «Proveu-ho» i després «Executeu» la consulta a la pàgina de WDQS. Què obteniu?
fill |
---|
wd:Q57225 |
wd:Q76428 |
… |
Bé, això és decebedor. Només veieu els identificadors. Si hi feu clic, anireu a la seva pàgina de Wikidata (on hi ha una etiqueta entenedora pels humans), però no hi ha una forma millor de veure els resultats?
Resulta que sí que n'hi ha una (oi que les preguntes retòriques són genials?), si hi incloeu el text màgic
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
en algun lloc dins la clàusula WHERE
, aconseguireu variables addicionals: Per a cada variable ?foo
de la vostra consulta, ara també tindreu una variable ?fooLabel
, la qual conté l'etiqueta de l'element ?foo
subjacent. Si afegiu això a la clàusula SELECT
, obtindreu tant l'element com la seva etiqueta:
SELECT ?fill ?fillLabel
WHERE
{
# ?fill pare Bach
?fill wdt:P22 wd:Q1339.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Proveu a executar aquesta consulta i hauríeu de veure els números d'ítem i els noms dels diferents fills.
fill | fillLabel |
---|---|
wd:Q57225 | Johann Christoph Friedrich Bach |
wd:Q76428 | Carl Philipp Emanuel Bach |
… |
Autocompletat
El fragment de codi de SERVICE
sembla complicat de recordar, oi? I haver d'anar fent consultes tota l'estona mentre escriviu la consulta és tediós. Afortunadament, WDQS ofereix una bona solució per això: l'«autocompletat». A l'editor de consultes de query.wikidata.org, hi podeu prémer en qualsevol moment les tecles Ctrl+Espai (o Alt+Retorn o Ctrl+Alt+Retorn) per a obtenir suggeriments del codi que podríeu necessitar. Seleccioneu el suggeriment correcte fent fletxa amunt/fletxa avall i prement Retorn.
Per exemple, en comptes d'escriure SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
cada cop, podeu teclejar només SERV
, prémer Ctrl+Espai i el primer suggeriment serà l'etiqueta completa de service, preparada per a ser executada! Premeu Retorn per a acceptar-lo. (El formatat serà una mica diferent, però no importa).
I l'autocompletat també pot fer la cerca per sí mateix. Si teclegeu un dels prefixos de Wikidata, com wd:
o wdt:
, i després escriviu qualsevol cosa darrere, Ctrl+Espai farà la cerca del text a Wikidata i us farà suggeriments. wd:
busca ítems i wdt:
propietats. Per exemple, en comptes de fer la cerca dels ítems Johann Sebastian Bach (Q1339) i father (P22), podeu senzillament teclejar wd:Bach
i wdt:pare
i seleccionar el suggeriment correcte de l'autocompletat. Això fins i tot funciona amb espais al text. Per exemple: wd:Johann Sebastian Bach
.
Patrons avançats de ternes
Ara ja sabem els fills d'en Johann Sebastian Bach -més concretament: tots els ítems que tenen com a pare en Johann Sebastian Bach. Però Bach va tenir dues esposes i, per tant, aquests ítems tenien dues mares diferents: i si només volem veure els fills de Johann Sebastian Bach amb la seva primera dona, Maria Barbara Bach (Q57487)? Proveu a escriure aquesta consulta, utilitzant l'anterior com a exemple.
Ja ho heu fet? Fem un cop d'ull a la solució! La manera més senzilla és fer-ho afegint una segona terna amb aquesta restricció:
SELECT ?fill ?fillLabel
WHERE
{
?fill wdt:P22 wd:Q1339.
?fill wdt:P25 wd:Q57487.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
En català, això és llegiria com:
Fill té pare Johann Sebastian Bach.
Fill té mare Maria Barbara Bach.
Sona estrany, oi? En llenguatge natural, ho hauríem abreujat així:
Fill té pare Johann Sebastian Bach i mare Maria Barbara Bach.
De fet, és pot expressar la mateixa abreviació en SPARQL: si acabeu una terna amb punt i coma (;
), en comptes de amb un punt, podeu afegir un altre parell predicat-complement directe. Això ens permet abreujar la consulta anterior com:
SELECT ?fill ?fillLabel
WHERE
{
?fill wdt:P22 wd:Q1339;
wdt:P25 wd:Q57487.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
que ofereix el mateix resultat, però la consulta és més concisa.
Ara suposem que, dels resultats, només ens interessen els fills que eren compositors i pianistes. Les propietats i ítems rellevants son occupation (P106), composer (Q36834) i pianist (Q486748). Feu la prova d'actualitzar la consulta anterior amb aquestes restriccions!
Aquesta és la meva solució:
SELECT ?fill ?fillLabel
WHERE
{
?fill wdt:P22 wd:Q1339;
wdt:P25 wd:Q57487;
wdt:P106 wd:Q36834;
wdt:P106 wd:Q486748.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Aquí s'utilitza l'abreviació ;
dos cops més per a afegir les dues ocupacions requerides. Però ja us haureu adonat que encara hi ha algunes repeticions. Això és com si diguéssim:
Fill té l'ocupació compositor i l'ocupació pianista.
que podríem abreujar com:
Fill té ocupació compositor i pianista.
I l'SPARQL té sintaxi per a això també: de la mateixa forma que ;
us permet afegir un parell predicat-complement directe a una terna (reutilitzant el subjecte), una ,
ens permet afegir un altre complement directe a la terna (reutilitzant tant el subjecte com el predicat). Amb això, la consulta es pot abreujar com:
SELECT ?fill ?fillLabel
WHERE
{
?fill wdt:P22 wd:Q1339;
wdt:P25 wd:Q57487;
wdt:P106 wd:Q36834,
wd:Q486748.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Nota: la indentació i altres espais en blanc realment no son importants -només fan el codi més llegible. També es pot escriure com:
SELECT ?fill ?fillLabel
WHERE
{
?fill wdt:P22 wd:Q1339;
wdt:P25 wd:Q57487;
wdt:P106 wd:Q36834, wd:Q486748.
# les dues ocupacions en una línia
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
o, força menys llegible:
SELECT ?fill ?fillLabel
WHERE
{
?fill wdt:P22 wd:Q1339;
wdt:P25 wd:Q57487;
wdt:P106 wd:Q36834,
wd:Q486748.
# sense indentació; costa bastant distingir entre ; i ,
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Per sort, l'editor de WDQS indenta les línies de forma automàtica i habitualment no us n'heu de preocupar.
Fem un resum: Hem vist que les consultes s'estructuren com a text. Cada terna sobre un subjecte s'acaba amb un punt. Diversos predicats sobre el mateix subjecte es poden separar amb punt i coma, i diversos complements directes del mateix subjecte i predicat es poden llistar separats per comes.
SELECT ?s1 ?s2 ?s3
WHERE
{
?s1 p1 o1;
p2 o2;
p3 o31, o32, o33.
?s2 p4 o41, o42.
?s3 p5 o5;
p6 o6.
}
Ara introduirem una abreviació més que ofereix l'SPARQL. Imaginem un altre escenari hipotètic...
Suposem que no volem els fills d'en Bach. Però estem interessants en els seus nets. Tenim una complicació aquí: els nets poden relacionar-se amb en Bach mitjançant la mare o el pare, que son dues propietats diferents. Fem-ho diferent, capgirem la relació: Wikidata també té la propietat «fill», P:P40, que apunta de pare a fill i és independent del gènere. Amb aquesta informació, podeu escriure la consulta que retornaria els nets d'en Bach?
Aquesta és la solució proposada:
SELECT ?net ?netLabel
WHERE
{
wd:Q1339 wdt:P40 ?fill.
?fill wdt:P40 ?net.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
En llenguatge natural, això es llegeix com:
Bach té un fill
?fill
.
?fill
té un fill?net
.
Un cop més, podem abreujar la frase en català i, després, veurem com l'SPARQL permet fer la mateixa abreviació. Observem com, de fet, no ens importa el fill: només utilitzem la variable per relacionar-la amb el net. Podem, per tant, abreujar la frase com:
Bach té com a fill algú que té com a fill
?net
.
En comptes de dir qui és el fill d'en Bach, només diem «algú»: no ens importa qui és. Però podem utilitzar-los de referència per què diem «algú «que»»: això inicia una clàusula relativa, i dins d'aquesta clàusula relativa hi podem dir coses com «algú» (per exemple, «que tingui un fill ?net
»). En certa manera, «algú» és una variable, però una d'especial que només és vàlida dins d'una clàusula relativa, i una a la qual no ens hi referim de forma explícita (diem, «algú que és X i fa Y» -això són dos «algú» diferents).
En SPARQL, això es pot escriure com:
SELECT ?net ?netLabel
WHERE
{
wd:Q1339 wdt:P40 [ wdt:P40 ?net ].
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Podeu utilitzar un parell de claudàtors ([]
) al lloc d'una variable, que actuarien com una variable anònima. Dins d'aquests claudàtors, podem especificar-hi parells predicat-complement directe, tal i com ho fem després de ;
després d'una terna normal; el subjecte implícit és, en aquest cas, la variable anònima que representen els claudàtors. (Nota:igual que després de ;
, podeu afegir més parells de predicat-complement directe amb més punts i coma, o més objectes pel mateix predicat amb comes).
I això és tot sobre els patrons de ternes! Hi ha molt més a parlar d'SPARQL, però com que estem a punt de deixar les parts que tenen relació directa amb el llenguatge natural, resumirem aquesta relació un altre cop:
llenguatge natural | exemple | SPARQL | exemple |
---|---|---|---|
frase | Julieta estima Romeo. | punt | julieta estima romeo.
|
conjunció (clàusula) | Romeo estima Julieta i mata ell mateix. | punt i coma | romeo estima julieta; mata romeo.
|
conjunció (nom) | Romeo mata Tybalt i ell mateix. | coma | romeo mata tybalt, romeo.
|
clàusula relativa | Julieta estima algú que mata Tybalt. | claudàtors | julieta estima [ mata tybalt ].
|
Instàncies i classes
Abans, hem dit que la majoria de propietats de Wikidata contenen relacions de «té»: «té» fill, «té» pare, «té» ocupació. Però algun cop (de fet, sovint), també hem de parlar de que alguna cosa «és». Però, de fet, això són dos tipus de relacions:
- Allò que el vent s'endugué és una pel·lícula.
- Una pel·lícula és una obra d'art.
Allò que el vent s'endugué és una pel·lícula en concret. Té un director concret (Victor Fleming), una durada concreta (238 minuts), un repartiment (Clark Gable, Vivien Leigh, etc), i més coses.
Pel·lícula és un concepte general. Les pel·lícules poden tenir directors, durades i actors del repartiment, però el concepte «pel·lícula» com a tal no té cap director, durada, ni intèrprets. I tot i que una pel·lícula és una obra d'art, i les obres d'art acostumen a tenir un creador, el concepte de «pel·lícula» no té un creador -només «instàncies» concretes d'aquest concepte en tenen.
Aquesta diferència és la raó pel què hi ha dues propietats «és» a Wikidata: instance of (P31) i subclass of (P279). «Allò que el vent s'endugué» és una instància concreta de la classe «pel·lícula»; la classe «pel·lícula» és una subclasse (més específicament, una especialització d'una classe) d'una classe més general, «obra d'art».
Per ajudar-vos a entendre la diferència, podeu provar a utilitzar dos verbs diferents: «és un» i «és una mena de». Si «és una mena de» funciona (per exemple: una pel·lícula «és una mena de» obra d'art), indica que estem parlant d'una subclasse, d'una especialització d'una classe més general i que hauríem d'utilitzar subclass of (P279). Si «és una mena de» no funciona (per exemple: la frase Allò que el vent s'endugué «és una mena de» pel·lícula no té sentit), indica que estem parlant d'una instància concreta i que hem d'utilitzar instance of (P31).
Així, què implica això quan estem escrivint consultes d'SPARQL? Quan volem buscar «totes les obres d'art», no és suficient buscar tots els ítems que són directament instàncies d'«obra d'art».
SELECT ?obra ?obraLabel
WHERE
{
?obra wdt:P31 wd:Q838948. # instància d'obra d'art
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Mentre escric això (octubre de 216), la consulta retorna solament 2.815 resultats, però, evidentment, existeixen més obres d'art! El problema és que no obtenim elements com ara Allò que el vent s'endugué, que és solament una instància de «pel·lícula», no d'«obra d'art». «Pel·lícula» és una subclasse d'«obra d'art», però li hem de dir a l'SPARQL que ho ha de tenir en compte en fer la cerca.
Una solució possible és la sintaxi amb []
que hem comentat abans: Allò que el vent s'endugué és una instància d'alguna subclasse d'«obra d'art». (Com a exercici, proveu a escriure aquesta consulta!) Però això encara genera problemes:
- No hi incloem ítems que son instàncies directes d'obra d'art.
- Encara estem obviant ítems que són instàncies d'alguna subclasse d'alguna altra subclasse d'«obra d'art – per exemple, Blancaneus i els set nanets és una pel·lícula animada, el qual és una pel·lícula, que és una obra d'art. En aquest cas, hem de seguir dues declaracions de «subclasse de» – però també podrien ser tres, o quatre, o cinc, qualsevol nombre, de fet.
La solució: ?item wdt:P31/wdt:P279* ?class
. Això significa que hi ha una «instància de» i després qualsevol nombre de declaracions «subclasse de» entre l'ítem i la classe.
SELECT ?obra ?obraLabel
WHERE
{
?obra wdt:P31/wdt:P279* wd:Q838948. # instància de qualsevol subclasse d'obra d'art
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
(no recomanem executar aquesta consulta. WDQS ho pot suportar (amb prou feines), però el vostre navegador pot blocar-se en intentar mostrar els resultats, per la gran quantitat que n'obtindrà).
Ara sabeu com cercar totes les obres d'art, o edificis, o establiments humans: la màgia de wdt:P31/wdt:P279*
, acompanyat de la classe apropiada. Això utilitza més característiques d'SPARQL de les que hem explicat fins ara però, sincerament, és gairebé l'únic rellevant d'aquestes característiques, així que «no necessitem» entendre com funciona a fi d'utilitzar WDQS de forma efectiva. Si ho voleu aprendre ho explicarem en breu, però també podeu saltar-vos la propera secció i memoritzar, o copiar, wdt:P31/wdt:P279*
per a quan ho necessiteu.
Rutes de propietats
En general, el camí que connecta el node font (subjecte) amb el node destinació (objecte) a través del gràfic no és sempre directe: és possible que calgui concatenar zero, un o molts enllaços (segments, és a dir, "elements del camí") en una cadena; i hi pot haver diversos d'aquests camins (rutes). L'objecte d'un element de camí de la cadena esdevé el subjecte de l'element següent. A SPARQL
, les rutes de propietats són una forma concisa d'escriure la ruta de propietats entre dos ítems. La ruta més simple és una sola propietat, la qual forma una terna normal:
?item wdt:P31 ?class.
Podeu afegir elements de ruta amb una barra inclinada (/
).
?item wdt:P31/wdt:P279/wdt:P279 ?class.
Això és equivalent a qualsevol dels següents:
?item wdt:P31 ?temp1.
?temp1 wdt:P279 ?temp2.
?temp2 wdt:P279 ?class.
?item wdt:P31 [ wdt:P279 [ wdt:P279 ?class ] ].
Exercici: reescriure la consulta dels «nets de Bach» amb aquesta sintaxi.
Un asterisc (*
) després d'un element de ruta significa «zero o més d'aquests elements».
?item wdt:P31/wdt:P279* ?class.
# <span lang="en" dir="ltr" class="mw-content-ltr">means:</span>
?item wdt:P31 ?class
# <span lang="en" dir="ltr" class="mw-content-ltr">or</span>
?item wdt:P31/wdt:P279 ?class
# <span lang="en" dir="ltr" class="mw-content-ltr">or</span>
?item wdt:P31/wdt:P279/wdt:P279 ?class
# <span lang="en" dir="ltr" class="mw-content-ltr">or</span>
?item wdt:P31/wdt:P279/wdt:P279/wdt:P279 ?class
# <span lang="en" dir="ltr" class="mw-content-ltr">or ...</span>
Si no hi ha elements a la ruta, ?a algunacosa* ?b
significa que ?b
pot només ser ?a
de forma directa, sense elements de ruta entre ells.
Un signe (+
) és similar a un asterisc, però significa «un o més d'aquests elements». La consulta següent troba tots els descendents de Bach:
SELECT ?descendent ?descendentLabel
WHERE
{
wd:Q1339 wdt:P40+ ?descendent.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Si aquí utilitzem un asterisc, en comptes d'un signe de suma, la consulta inclourà el mateix Bach.
Un signe d'interrogació (?
) és similar a un asterisc o un signe de suma, però significa «zero o cap d'aquests elements».
Podeu separar elements de ruta amb una barra vertical (|
) en comptes de amb una barra inclinada; això significa «un o altre»: el camí pot tenir qualsevol de les propietats llistades. (Però no combinades - un segment de ruta «un o altre» sempre coincideix amb rutes de longitud 1).
També podem agrupar elements de ruta amb parèntesi (()
), i combinar tots aquests elements sintàctics (/|*+?
). Això significa que una altra manera de trobar tots els descendents de Bach, és:
SELECT ?descendent ?descendentLabel
WHERE
{
?descendent (wdt:P22|wdt:P25)+ wd:Q1339.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
En comptes d'utilitzar la propietat «fill» per anar des de Bach als seus descendents, utilitzem les propietats «pare» i «mare» per anar dels descendents a Bach. La ruta hauria d'incloure dues mares i un pare, o quatre pares, o pare-mare-mare-pare, o qualsevol altra combinació. (Tot i que, per suposat, Bach no pot ser la mare de ningú, així que l'últim element ha de ser sempre pare).
Qualificadors
(Primer les bones notícies: aquesta secció no introdueix més sintaxi d'SPARQL - visca! Respireu a fons i relaxeu-vos, això hauria de ser senzill, oi?)
Fins ara només hem parlat de declaracions senzilles: subjecte, propietat, complement directe. Però les declaracions de Wikidata son més que això: també poden tenir qualificadors i referències. Per exemple, la Mona Lisa (Q12418) té tres declaracions made from material (P186).
- oil paint (Q296955), el material principal;
- poplar wood (Q291034), amb el qualificador applies to part (P518)painting support (Q861259) – aquest és el material on es va pintar la Mona Lisa; i
- wood (Q287), amb els qualificadors applies to part (P518)stretcher (Q1737943) i start time (P580) 1951 – aquesta és una part que es va afegir després a la pintura..
Suposem que volem trobar totes les pintures que tenen una superfície concreta, o sigui, les declaracions made from material (P186) amb un qualificador applies to part (P518)painting support (Q861259). Com ho podem fer? Aquesta és més informació de la que es pot representar amb una terna senzilla.
La resposta és: més ternes! (Regla general: la solució de Wikidata per a gairebé tot és «més ternes». Referències, precisió numèrica, valors amb unitats, coordenades, etc, ple de coses que aquí no tractem, també funcionen així). Fins ara, hem utilitzat el prefix wdt:
per les nostres declaracions de ternes, que apunten directament al complement directe de la declaració. Però també hi ha un altre prefix: p:
, que no apunta al complement directe si no a un «node de declaració». Aquest node després és el subjecte d'altres ternes: el prefix ps:
(per a property statement -declaració en anglès-) apunta a la declaració del complement directe, el prefix pq:
(propietat qualificador) als qualificadors i prov:wasDerivedFrom
apunta als nodes de referència (els quals per ara ignorarem).
Això ha estat un munt de text abstracte. Aquí tenim un exemple concret amb la Mona Lisa:
wd:Q12418 p:P186 ?statement1. # Mona Lisa: material used: ?statement1
?statement1 ps:P186 wd:Q296955. # value: oil paint
wd:Q12418 p:P186 ?statement2. # Mona Lisa: material used: ?statement2
?statement2 ps:P186 wd:Q291034. # value: poplar wood
?statement2 pq:P518 wd:Q861259. # qualifier: applies to part: painting surface
wd:Q12418 p:P186 ?statement3. # Mona Lisa: material used: ?statement3
?statement3 ps:P186 wd:Q287. # value: wood
?statement3 pq:P518 wd:Q1737943. # qualifier: applies to part: stretcher bar
?statement3 pq:P580 1951. # qualifier: start time: 1951 (pseudo-syntax)
Podem abreujar això un piló amb la sintaxi []
, reemplaçant la ?statement
de variables:
wd:Q12418 p:P186 [ ps:P186 wd:Q296955 ].
wd:Q12418 p:P186 [
ps:P186 wd:Q291034;
pq:P518 wd:Q861259
].
wd:Q12418 p:P186 [
ps:P186 wd:Q287;
pq:P518 wd:Q1737943;
pq:P580 1951
].
Podem utilitzar aquest coneixement per escriure una consulta de totes les pintures amb la seva superfície de pintura?
Aquesta és la solució proposada:
SELECT ?quadre ?quadreLabel ?material ?materialLabel
WHERE
{
?quadre wdt:P31/wdt:P279* wd:Q3305213;
p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Primer, limitem ?quadre
a totes les instàncies amb painting (Q3305213) o una subclasse d'això. Després, extraiem el material del node de declaració p:P186
, limitant les declaracions a aquelles que tenen un qualificador applies to part (P518)painting support (Q861259).
ORDER
i LIMIT
Tornem al nostre programa habitual per a més característiques d'SPARQL.
Fins ara, només hem vist consultes en les que volíem totes les coincidències. Però és prou habitual necessitar només uns quants resultats: aquells que son més extrems, d'alguna manera - més vells, més joves, primers, últims, amb més població, amb menor punt de fusió, amb més fills, més materials usats, etc. El factor comú és que els resultats es classifiquen d'alguna forma, i després ens interessen els primers resultats (els que son més importants).
Això es controla amb dues clàusules, les quals s'afegeixen al bloc WHERE {}
(després de les claus, no dins!): ORDER BY
i LIMIT
.
ORDER BY alguna cosa
ordena els resultats per alguna cosa
. alguna cosa
pot ser qualsevol expressió -per ara i tant, l'única forma d'expressió que coneixem son variables senzilles (?alguna cosa
), però aviat en veurem algunes d'altres tipus. Aquesta expressió també es pot embolcallar dins ASC()
o DESC()
per a especificar l'ordre d'ordenació (ascendent o descendent). (Si no voleu especificar cap de les dues coses, l'opció predeterminada és ordenació ascendent, així que ASC(something)
equival a només alguna cosa
.)
LIMIT nombre
retalla la llista de resultats a nombre
resultats, on nombre
és qualsevol nombre natural. Per exemple, LIMIT 10
limita la consulta a 10 resultats. LIMIT 1
només retorna un resultat.
(També es pot utilitzar LIMIT
sense ORDER BY
. En aquest cas, els resultats sortiran sense ordenar, així que no tindrem cap garantia de quin resultat obtindrem. El qual està bé si sabem que hi ha un nombre limitat de resultats, o si només estem interessats en alguns resultats, però no us preocupa quins. En qualsevol cas, afegint LIMIT
pot accelerar de forma significativa la consulta, perquè WDQS aturarà la cerca bon punt tingui la quantitat de resultats que se li han demanat.)
Temps d'exercici! Proveu d'escriure una consulta que retorni el deu països més poblats. (Un país és un sovereign state (Q3624078), i la propietat per a població és P:P1082.) Podeu començar buscant països amb la seva població, i després afegir-hi les clàusules ORDER BY
i LÍMIT
.
Aquesta és la solució proposada:
SELECT ?country ?countryLabel ?population
WHERE
{
?country wdt:P31/wdt:P279* wd:Q3624078;
wdt:P1082 ?population.
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
ORDER BY DESC(?population)
LIMIT 10
Preneu nota de que si volem els països més poblats, ho hem d'ordenar de forma descendent, per fer que els primers resultats siguin els de valors més alts.
Exercici
Hem cobert molta matèria fins ara – és hora de fer alguns exercicis (podeu saltar-vos aquesta secció si teniu pressa.)
Llibres d'Arthur Conan Doyle
Escriviu una consulta que retorni tots els llibres de Sir Arthur Conan Doyle.
Suggeriment |
---|
Els ítems i propietats rellevants, son: Arthur Conan Doyle (Q35610), author (P50). |
Solució d'exemple |
---|
SELECT ?book ?bookLabel
WHERE
{
?book wdt:P50 wd:Q35610.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
|
Elements químics
Escriviu una consulta que retorni tots els elements químics amb el seu símbol i nombre atòmic, ordenats pel nombre atòmic.
Suggeriment |
---|
Els ítems i les propietats rellevants, son: chemical element (Q11344), element symbol (P246), atomic number (P1086). |
Solució d'exemple |
---|
SELECT ?element ?elementLabel ?symbol ?number
WHERE
{
?element wdt:P31 wd:Q11344;
wdt:P246 ?symbol;
wdt:P1086 ?number.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
ORDER BY ?number
|
Rius que desemboquen al Mississippi
Escriviu una consulta que retorni tots els dius que desemboquen directament al riu Mississippi. (El repte més complicat és trobar la propietat correcta).
Suggeriment |
---|
Els ítems i propietats rellevants, son: Mississippi River (Q1497), mouth of the watercourse (P403). |
Solució d'exemple |
---|
SELECT ?river ?riverLabel
WHERE
{
?river wdt:P403 wd:Q1497.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
|
Rius que desemboquen al Mississippi II
Escriviu una consulta que retorni tots els rius que desemboquen, de forma directa o indirecta, al riu Mississippi.
Suggeriment |
---|
Aquesta consulta és gairebé idèntica a l'anterior. La diferència és que aquest cop necessiteu una ruta en comptes d'una terna. (si us heu saltat la secció sobre rutes, salteu-vos també aquest exercici). |
Solució d'exemple |
---|
SELECT ?river ?riverLabel
WHERE
{
?river wdt:P403+ wd:Q1497.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
|
OPTIONAL
En els exercicis anterior, hi teníem una consulta de tots els llibres de Sir Arthur Conan Doyle:
SELECT ?book ?bookLabel
WHERE
{
?book wdt:P50 wd:Q35610.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Però això és avorrit. Hi ha moltes més dades potencials sobre llibres, i només mostrarem l'etiqueta? Provem a muntar una consulta que també inclogui title (P1476), illustrator (P110), publisher (P123) i publication date (P577).
Un primer intent podria ser així:
SELECT ?llibre ?titol ?illustradorLabel ?editorLabel ?publicat
WHERE
{
?llibre wdt:P50 wd:Q35610;
wdt:P1476 ?titol;
wdt:P110 ?illustrador;
wdt:P123 ?editor;
wdt:P577 ?publicat.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Executeu aquesta consulta. Mentre escric això, només retorna dos resultats – una mica escàs! Per què passa això? Abans hem trobat més de dos-cents llibres!
La raó és que per a coincidir amb la consulta, un resultat potencial (un llibre) ha de coincidir a totes les ternes demanades: ha de tenir un títol, un il·lustrador, un editor i una data de publicació. Només que falti una d'aquestes propietats, ja no coincidirà. I això no és el que volem, en aquest cas: primer volem una llista de tots els llibres i, si hi ha dades addicionals disponibles, les volem incloure, però en cap cas volem limitar la llista de resultats.
La solució és dir-li al WDQS que aquelles ternes son opcionals:
SELECT ?llibre ?titol ?illustradorLabel ?editorLabel ?publicat
WHERE
{
?llibre wdt:P50 wd:Q35610.
OPTIONAL { ?llibre wdt:P1476 ?titol. }
OPTIONAL { ?llibre wdt:P110 ?ilustrador. }
OPTIONAL { ?llibre wdt:P123 ?editor. }
OPTIONAL { ?llibre wdt:P577 ?publicat. }
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Això ens dona les variables addicionals (?titol
, ?editor
, etc.). Si l'element existeix, però no té les declaracions opcionals, no es descarta el resultat, si no que, senzillament, la variable queda buida.
Nota: és molt important utilitzar clàusules OPTIONAL
separades aquí. Si posem totes les ternes en una sola clàusula, com aquí –
SELECT <span lang="en" dir="ltr" class="mw-content-ltr">?book ?title ?illustratorLabel ?publisherLabel ?published</span>
WHERE
{
?book wdt:P50 wd:Q35610.
OPTIONAL {
?book wdt:P1476 ?title;
wdt:P110 ?illustrator;
wdt:P123 ?publisher;
wdt:P577 ?published.
}
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
– us adonareu que la majoria de resultats no inclouen cap informació extra. Això és per què una clàusula opcional amb diverses ternes només coincidirà si totes les ternes coincideixen. O sigui: si un llibre té títol, té il·lustrador, té editor i data de publicació, aleshores la clàusula opcional coincideix i aquests valors es passaran a la variable corresponent. Però, per exemple, si un llibre té títol però no té il·lustrador, falla tota la clàusula i el resultat no mostrarà cap de les quatre variables demanades.
Expressions, FILTER
i BIND
Aquesta secció pot semblar més desorganitzada que les altres, per què cobreix un tema prou ampli i divers. El concepte bàsic és que volem fer alguna cosa amb els valors que, fins ara, només hem seleccionat i retornat sense més. I la forma de fer aquesta mena d'operacions sobre valors és amb «expressions». Hi ha molts tipus d'expressions i es poden fer un munt de coses amb elles però, abans de res, comencem amb el més bàsic: tipus de dades.
Tipus de dades
Cada valor és d'un tipus a SPARQL, el qual indica quina mena de valor és i què podem fer amb ell. Els tipus més importants, son:
- ítem, com
wd:Q42
per a Douglas Adams (Q42). - booleà, que té dos valors possibles
cert
ifals
. Els valors booleans no es desen en declaracions, però moltes expressions retornen valors booleans, per ex.2 < 3
(cert
) o"a" = "b"
(fals
). - cadena de text, un tros de text. Les cadenes de text que son literals s'escriuen entre dobles cometes.
- text monolingüe, una cadena de text associada a una etiqueta d'idioma. En un literal, hi podem afegir l'etiqueta de l'idioma després de la cadena amb un símbol
@
, per ex."Douglas Adams"@ca
. - nombres, tant enters (
1
) com decimals (1.23
). - dates. Els literals de dates es poden escriure afegint
^^xsd:dateTime
(distingeix majúscules i minúscules –^^xsd:datetime
no funcionarà!) a una ISO 8601 cadena de data:"2012-10-29"^^xsd:dateTime
.
Operadors
També tenim disponibles els operadors matemàtics habituals: +
, -
, *
, /
per sumar, restar, multiplicar o dividir nombres, <
, >
, =
, <=
, >=
per comparar-los. La prova de no-igualtat ≠ s'escriu !=
. Altres tipus de dades també tenen definida la igualtat; per exemple, "abc" < "abd"
és cert (comparació lèxica), com també ho fan "2016-01-01"^^xsd:dateTime > "2015-12-31"^^xsd:dateTime
i wd:Q4653 != wd:Q283111
. Les comparacions booleanes es poden combinar amb &&
(and lògic: a && b
torna cert si ambdós both a
and b
son certs) i ||
(or lògic: a || b
torna cert si qualsevol dels dos (o tots dos) de a
i b
son certs).
FILTER
Info Per a una versió alternativa i, a vegades, més ràpida que FILTER
, podem utilitzar MINUS
, vegeu l'exemple.
FILTER(condition).
és una clàusula que podem inserir a la nostra consulta d'SPARQL per a filtrar-ne els resultats. Dins del parèntesi hi podem posar qualsevol expressió de tipus booleà, i només mostrarà els valors que retornin cert
a l'expressió.
Per exemple, per a tenir una llista de tots els humans nascuts l'any 2015, primer llistem tots els humans per la data de naixement –
SELECT ?person ?personLabel ?dob
WHERE
{
?person wdt:P31 wd:Q5;
wdt:P569 ?dob.
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
– i després ho filtrem a fi de que només ens torni els resultats dels quals la data de naixement sigui 2015. Hi ha dues formes de fer-ho: extraient l'any de la data amb la funció YEAR
, i verificant que sigui 2015 –
FILTER(YEAR(?dob) = 2015).
– o revisant que la data estigui entre el primer de gener de 2015 (inclòs) i el primer de gener de 2016 (exclòs):
FILTER("2015-01-01"^^xsd:dateTime <= ?dob && ?dob < "2016-01-01"^^xsd:dateTime).
Probablement la primera opció sigui més clara, però resulta que la segona és molt més ràpida, o sigui que utilitzem-la:
SELECT ?person ?personLabel ?dob
WHERE
{
?person wdt:P31 wd:Q5;
wdt:P569 ?dob.
FILTER("2015-01-01"^^xsd:dateTime <= ?dob && ?dob < "2016-01-01"^^xsd:dateTime).
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Una altra possible utilitat de FILTER
té relació amb les etiquetes. El servei d'etiquetes és molt útil si només volem mostrar l'etiqueta d'una variable. Però si volem operar amb l'etiqueta – per exemple: comprovar si comença amb “Sr. ” – veurem que no funciona:
SELECT ?human ?humanLabel
WHERE
{
?human wdt:P31 wd:Q15632617.
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
#This FILTER does not work!
FILTER(STRSTARTS(?humanLabel, "Mr. ")).
}
Aquesta consulta troba totes les instàncies de fictional human (Q15632617) i comprova si la seva etiqueta comença amb "Sr. "
(STRSTARTS
és la contracció (en anglès) de «la cadena comença amb»; també tenim STRENDS
i CONTAINS
). La raó per la qual això no funciona és per què el servei d'etiquetes afegeix les seves variables cap al final de l'avaluació de la consulta; en el punt en el qual intentem filtrar amb ?personLabel
, el servei d'etiquetes encara no ha creat aquesta variable.
Afortunadament, el servei d'etiquetes no és l'única forma d'obtenir l'etiqueta d'un ítem. Les etiquetes també es desen com a ternes normals, utilitzant el predicat rdfs:label
. Per suposat, això és així amb totes les etiquetes, no només amb les que són en anglès; si només les volem en un idioma, haurem de filtrar per l'idioma de l'etiqueta:
FILTER(LANG(?label) = "en").
La funció LANG
retorna l'idioma d'una cadena de text monolingüe i aquí només seleccionem les etiquetes que sonen català. La consulta completa, és:
SELECT ?human ?label
WHERE
{
?human wdt:P31 wd:Q15632617;
rdfs:label ?label.
FILTER(LANG(?label) = "[AUTO_LANGUAGE]").
FILTER(STRSTARTS(?label, "Sr. ")).
}
Obtenim l'etiqueta amb la terna ?human rdfs:label ?label
, ho restringim a les etiquetes en català i després verifiquem que comenci per «Sr. ».
També es pot utilitzar FILTER amb una expressió regular. A l'exemple següent
SELECT ?item ?itemLabel ?bblid
WHERE {
?item wdt:P2580 ?bblid .
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en" }
FILTER(!REGEX(STR(?bblid), "[\\.q]"))
}
Si la restricció de format d'una ID és [A-Za-z][-.0-9A-Za-z]{1,}
:
SELECT ?item ?itemLabel ?bblid
WHERE {
?item wdt:P2580 ?bblid .
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en" }
FILTER(!REGEX(STR(?bblid), "^[A-Za-z][-.0-9A-Za-z]{1,}$"))
}
És possible filtrar i eliminar elements específics com aquest
FILTER ( ?item not in ( wd:Q4115189,wd:Q13406268,wd:Q15397819 ) )
És possible filtrar i obtenir elements que siguin buits:
FILTER ( NOT EXISTS { ?item wdt:P21 [] } )
BIND
, BOUND
, IF
Aquestes tres característiques se solen utilitzar conjuntament, així que primer les explicarem les tres i després veurem alguns exemples.
Una clàusula BIND(expressió AS ?variable).
es pot utilitzar per a assignar el resultat d'una expressió a una variable (habitualment una variable nova, però també es pot sobreescriure el valor d'alguna preexistent).
BOUND(?variable)
comprova si una variable s'ha vinculat a un valor (retorna cert
o fals
). Sol ser útil per a variables que es posin en una clàusula OPTIONAL
.
IF(condició,aleshoresExpressió,altramentExpressió)
avalua a aleshoresExpressió
si condició
avalua a cert
, i a altramentExpressió
si condició
avalua a fals
.
O sigui, IF(cert, "sí", "no")
avalua a "sí"
, i IF(fals, "genial", "terrible")
avalua a "terrible"
.
BIND
es pot utilitzar per a vincular el resultat d'algun càlcul a una variable nova. Pot ser un resultat intermedi d'un càlcul més gran o un resultat directe d'una consulta. Per exemple, per obtenir l'edat de les víctimes de la pena de mort:
SELECT ?person ?personLabel ?age
WHERE
{
?person wdt:P31 wd:Q5;
wdt:P569 ?born;
wdt:P570 ?died;
wdt:P1196 wd:Q8454.
BIND(?died - ?born AS ?ageInDays).
BIND(?ageInDays/365.2425 AS ?ageInYears).
BIND(FLOOR(?ageInYears) AS ?age).
# o, com una expressió:
#BIND(FLOOR((?died - ?born)/365.2425) AS ?age).
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
BIND
també es pot utilitzar per a vincular valors constants a variables, a fi d'incrementar-ne la llegibilitat. Per exemple, una consulta que trobi tots els sacerdots que siguin dona:
SELECT ?woman ?womanLabel
WHERE
{
?woman wdt:P31 wd:Q5;
wdt:P21 wd:Q6581072;
wdt:P106 wd:Q42603.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
es pot reescriure com:
SELECT ?woman ?womanLabel
WHERE
{
BIND(wdt:P31 AS ?instanceOf).
BIND(wd:Q5 AS ?human).
BIND(wdt:P21 AS ?sexOrGender).
BIND(wd:Q6581072 AS ?female).
BIND(wdt:P106 AS ?occupation).
BIND(wd:Q42603 AS ?priest).
?woman ?instanceOf ?human;
?sexOrGender ?female;
?occupation ?priest.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
La part significativa de la consulta, des de ?dona
fins ?sacerdot.
, ara és probablement més llegible. Tot i això, el bloc gran BIND
que té just abans és prou confús, així que aquesta tècnica s'ha d'utilitzar amb precaució. (A la interfície d'usuari de WDQS podem posar el punter damunt de qualsevol terme com wd:Q123
o wdt:P123
i veurem l'etiqueta i la descripció de l'entitat. Per tant, ?dona
només és més llegible que wd:Q6581072
si obviem aquesta característica.)
Les expressions IF
s'utilitzen sovint amb condició-expressió fetes amb BOUND
. Per exemple, suposem que volem una consulta que mostri alguns humans i que, en comptes de només mostrar-ne l'etiqueta, volem veure el seu pseudonym (P742) si en tenen, i només volem veure l'etiqueta si no tenen pseudònim. Per a això, seleccionem el pseudònim en una clàusula OPTIONAL
(ha de ser opcional, no volem descartar resultats que no tinguin pseudònim), i després utilitzem BIND(IF(BOUND(…
per seleccionar bé el pseudònim, bé l'etiqueta.
SELECT ?writer ?label
WHERE
{
# French writer born in the second half of the 18th century
?writer wdt:P31 wd:Q5;
wdt:P27 wd:Q142;
wdt:P106 wd:Q36180;
wdt:P569 ?dob.
FILTER("1751-01-01"^^xsd:dateTime <= ?dob && ?dob < "1801-01-01"^^xsd:dateTime).
# get the English label
?writer rdfs:label ?writerLabel.
FILTER(LANG(?writerLabel) = "en").
# get the pseudonym, if it exists
OPTIONAL { ?writer wdt:P742 ?pseudonym. }
# bind the pseudonym, or if it doesn’t exist the English label, as ?label
BIND(IF(BOUND(?pseudonym),?pseudonym,?writerLabel) AS ?label).
}
Altres propietats que es poden utilitzar d'una forma semblant, son nickname (P1449), posthumous name (P1786) i taxon common name (P1843) – qualsevol cosa on hi tingui sentit una alternativa.
També podem combinar BOUND
amb FILTER
per a assegurar que com a mínim un dels molts blocs OPTIONAL
rep informació. Per exemple, busquem tots els astronautes que han anat a la lluna, així com els membres de Apollo 13 (Q182252) (prou acurat, oi?). Aquesta restricció es pot expressar com una ruta d'una propietat única, així que necessitem una clàusula OPTIONAL
per a membre d'alguna missió a la lluna i una altra per a membre de l'Apollo 13. Però només volem seleccionar els resultats que compleixin, com a mínim, una de les dues condicions.
SELECT ?astronaut ?astronautLabel
WHERE
{
?astronaut wdt:P31 wd:Q5;
wdt:P106 wd:Q11631.
OPTIONAL {
?astronaut wdt:P450 ?mission.
?mission wdt:P31 wd:Q495307.
}
OPTIONAL {
?astronaut wdt:P450 wd:Q182252.
BIND(wd:Q182252 AS ?mission).
}
FILTER(BOUND(?mission)).
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
COALESCE
La funció COALESCE
es pot utilitzar com a abreviació del patró BIND(IF(BOUND(?x), ?x, ?y) AS ?z).
com a alternativa en cas de fallada pels casos esmentats abans: calen un grapat d'expressions i retorna el primer resultat que avalua sense error.
Per exemple, l'alternativa en cas de fallada de més amunt per pseudònim
BIND(IF(BOUND(?pseudonym),?pseudonym,?writerLabel) AS ?label).
es pot escriure de forma més concisa com
BIND(COALESCE(?pseudonym, ?writerLabel) AS ?label).
també és senzill afegir una etiqueta alternativa en cas de fallada per si tampoc s'ha definit ?writerLabel
:
BIND(COALESCE(?pseudonym, ?writerLabel, "<no label>") AS ?label).
Agrupant
Fins aquí, totes les consultes que hem vist trobaven resultats que complien algunes condicions. En alguns casos, també hi hem inclòs declaracions addicionals de l'ítem (pintures amb materials, llibres de l'Arthur Conan Doyle amb títol i il·lustrador).
Però, sovint, no volem una llista de resultats gaire llarga. En canvi, podem fer preguntes com aquesta:
- Quantes pintures s'han fet sobre tela, fusta de pollancre, etc?
- Quina ciutat de cada país té més població?
- Quin és el total d'armes produïdes per fabricant?
- Qui publica, de mitjana, els llibres més llargs?
Població de ciutats
Fem un cop d'ull a la segona pregunta. És prou senzill escriure una consulta que llisti totes les ciutats amb la seva població i nació, ordenades per nació:
SELECT ?country ?city ?population
WHERE
{
?city wdt:P31/wdt:P279* wd:Q515;
wdt:P17 ?country;
wdt:P1082 ?population.
}
ORDER BY ?country
(Nota: aquesta consulta retorna un piló de resultats, el que us pot portar problemes al navegador. Podeu afegir-hi una clàusula LIMIT
)
Com que ho estem ordenant per nació, totes les ciutats de la mateixa formaran un bloc contigu de resultats. Per trobar la població més alta en aquest bloc, volem considerar-lo com un grup, i agregar tots els valors de població en un de sol: el màxim. Això es fa amb una clàusula GROUP BY
sota del bloc WHERE
i amb una funció d'agregació (MAX
) a la clàusula SELECT
.
SELECT ?country (MAX(?population) AS ?maxPopulation)
WHERE
{
?city wdt:P31/wdt:P279* wd:Q515;
wdt:P17 ?country;
wdt:P1082 ?population.
}
GROUP BY ?country
Hem reemplaçat ORDER BY
amb un GROUP BY
. L'efecte d'això és que els resultats amb el mateix ?country
ara s'agrupen en un sol resultat. Això significa que també hem de canviar la clàusula SELECT
. Si mantenim la clàusula anterior SELECT ?country ?city ?population
, quins ?city
i ?population
retornarà?
Recordeu, hi ha molts resultats dins d'aquest; tots tenen el mateix ?country
, així que el podem seleccionar, però com que cadascun té un ?city
i ?population
diferents, li podem dir al WDQS quins d'aquests valors volem. Aquesta és la feina de la funció d'agregació. En aquest cas, utilitzem MAX
: de tots els valors de ?population
, seleccionem els màxims de cada grup agregat. (També li hem d'assignar un nom a aquest valor amb la construcció AS
, però aquest és un detall menor).
Aquesta és la pauta general per a escriure consultes agregades: escriure una consulta normal que retorna les dades que volem (no agrupades, amb molts resultats per cada grup), després afegim la clàusula GROUP BY
i hi afegim una funció d'agregació a les variables no agrupades de la clàusula SELECT
.
Materials de pintura
Provem-ho amb una altra pregunta: quantes pintures s'han fet amb cada material? Primer, escriurem una consulta que només retorni totes les pintures amb el seu material (Tingueu cura d'utilitzar només les declaracions made from material (P186) amb el qualificador applies to part (P518)painting support (Q861259)).
SELECT ?material ?painting
WHERE
{
?painting wdt:P31/wdt:P279* wd:Q3305213;
p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
}
Seguidament, afegim una clàusula GROUP BY
al ?material
i, després, una funció d'agregació a l'altra variable seleccionada (?painting
). En aquest cas, estem interessats en el nombre de pintures: la funció d'agregació per a això és COUNT
.
SELECT ?material (COUNT(?painting) AS ?count)
WHERE
{
?painting wdt:P31/wdt:P279* wd:Q3305213;
p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
}
GROUP BY ?material
Un problema amb això és que no tenim l'etiqueta dels materials, així que els resultats no seran senzills d'interpretar. Si afegim la variable d'etiqueta, ens mostrarà aquest error:
SELECT ?material ?materialLabel (COUNT(?painting) AS ?count)
WHERE
{
?painting wdt:P31/wdt:P279* wd:Q3305213;
p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?material
Bad aggregate
“Bad aggregate” és un missatge d'error que veurem sovint si treballem amb consultes agrupades; significa que li falta la funció d'agregació a alguna de les variables seleccionades, o que en té alguna variable que no n'hauria de tenir. En aquest cas, el WDQS interpreta que hi pot haver diverses ?etiquetaMaterial
per a cada ?material
(tot i que sabem que això no pot passar) i per això es queixa de que no estem especificant cap funció d'agregació per a aquella variable.
Una solució és agrupar per diverses variables. Si llistem diverses variables a la clàusula GROUP BY
, hi haurà un resultat per a cada combinació d'aquestes variables i podrem seleccionar-les sense cap funció d'agregació. En aquest cas, agruparem tant per ?material
com per ?etiquetaMaterial
.
SELECT ?material ?materialLabel (COUNT(?painting) AS ?count)
WHERE
{
?painting wdt:P31/wdt:P279* wd:Q3305213;
p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?material ?materialLabel
Gairebé ja estem d'aquesta consulta, només ens falta una altra millora: ens agradaria veure primer els materials més utilitzats. Per sort, podem utilitzar les variables d'agregació de la clàusula SELECT
(aquí seria ?count
) dins d'una clàusula ORDER BY
, que és senzill de fer:
SELECT ?material ?materialLabel (COUNT(?painting) AS ?count)
WHERE
{
?painting wdt:P31/wdt:P279* wd:Q3305213;
p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?material ?materialLabel
ORDER BY DESC(?count)
Fem les altres consultes com a exercici.
Armes per fabricant
Quin és el total d'armes produïdes per cada fabricant?
Suggeriment |
---|
Els ítems i propietats rellevants, son: firearm (Q12796), manufacturer (P176), total produced (P1092). |
Solució d'exemple |
---|
SELECT ?manufacturer ?manufacturerLabel (SUM(?produced) AS ?totalProduced)
WHERE
{
?model wdt:P31?/wdt:P279* wd:Q12796;
wdt:P176 ?manufacturer;
wdt:P1092 ?produced.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?manufacturer ?manufacturerLabel
ORDER BY DESC(?produced)
|
Editorials per nombre de pàgines
Quina és el nombre mitjà (function: AVG
) de pàgines dels llibres per a cada editorial?
Suggeriment |
---|
Els ítems i propietats rellevants, son: publisher (P123), number of pages (P1104). |
Solució d'exemple |
---|
SELECT ?publisher ?publisherLabel (AVG(?pages) AS ?avgPages)
WHERE
{
?book wdt:P123 ?publisher;
wdt:P1104 ?pages.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?publisher ?publisherLabel
ORDER BY DESC(?avgPages)
|
HAVING
Un petit afegitó a aquesta consulta: si mirem els resultats, veurem que el resultat superior té una mitjana extremadament alta, de l'ordre de deu cops la del segon lloc. Una mica d'investigació revela que això és perquè aquell editor (UTET (Q4002388)) només ha publicat un llibre amb la declaració number of pages (P1104), Grande dizionario della lingua italiana (Q3775610), fet que esbiaixa una mica el resultat. Per a treure valors atípics com aquest, podem intentar seleccionar només editors que hagin editat com a mínim dos llibres amb la declaració number of pages (P1104) a Wikidata.
Com ho fem això? Podem restringir els resultats amb la clàusula FILTER
però, en aquest cas, volem restringir en funció de l'agrupació (el nombre de llibres), no per cap resultat individual. Així, ho farem amb la clàusula HAVING
, la qual es pot posar just després de la clàusula GROUP BY
i agafa una expressió tal i com fa FILTER
:
SELECT ?publisher ?publisherLabel (AVG(?pages) AS ?avgPages)
WHERE
{
?book wdt:P123 ?publisher;
wdt:P1104 ?pages.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?publisher ?publisherLabel
HAVING(COUNT(?book) > 1)
ORDER BY DESC(?avgPages)
Resum de funcions d'agregació
Aquest és un breu resum de les funcions d'agregació disponibles:
COUNT
: el nombre d'elements. També podem escriureCOUNT(*)
si només volem comptar els resultats.SUM
,AVG
: la suma o la mitjana de tots els elements, de forma respectiva. Si els elements no son nombres, obtindrem resultats estranys.MIN
,MAX
: els valors mínim o el màxim de tots els elements, de forma respectiva. Això funciona amb tota mena de tipus de dades. Els nombres s'ordenen de forma numèrica, les cadenes de text i altres tipus, de forma alfabètica.SAMPLE
: qualsevol element. Això pot ser útil si sabem que només hi ha un resultat, o si no ens importa especialment quin se'ns retorna.GROUP_CONCAT
: concatena tots els elements. Pot ser útil si, per exemple, només volem un resultat d'un ítem però hi volem incloure diverses declaracions d'aquest ítem, com les ocupacions d'una persona. Les diferents ocupacions es podran reagrupar o concatenar a fi de mostrar-les totes en una sola variable en comptes de en diverses línies de resultats. Si us interessa, ho podeu mirar a les especificacions d'SPARQL.
De forma addicional, podem afegir un modificador DISTINCT
per a qualsevol d'aquestes funcions per a eliminar-ne els duplicats dels resultats. Per exemple, si hi ha dos resultats però ambdós tenen el mateix valor a ?var
, aleshores COUNT(?var)
tornarà 2
però COUNT(DISTINCT ?var)
només tornarà 1
. Sovint hem d'utilitzar DISTINCT
quan la nostra consulta pot tornar el mateix ítem diversos cops – això pot passar, per exemple, utilitzem ?item wdt:P31/wdt:P279* ?class
, i hi ha diverses rutes des de ?item
fins ?class
: obtindrem un resultat nou per a cadascuna d'aquestes rutes, tot i que els valors dels diferents resultats seran idèntics. (Si no estem agrupant, també podrem eliminar aquests resultats duplicats començant la consulta amb un SELECT DISTINCT
en comptes d'un SELECT
.)
wikibase: Etiquetes i agregacions
Una consulta com la següent, que busca a Wikidata totes les persones acadèmiques amb ciutadania de més de dos països, no mostra el nom d'aquests països a la columna ?citizenships:
select ?person ?personLabel (group_concat(?citizenshipLabel;separator="/") as ?citizenships) {
# find all academics
?person wdt:P106 wd:Q3400985 ;
wdt:P27 ?citizenship .
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
} group by ?person ?personLabel having (count(?citizenship) > 2)
Per mostrar la columna ?citizenships, indiqueu explícitament ?personLabel i ?citizenshipLabel a la crida del servei wikibase:label
, d'aquesta manera:
SERVICE wikibase:label {
bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en".
?citizenship rdfs:label ?citizenshipLabel .
?person rdfs:label ?personLabel .
}
La següent consulta funciona com esperem:
select ?person ?personLabel (group_concat(?citizenshipLabel;separator="/") as ?citizenships) {
?person wdt:P106 wd:Q3400985 ;
wdt:P27 ?citizenship .
SERVICE wikibase:label {
bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en".
?citizenship rdfs:label ?citizenshipLabel .
?person rdfs:label ?personLabel .
}
} group by ?person ?personLabel having (count(?citizenship) > 2)
VALUES
Podem seleccionar els ítems basant-nos en una llista d'ítems:
SELECT ?item ?itemLabel ?mother ?motherLabel WHERE {
VALUES ?item { wd:Q937 wd:Q1339 }
OPTIONAL { ?item wdt:P25 ?mother. }
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
També podem seleccionar basant-nos en una llista de valors d'una propietat específica:
SELECT ?item ?itemLabel ?mother ?motherLabel ?ISNI WHERE {
VALUES ?ISNI { "000000012281955X" "0000000122764157" }
?item wdt:P213 ?ISNI.
OPTIONAL { ?item wdt:P25 ?mother. }
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
VALUES també pot fer més que crear enumeracions de valors possibles per a una parella (o una terna) de variables. Per exemple, diguem que volem utilitzar variables personalitzades (però conegudes) de les persones enumerades al primer « valor » d'exemple. Aleshores, és possible utilitzar una clàusula « values » com VALUES (?item ?etiquetaPersonalitzadaItem) { (wd:Q937 "Einstein") (wd:Q1339 "Bach") }
la qual assegura que quan un ?item
tingui el valor wd:Q937
a un resultat, el valor propi de la ?etiquetaPersonalitzadaItem
és Einstein
i quan ?item
té el valor wd:Q1339
, el valor de ?etiquetaPersonalitzadaItem
és Bach
.
SELECT ?item ?customItemLabel ?mother ?motherLabel WHERE {
VALUES (?item ?customItemLabel) { (wd:Q937 "Einstein") (wd:Q1339 "Bach") }
OPTIONAL { ?item wdt:P25 ?mother. }
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Label in multiple languages
You can also use rdfs:label
to query labels in different languages at the same time.
SELECT ?book ?bookLabel_de ?bookLabel_en WHERE {
?book wdt:P50 wd:Q35610.
OPTIONAL { SERVICE wikibase:label { bd:serviceParam wikibase:language "de". ?book rdfs:label ?bookLabel_de } }
OPTIONAL { SERVICE wikibase:label { bd:serviceParam wikibase:language "en". ?book rdfs:label ?bookLabel_en } }
}
I més enllà…
Aquí s'acaba la guia, però no l'SPARQL: hi ha molt més que no hem vist. Si heu arribat fins aquí, ja sabeu un munt de coses sobre el WDQS i hauríeu de poder escriure algunes consultes prou potents. Però si en voleu aprendre encara més, aquí teniu unes quantes coses que podeu consultar:
- Subconsultes. Afegim una consulta entre claus (
{ SELECT ... WHERE { ... } LIMIT 10 }
), i el resultat es mostrarà a la consulta exterior. (Si teniu familiaritat amb l'SQL, haure de replantejar una mica el concepte – les subconsultes d'SPARQL son purament de baix cap a dalt i no poden utilitzar valors de la consulta exterior, com si fa l'SQL amb les consultes correlacionades). MINUS
ens permet seleccionar resultats que no concordin amb algun patró gràfic.FILTER NOT EXISTS
és pràcticament equivalent (vegeu les especificacions d'SPARQL per a un exemple on poden diferir.), però – com a mínim, al WDQS – sol ser prou més lent.
La referència principal per a aquest i altres temes és l'especificació d'SPARQL.
També podeu consultar el tutorial d'SPARQL a Wikibooks i aquest tutorial de data.world.
I, per suposat, hi ha algunes parts de Wikidata que no hem tocat, com les referències, precisió numèrica (100±2.5), valors amb unitats (dos kilograms), coordenades, sitelinks, declaracions a propietats i d'altres. Podeu veure com es modela tot això en ternes a mw:Wikibase/Indexing/RDF Dump Format.