Wikidata:tutorial d'SPARQL

This page is a translated version of the page Wikidata:SPARQL tutorial and the translation is 99% complete.
Outdated translations are marked like this.

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:

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 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 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 WDQS, 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.
}
Try it!

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]". }
}
Try it!

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]". }
}
Try it!

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]". }
}
Try it!

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]". }
}
Try it!

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]". }
}
Try it!

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]". }
}
Try it!

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]". }
}
Try it!

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]". }
}
Try it!

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]". }
}
Try it!

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]". }
}
Try it!

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:

  1. No hi incloem ítems que son instàncies directes d'obra d'art.
  2. 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]". }
}
Try it!

(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

Les rutes de propietats son 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.
# means:
?item wdt:P31 ?class
# or
?item wdt:P31/wdt:P279 ?class
# or
?item wdt:P31/wdt:P279/wdt:P279 ?class
# or
?item wdt:P31/wdt:P279/wdt:P279/wdt:P279 ?class
# or ...

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]". }
}
Try it!

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]". }
}
Try it!

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).

  1. oil paint (Q296955), el material principal;
  2. 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
  3. 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]". }
}
Try it!

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
Try it!

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.

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.

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).

Rius que desemboquen al Mississippi II

Escriviu una consulta que retorni tots els rius que desemboquen, de forma directa o indirecta, al riu Mississippi.

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]". }
}
Try it!

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]". }
}
Try it!

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]". }
}
Try it!

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 ?book ?title ?illustratorLabel ?publisherLabel ?published
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". }
}
Try it!

– 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 i fals. 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]". } 
}
Try it!

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. ")).
}
Try it!

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. ")).
}
Try it!

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]")) 
}
Try it!

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,}$"))
}
Try it!

É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]". }
}
Try it!

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]". }
}
Try it!

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]". }
}
Try it!

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).
}
Try it!

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]". }
}
Try it!

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
Try it!

(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
Try it!

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 ].
}
Try it!

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
Try it!

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
Try it!

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
Try it!

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)
Try it!

Fem les altres consultes com a exercici.

Armes per fabricant

Quin és el total d'armes produïdes per cada fabricant?

Editorials per nombre de pàgines

Quina és el nombre mitjà (function: AVG) de pàgines dels llibres per a cada editorial?

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)
Try it!

Resum de funcions d'agregació

Aquest és un breu resum de les funcions d'agregació disponibles:

  • COUNT: el nombre d'elements. També podem escriure COUNT(*) 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)
Try it!

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)
Try it!

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". }
}
Try it!

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". }
}
Try it!

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". }
}
Try it!

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.

Vegeu també