Wikidata:SPARQL tutorial/nl

This page is a translated version of the page Wikidata:SPARQL tutorial and the translation is 100% complete.

WDQS, de Wikidata Query Service, is een krachtig hulpmiddel om inzicht te krijgen in de inhoud van Wikidata. Deze handleiding zal u leren hoe u het moet gebruiken. Zie ook de interactive tutorial gemaakt door Wikimedia Israël.

Voordat u uw eigen SPARQL-query schrijft, kijk dan naar {{Item documentation}} of een andere generische SPARQL-query sjabloon en kijk of uw query al is toegevoegd.

Voordat we beginnen

Hoewel deze tutorial misschien heel lang en intimiderend lijkt, laat u daar niet door afschrikken. Alleen het leren van de SPARQL-basis zal u ver op weg helpen - zelfs als u na #Onze eerste query stopt te lezen, zult u al meer begrijpen om veel interessante queries te bouwen. Elk deel van deze tutorial zal u in staat stellen die query nog krachtiger te maken.

Als u nog nooit van Wikidata, SPARQL of WDQS hebt gehoord, is hier een korte uitleg van die termen:

SPARQL basis

Een eenvoudige SPARQL-query kan er zo uitzien:

SELECT ?a ?b ?c
WHERE
{
  x y ?a.
  m n ?b.
  ?b f ?c.
}

De SELECT clause geeft de variabele aan die u wilt opvragen (elke variabele begint met een vraagteken), en de WHERE clause bevat de beperkingen, meestal in de vorm van tripels. Alle informatie in Wikidata (en vergelijkbare kennisdatabases) wordt opgeslagen in de vorm van tripels; Wanneer u de query uitvoert, probeert de queryservice de variabelen in te vullen met werkelijke waarden, zodat de resulterende triples worden weergegeven in de kennisdatabase en één resultaat retourneert voor elke combinatie van variabelen die wordt gevonden.

Een tripel kan worden gezien als twee hoekpunten (alias 2 knooppunten, 2 bronnen) verbonden door een rand (een boog, een eigenschap) binnen de gerichte (georiënteerde) eigenschapsmultigraaf die Wikidata vormt. Het kan worden gelezen als een zin (daarom eindigt het met een punt), met een onderwerp, een predicaat en een object:

SELECT ?fruit
WHERE
{
  ?fruit hasColor yellow.
  ?fruit tastes sour.
}

Het resultaat van deze query kan bijvoorbeeld "lemon" bevatten. In Wikidata zijn de meeste eigenschappen eigenschappen van het soort "has", dus de query kan in plaats daarvan luiden:

SELECT ?fruit
WHERE
{
  ?fruit color yellow.
  ?fruit taste sour.
}

wat leest als “?fruit heeft kleur ‘geel’” (niet?fruit is de kleur van ‘geel’” – denk hieraan voor eigenschapsparen als “parent”/“child”!)

Dat is echter geen goed voorbeeld voor WDQS. Smaak is subjectief, dus Wikidata heeft er geen eigenschap voor. Laten we in plaats daarvan eens nadenken over parent/child-relaties, die meestal ondubbelzinnig zijn.

Onze eerste query

Stel dat we alle kinderen van de barokcomponist Johann Sebastian Bach willen opnemen. Met pseudo-elementen zoals in de vragen hierboven, hoe zou u die query schrijven?

Hopelijk heeft u iets als dit:

SELECT ?child
WHERE
{
  #  child "has parent" Bach
  ?child parent Bach.
  # (NB: alles na een '#' is een commentaar en wordt door WDQS genegeerd. )
}

of dit,

SELECT ?child
WHERE
{
  # child "has father" Bach 
  ?child father Bach. 
}

of dit,

SELECT ?child
WHERE
{
  #  Bach "has child" child
  Bach child ?child.
}

De eerste twee tripels zeggen dat het kind als ouder/vader Bach moet hebben; de derde zegt dat Bach het kind het kind moet hebben. Laten we nu de tweede nemen.

Wat moet er dan nog gedaan worden om dit in een goede WDQS-vraag te veranderen? Op Wikidata worden items en eigenschappen niet geïdentificeerd door door menselijk leesbare namen zoals "vader" (eigendom) of "Bach" (item). (Met goede reden: "Johann Sebastian Bach" is ook de naam van een Duitse schilder, en "Bach" kan ook verwijzen naar de achternaam, de Franse commune, de Mercuriuskrater, enz. ) In plaats daarvan wordt aan Wikidata-artikelen en eigenschappen een identificatiecode toegewezen. de identificator voor een item we zoeken naar het item en kopiëren het Q-nummer van het resultaat dat klinkt alsof het het item is dat we zoeken (gebaseerd op de beschrijving, bijvoorbeeld). Om de identificatiecode voor een eigenschap te vinden, doen we hetzelfde, maar zoeken we naar "P:zoekterm" in plaats van gewoon "zoekterm", wat de zoekopdracht beperkt tot eigenschappen. Dit vertelt ons dat de beroemde componist Johann Sebastian Bach Q1339 is, en de eigenschap om de vader van een item te vinden P:P22 is.

En last but not least, we moeten voorvoegsels toevoegen. Voor eenvoudige WDQS-tripels moeten de items worden voorafgegaan metwd:, en de eigenschappen met wdt:. (Maar dit geldt alleen voor vaste waarden - variabelen krijgen geen prefix!)

Samengevat wordt onze eerste WDQS-query:

SELECT ?child
WHERE
{
# ?child  father   Bach
  ?child wdt:P22 wd:Q1339.
}
Try it!

Klik op de link Probeer het en vervolgens op Uitvoeren op de WDQS-pagina. Wat krijgt u als antwoord?

child
wd:Q57225
wd:Q76428

Dat is teleurstellend. U ziet alleen de identificaties. U kunt op ze klikken om hun Wikidata-pagina te zien (inclusief een voor mensen leesbaar label), maar is er geen betere manier om de resultaten te zien?

Die is er natuurlijk wel. (Zijn retorische vragen niet geweldig?) Als u de magische tekst toevoegt

SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }

in de WHERE-clausule wordt er nog een aantal variabelen opgehaald: voor elke variabele ?foo in de query is er nu ook een variabele ?fooLabel, die het label van het item achter ?foo bevat. Als u dit toevoegt aan de SELECT-clausule, krijgt u het item en zijn label:

SELECT ?child ?childLabel
WHERE
{
# ?child  father   Bach
  ?child wdt:P22 wd:Q1339.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Probeer die query uit te voeren, u zou nu niet alleen de artikelnummers moeten zien, maar ook de namen van de verschillende kinderen.

child childLabel
wd:Q57225 Johann Christoph Friedrich Bach
wd:Q76428 Carl Philipp Emanuel Bach

Automatisch aanvullen

Het stukje code SERVICE ziet er moeilijk uit om te onthouden, toch? En de hele tijd door de zoekfunctie gaan terwijl u de zoekopdracht schrijft, is ook vervelend. Gelukkig biedt WDQS hier een prima oplossing voor: autocompletion. In de query-editor query.wikidata.org kunt u op elk gewenst moment in de query op Ctrl+Space (of Alt+Enter of Ctrl+Alt+Enter) drukken en suggesties voor code krijgen die geschikt kunnen zijn. Selecteer de juiste suggestie met de pijltoetsen omhoog /omlaag en druk op Enter om deze te selecteren.

Voorbeeld, in plaats van het uitschrijven van SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }, kunt u ook intikken SERV, vervolgens Ctrl+Space doen, en de eerste suggestie zal het automatisch aanvullen van het label zijn, klaar voor gebruik! Accepteer de suggestie met Enter. (De opmaak zal een beetje anders zijn, maar dat maakt niet uit.)

Met automatisch aanvullen kunt u ook zoeken. Als u een van de Wikidata-voorvoegsels, zoals wd: of wdt:, tikt en vervolgens gewoon tekst schrijft, zal Ctrl+Space op Wikidata naar die tekst zoeken en suggesties tonen. wd: zoekopdrachten naar items, wdt: zoekopdracht naar eigenschappen. In plaats van bijvoorbeeld naar items voor Johann Sebastian Bach (Q1339) en father (P22) te zoeken, kunt u gewoon wd:Bach en wd:Bach typen en vervolgens gewoon de juiste suggestie selecteren. (Dit werkt zelfs met spaties in de tekst, bijvoorbeeld wd:Johann Sebastian Bach.)

Geavanceerde tripel patronen

We hebben nu alle kinderen van Johann Sebastian Bach opgezocht. Meer specifiek: alle items met de vader Johann Sebastian Bach. Maar Bach had twee vrouwen, en dus kunnen die personen verschillende moeders hebben: wat als we alleen de kinderen van Johann Sebastian Bach met zijn eerste vrouw willen zien, Maria Barbara Bach (Q57487)? Probeer die query te schrijven.

Heeft u dat gedaan? Oké, dan op de oplossing! De eenvoudigste manier om dit te doen is om een tweede tripel toe te voegen met die beperking:

SELECT ?child ?childLabel
WHERE
{
  ?child wdt:P22 wd:Q1339.
  ?child wdt:P25 wd:Q57487.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Er staat dan:

Child has father Johann Sebastian Bach.

Child has mother Maria Barbara Bach.

Dat klinkt een beetje ongemakkelijk, nietwaar? In natuurlijke taal, zouden we dit afkorten tot:

Child has father Johann Sebastian Bach and mother Maria Barbara Bach.

In SPARQL is het zelfs mogelijk om dezelfde afkorting uit te drukken: Als u een tripel eindigt met een puntkomma (;) in plaats van een punt, kunt u een ander predikaat-objectpaar toevoegen. Hiermee kunnen we de bovenstaande query verkorten tot:

SELECT ?child ?childLabel
WHERE
{
  ?child wdt:P22 wd:Q1339;
         wdt:P25 wd:Q57487.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Dat geeft dezelfde resultaten, maar minder herhaling in de query.

Stel dat we alleen maar belangstelling hebben voor kinderen die ook componisten en pianisten waren. De relevante eigenschappen en items zijn: occupation (P106), composer (Q36834) en pianist (Q486748). Probeer de bovenstaande query bij te werken om deze beperkingen toe te voegen!

Dit is mijn oplossing:

SELECT ?child ?childLabel
WHERE
{
  ?child 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!

Hierbij wordt ; twee keer meer gebruikt om de twee vereiste beroepen toe te voegen. Maar zoals u kunt zien, is er nog steeds een beetje herhaling. Het is alsof we zeggen:

Kind heeft het beroep componist en het beroep pianist.

We zeggen dan meestal:

Kind heeft de beroepen componist en pianist.

En SPARQL heeft daar ook een syntaxis voor: net als ; laat u toe een predikate-objectpaar aan een tripel toe te voegen (met hergebruik van het onderwerp), een , laat u toe om een ander object aan een tripel aan te voegen (met hergebruik van onderwerp en predikaat). Hiermee kan de query worden:

SELECT ?child ?childLabel
WHERE
{
  ?child 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!

NB: inspringing en andere witruimtes doen er eigenlijk niet toe, ze maken het alleen leesbaarder. U kunt dit ook schrijven als:

SELECT ?child ?childLabel
WHERE
{
  ?child wdt:P22 wd:Q1339;
         wdt:P25 wd:Q57487;
         wdt:P106 wd:Q36834, wd:Q486748.
  # beide beroepen op een regel
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

of, wat minder leesbaar:

SELECT ?child ?childLabel
WHERE
{
  ?child wdt:P22 wd:Q1339;
  wdt:P25 wd:Q57487;
  wdt:P106 wd:Q36834,
  wd:Q486748.
  # zonder inspringen of nieuwe regels wordt het moeilijker leesbaar of er nu een ';' staat of een ','
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Gelukkig springt de WDQS-editor automatisch in, dus u hoeft u hier meestal geen zorgen over te maken.

Oké, laten we hier samenvatten. We hebben gezien dat queries zijn gestructureerd als tekst. Elk tripel over een onderwerp wordt afgesloten met een punt. Meerdere predicaten over hetzelfde onderwerp worden gescheiden door middel van puntkomma's, en meerdere objecten voor hetzelfde onderwerp en predicaat kunnen worden vermeld gescheiden door komma's.

SELECT ?s1 ?s2 ?s3
WHERE
{
  ?s1 p1 o1;
      p2 o2;
      p3 o31, o32, o33.
  ?s2 p4 o41, o42.
  ?s3 p5 o5;
      p6 o6.
}

Nu wil ik nog een afkorting introduceren die SPARQL biedt. Dus als u een hypothetisch scenario wilt laten maken...

Stel dat we niet geïnteresseerd zijn in Bach's kinderen. Maar wij zijn geïnteresseerd in zijn "kleinkinderen". Er is hier één complicatie: een kleinkind kan verwant zijn aan Bach via de moeder of de vader. Dat zijn twee verschillende eigenschappen, wat ongemakkelijk is. Laten we de relatie omdraaien: Wikidata heeft ook een "kind" eigenschap, P:P40, die van ouder naar kind wijst en geslachtonafhankelijk is. Kunt u met deze informatie een query schrijven om naar Bachs kleinkinderen te zoeken?

Dit is mijn oplossing:

SELECT ?grandChild ?grandChildLabel
WHERE
{
  wd:Q1339 wdt:P40 ?child.
  ?child wdt:P40 ?grandChild.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

In natuurlijke taal is dat:

Bach has a child ?child.

?child has a child ?grandChild.

Opnieuw stel ik voor dat we deze Engelse zin verkorten, en dan wil ik u laten zien hoe SPARQL een vergelijkbare afkorting ondersteunt. Let op dat we eigenlijk niet om het kind geven: we gebruiken de variabele niet, behalve om te praten over het kleinkind. We kunnen de zin verkorten tot:

Bach has as child someone who has a child ?grandChild.

In plaats van te zeggen wie Bachs kind is, zeggen we gewoon "iemand": het maakt ons niet uit wie het is. Maar we kunnen terug naar hen, omdat we hebben gezegd "iemand "wie": dit begint een relatieve clausule, en binnen die relatieve clausule kunnen we dingen zeggen over "iemand" (bijvoorbeeld dat ze "een kind hebben ?grandChild"). Op een bepaalde manier is "iemand" een variabele, maar een speciale die alleen geldig is binnen deze relatieve clausule, en een waar we niet expliciet naar verwijzen (we zeggen "iemand die dit is en dat doet", niet "iemand dat dit is en iemand die dat doet" - dat zijn twee verschillende "iemanden").

In SPARQL kan dit worden geschreven als:

SELECT ?grandChild ?grandChildLabel
WHERE
{
  wd:Q1339 wdt:P40 [ wdt:P40 ?grandChild ].
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

We kunt een paar haakjes ([]) gebruiken in plaats van een variabele, die als een anonieme variabele fungeert. Binnen de koppels kunt u predicaat-objectparen vermelden, net als na een ; na een normale tripel; het impliciete onderwerp is in dit geval de anonieme variabele die de koppels vertegenwoordigen. (NB: ook net als na een ; kunt u meer predicaat-objectparen toevoegen met meer puntkomma's, of meer objecten voor hetzelfde predicaat met komma's.)

En dat is het voor drievoudige patronen! Er is meer aan SPARQL, maar omdat we de delen ervan gaan verlaten die sterk vergelijkbaar zijn met natuurlijke taal, wil ik die relatie nog een keer samenvatten:

natuurlijke taal voorbeeld SPARQL voorbeeld
zin Juliet loves Romeo. punt juliet loves romeo.
conjunctie (clause) Romeo loves Juliet and kills himself. puntkomma romeo loves juliet; kills romeo.
conjunctie (zelfstandig naamwoord) Romeo kills Tybalt and himself. komma romeo kills tybalt, romeo.
relatieve clausule Juliet loves someone who kills Tybalt. haakjes juliet loves [ kills tybalt ].

Instanties en classes

Eerder zei ik dat de meeste Wikidata-eigenschappen "has/heeft" relaties zijn: "heeft" kind, "heeft" vader, "heeft" baan. Maar soms (in feite, vaak) moet u ook praten over wat iets "is". Maar er zijn eigenlijk twee soorten relaties:

  • Gone with the Wind is een film.
  • Een film is een kunstwerk.

Gone with the Wind is een bepaalde film. Het heeft een bepaalde regisseur (Victor Fleming), een specifieke duur (238 minuten), een lijst van castleden (Clark Gable, Vivien Leigh,...), enzovoort.

Film is een algemeen begrip. Films kunnen regisseurs, (speel)duur en castleden hebben, maar het concept "film" als zodanig heeft geen specifieke regisseur, duur of castleden. En hoewel een film een kunstwerk is, en een kunstwerk meestal een schepper heeft, heeft het concept van "film" zelf geen schepper - alleen specifieke instancties van dit concept wel.

Dit verschil is de reden waarom er in Wikidata twee eigenschappen voor "is" zijn: instance of (P31) en subclass of (P279). "Gone with the Wind" is een bijzonder voorbeeld van de klasse "film"; de klasse "film" is een onderklasse (meer specifieke klasse; specialisatie) van de meer algemene klasse "kunstwerk".

Om u te helpen het verschil te begrijpen, kunt u twee verschillende werkwoorden gebruiken: "is een" en "is een soort van". Als "is een soort" werkt (bijv. een film "is een type" kunstwerk), geeft het aan dat u het heeft over een subklasse, een specialisatie van een bredere klasse en moet u subclass of (P279) gebruiken. Als "is een soort" niet werkt (bijvoorbeeld de zin "Gone with the wind "is een type" film" is niet logisch), geeft het aan dat u praat over een bepaalde instantie en moet u instance of (P31) gebruiken.

Wat betekent dit voor ons als we SPARQL-queries schrijven? Als we naar "alle kunstwerken" willen zoeken, is het niet genoeg om naar alle items te zoeken die direct voorbeelden zijn van "kunstwerken":

SELECT ?work ?workLabel
WHERE
{
  ?work wdt:P31 wd:Q838948. # instantie van kunstwerk
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Terwijl ik dit schrijf (oktober 2016), geeft die query slechts 2.815 resultaten terug - natuurlijk zijn er meer kunstwerken dan dat! Het probleem is dat er hier items zoals "Gone with the Wind" missen, wat slechts een voorbeeld is van "film", niet van "kunstwerk". "film" is een onderklasse van "kunstwerk", maar we moeten SPARQL vertellen om daar rekening mee te houden bij het zoeken.

Een mogelijke oplossing hiervoor is de syntaxis [] waarover we spraken: "Gone with the Wind" is een voorbeeld van sommige onderklasse van "kunstwerk". (Oefening: probeer die query te schrijven!) Maar dat heeft nog steeds problemen:

  1. We nemen niet langer items op die rechtstreeks voorbeelden van kunstwerken zijn.
  2. We missen nog steeds items die voorbeelden zijn van een bepaalde subklasse van een "andere" subklasse "kunstwerk" - bijvoorbeeld, "Snow White and the Seven Dwarfs" is een animatiefilm, dat een film is, dat een kunstwerk is. In dit geval moeten we twee "subklasse van" verklaringen volgen - maar het kan ook drie, vier, vijf zijn, elk aantal is mogelijk.

De oplossing: ?item wdt:P31/wdt:P279* ?class. Dit betekent dat er één "instantie van" en dan een aantal "subclass van" verklaringen zijn tussen het item en de klasse.

SELECT ?work ?workLabel
WHERE
{
  ?work wdt:P31/wdt:P279* wd:Q838948. # een instantie van een subklasse kunstwerken
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

(Ik raad u niet aan om die query te laten draaien. WDQS kan het (maar net aan), maar uw browser kan struikelen als u de resultaten probeert weer te geven omdat er zoveel van hen zijn.)

Nu weet u hoe u moet zoeken naar alle kunstwerken, of alle gebouwen, of alle menselijke nederzettingen: de magische bezwering : wdt:P31/wdt:P279*, samen met de juiste klasse. Dit gebruikt wat meer SPARQL-functies die ik nog niet heb uitgelegd, maar eerlijk gezegd is dit bijna het enige relevante gebruik van die functies, dus hoeft u niet te moeten begrijpen hoe het werkt om WDQS effectief te gebruiken. Als u het wilt weten, zal ik het zo uitleggen, maar u kunt ook gewoon het volgende gedeelte overslaan en wdt:P31/wdt:P279* onthouden of kopiëren + plakken vanaf hier wanneer u het nodig hebt.

Eigenschapspaden

Over het algemeen is het pad dat het bronknooppunt (subject) verbindt met het doelknooppunt (object) door de grafiek niet altijd direct: het kan zijn dat men nul, één of meerdere schakels (segmenten, namelijk padelementen) moet samenvoegen tot een keten; En er kunnen meerdere van dergelijke paden (routes) zijn. Het object van een padelement in de keten wordt het onderwerp van het volgende element. In SPARQL, eigenschapspaden zijn een manier om heel strikt zo'n pad van eigenschappen tussen twee items op te schrijven. Het eenvoudigste pad is slechts een enkele eigenschap, die een gewone tripel vormt:

?item wdt:P31 ?class.

U kunt pad-elementen toevoegen met een slash (/).

?item wdt:P31/wdt:P279/wdt:P279 ?class.

Dit is gelijk aan een van de volgende:

?item wdt:P31 ?temp1.
?temp1 wdt:P279 ?temp2.
?temp2 wdt:P279 ?class.
?item wdt:P31 [ wdt:P279 [ wdt:P279 ?class ] ].

Oefening: schrijf de query "kleinkinderen van Bach" met gebruik van deze syntaxis.

Een ster (*) na een pad betekent “nul of meer van deze elementen”.

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

In het speciale geval dat er een nul-eigenschap in een pad is (geen specifieke relatieboog: een NULL, "universele" eigenschap), dan is de onderwerpknoop direct verbonden met de objectknoop in de grafiek, ongeacht de objectknoop, inclusief zichzelf. Zodat er altijd een match is. Dus, in SPARQL, bijvoorbeeld in het geval "nul iets", wordt ?a iets*?b gereduceerd tot ?a ?b, zonder pad ertussen, en ?a neemt direct de waarde van ?b.

Een plus (+) is vergelijkbaar met een ster, maar betekent "één" of meer van deze elementen. De volgende query vindt alle afstammelingen van Bach:

SELECT ?descendant ?descendantLabel
WHERE
{
  wd:Q1339 wdt:P40+ ?descendant.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Als we hier een ster zouden gebruiken in plaats van een plus, zou het resultaat ook Bach zelf bevatten.

Een vraagsteken (?) is vergelijkbaar met een ster of een plus, maar betekent "nul of één" van dit element.

U kunt pad-elementen met een verticale lijn (|) scheiden in plaats van een slash te gebruiken; dit betekent "of-of": het pad kan een van deze eigenschappen gebruiken. (Maar niet gecombineerd - een een of ander segment past altijd bij een pad van lengte één.)

U kunt ook pad-elementen met haakjes (()) groeperen en al deze syntaxiselementen vrij combineren (/|*+?). Dat betekent dat een andere manier om alle afstammelingen van Bach te vinden is:

SELECT ?descendant ?descendantLabel
WHERE
{
  ?descendant (wdt:P22|wdt:P25)+ wd:Q1339.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

In plaats van de eigenschap "child" te gebruiken om van Bach naar zijn nakomelingen te gaan, gebruiken we de eigenschappen "father" en "mother" om van de afstammelingen naar Bach te gaan. Het pad kan twee moeders en een vader omvatten, of vier vaders, of vader-moeder-moeder-vader, of een andere combinatie. (Al kan Bach natuurlijk niet de moeder van iemand zijn, dus het laatste element zal altijd vader zijn.)

Kwalificaties

(Eerst goed nieuws: dit gedeelte introduceert geen extra SPARQL syntaxis. Dit is een toetje.)

Tot nu toe hebben we alleen gesproken over eenvoudige verklaringen: onderwerp, eigenschap, object. Maar Wikidata-verklaringen zijn meer dan dat: ze kunnen ook kwalificaties en referenties hebben. De Mona Lisa heeft bijvoorbeeld drie verklaringen made from material (P186):

  1. oil paint (Q296955), het hoofdmateriaal;
  2. poplar wood (Q291034), met de kwalificatie applies to part (P518)painting support (Q861259) – het materiaal waarop de Mona Lisa is geschilderd; en
  3. wood (Q287), met de kwalificaties applies to part (P518)stretcher (Q1737943) en start time (P580) 1951 – het deel dat later aan het schilderij is toegevoegd.

Stel dat we alle schilderijen met hun schilderijoppervlak willen vinden, dat wil zeggen, die verklaringen made from material (P186) met een kwalificatie applies to part (P518)painting support (Q861259). Hoe doen we dat? Dat is meer informatie dan in een enkele tripel kan worden weergegeven.

Het antwoord is: meer tripels! (De vuistregel: de oplossing van Wikidata voor bijna alles is "meer items", en de overeenkomstige WDQS regel is "meer tripels". Referenties, numerieke precisie, waarden met eenheden, coördinaten, ..., die we hier allemaal overslaan, werken ook op deze manier.) Tot nu toe hebben we de prefix wdt: gebruikt voor onze tripels, die rechtstreeks naar het object van de verklaring wijst. Maar er is ook een ander prefix: p:, dat niet naar het object wijst, maar naar een "verklaring node". Deze node is dan het onderwerp van andere tripels: de prefix ps: (voor property statement) wijst naar het statement object, de prefix pq: (property qualifier) naar kwalificaties, en prov:wasDerivedFrom wijst naar referentie-nodes (die we voorlopig zullen negeren).

Dat was een heleboel abstracte tekst. Hier is een concreet voorbeeld voor de 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)

We kunnen dit afkorten met de syntaxis [], waarbij de ?statement"-variabelen worden vervangen:

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

Kunt u deze kennis gebruiken om een query te schrijven voor alle schilderijen met hun schilderijoppervlak?

Dit is mijn oplossing:

SELECT ?painting ?paintingLabel ?material ?materialLabel
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]". }
}
Try it!

Ten eerste beperken we ?painting (de schilderijen) tot alle instanties van painting (Q3305213) of een subklasse daarvan. Vervolgens extraheren we het materiaal uit de p:P186 node, waarbij we de verklaringen beperken tot die met een kwalificatie applies to part (P518)painting support (Q861259).

ORDER en LIMIT

We gaan terug naar ons reguliere programma met meer SPARQL-functies.

Tot nu toe hebben we alleen queries gedaan waar we geïnteresseerd waren in alle resultaten. Maar het is heel gebruikelijk om alleen maar om een paar resultaten te willen zoeken: die die op een of andere manier het meest extreme zijn - oudste, jongste, vroegste, nieuwste, hoogste bevolking, laagste smeltpunt, de meeste kinderen, de meeste gebruikte materialen, enzovoort. De gemeenschappelijke factor hier is dat de resultaten op een of andere manier "rangschikt" zijn, en dan geven we om de eerste resultaten (de resultaten met de beste rang).

Dit wordt gecontroleerd door twee clausules, die aan de WHERE {} (na de accolades, niet binnen!) worden gehecht: ORDER BY en LIMIT.

De resultaten worden volgens op iets gesorteerd met ORDER BY iets. Het enige soort expressie (iets) dat we nu kennen zijn eenvoudige variabelen, maar we zullen later andere soorten zien. Deze expressie kan ook worden omwikkelt met ASC() of DESC() om de sorteervolgorde ("'ascending" of "desc"'ending) te specificeren. Als u het niet aangeeft dan is het ascending, dus oplopend.

LIMIT count kapt het aantal vondsten in het resultaat af op count resultaten, waar count gewoon een getal is. Voorbeeld: LIMIT 10 zorgt ervoor dat de query maximaal 10 regels teruggeeft. LIMIT 1 geeft dus maar maximaal één regel in het resultaat.

(U kunt ook LIMITgebruiken zonder ORDER BY. In dit geval worden de resultaten niet gesorteerd, dus u heeft geen garantie welke resultaten u krijgt. Wat goed is als u weet dat er slechts een bepaald aantal resultaten zijn, of u gewoon geïnteresseerd bent in "sommige" resultaten, maar het u niet uitmaakt, welke dat zijn. In beide gevallen kan het toevoegen van de LIMIT de zoekopdracht aanzienlijk versnellen, omdat WDQS het zoeken kan stoppen zodra er genoeg gevonden zijn volgens de limiet.)

Tijd om te oefenen! Probeer een query te schrijven die de tien meest bevolkte landen teruggeeft. Een land is een sovereign state (Q3624078), en de eigenschap voor bevolking is P:P1082. U kunt beginnen met het zoeken naar landen met een bevolking en vervolgens de ORDER BY en LIMIT clausules toevoegen.

Dit is mijn oplossing:

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!

Merk op dat als we de meest bevolkte landen willen, we moeten descending moeten ordenen op bevolking, zodat de eerste resultaten de landen met de hoogste bevolking zullen zijn.

Oefening

We hebben al veel zaken besproken, ik denk dat het tijd is voor oefeningen. (U kunt dit gedeelte overslaan als u haast hebt.)

Arthur Conan Doyle boeken

Schrijf een query die alle boeken van Sir Arthur Conan Doyle ophaalt.

Chemische elementen

Schrijf een query die alle chemische elementen retourneert met hun elementsymbool en atoomnummer, in volgorde van hun atoomnummer.

Rivers die in de Mississippi stromen

Schrijf een query die alle rivieren retourneert die rechtstreeks in de Mississippi uitmonden. (De grootste uitdaging is het vinden van de juiste eigenschap...)

Rivers die in de Mississippi stromen II

Schrijf een query die alle rivieren geeft die direct of indirect in de Mississippi rivier stromen.

OPTIONAL

In de oefeningen hierboven staat een query voor alle boeken van Sir Arthur Conan Doyle:

SELECT ?book ?bookLabel
WHERE
{
  ?book wdt:P50 wd:Q35610.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Maar dat is een beetje saai. Er zijn zoveel potentiële gegevens over boeken, en we tonen alleen het label? Laten we proberen een query te maken die ook title (P1476), illustrator (P110), publisher (P123) en publication date (P577) bevat.

Een eerste poging zou er zo uit kunnen zien:

SELECT ?book ?title ?illustratorLabel ?publisherLabel ?published
WHERE
{
  ?book wdt:P50 wd:Q35610;
        wdt:P1476 ?title;
        wdt:P110 ?illustrator;
        wdt:P123 ?publisher;
        wdt:P577 ?published.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Voer de query uit. Terwijl ik dit schrijf, geeft het slechts twee resultaten! Waarom is dat zo? We vonden eerder meer dan honderd boeken!

De reden is dat een potentieel resultaat (een boek) om deze vraag te beantwoorden, alle drie de tripels moeten overeenkomen die we hebben vermeld: het moet een titel hebben, een illustrator, een uitgever en een publicatiedatum. Als het een aantal van die eigenschappen heeft, maar niet alle, zal het niet overeenkomen. En dat is niet wat we willen in dit geval: we willen in de eerste plaats een lijst van alle boeken. Als er extra gegevens beschikbaar zijn, willen we die weten, maar we willen niet dat dat onze lijst van resultaten beperkt.

De oplossing is om WDQS te vertellen dat die tripels optioneel zijn:

SELECT ?book ?title ?illustratorLabel ?publisherLabel ?published
WHERE
{
  ?book wdt:P50 wd:Q35610.
  OPTIONAL { ?book wdt:P1476 ?title. }
  OPTIONAL { ?book wdt:P110 ?illustrator. }
  OPTIONAL { ?book wdt:P123 ?publisher. }
  OPTIONAL { ?book wdt:P577 ?published. }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Dit geeft ons de extra variabelen (?title, ?publisher etc.) als de juiste verklaring bestaat, maar als het niet bestaat, wordt het resultaat niet weggegooid - de variabele is gewoon niet ingesteld.

NB: het is erg belangrijk om hier afzonderlijke OPTIONAL clausules te gebruiken. Als u alle tripels in één clausule zet, zoals hier -

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!

- zult u merken dat de meeste resultaten geen extra informatie bevatten. Dit komt omdat een optionele clausule met meerdere tripels alleen overeenkomt wanneer alle die triples tegelijk voorkomen. Dat wil zeggen: als een boek een titel, een illustrator, een uitgever en een publicatiedatum heeft, dan past de optionele clausule overeen, en worden die waarden toegewezen aan de juiste variabelen. Maar als een boek bijvoorbeeld een titel heeft, maar geen illustrator, dan past de volledige optionele clausule niet overeen, en hoewel het resultaat niet wordt afgewezen, blijven alle vier variabelen leeg.

Expressies, FILTER en BIND

Deze sectie lijkt misschien iets minder georganiseerd dan de andere, omdat het een vrij breed en divers onderwerp behandelt. Het basisconcept is dat we iets willen doen met de waarden die we tot nu toe gewoon hebben geselecteerd en lukraak hebben teruggegeven. En expressies zijn de manier om deze operaties op waarden uit te drukken. Er zijn veel soorten expressies en veel dingen die u ermee kunt doen, maar laten we eerst beginnen met de basis: gegevenstypen.

Gegevenstypes

Elke waarde in SPARQL heeft een type, die aangeeft wat voor soort waarde het is en wat u ermee kunt doen. De belangrijkste types zijn:

  • item, zoals wd:Q42 voor Douglas Adams (Q42).
  • boolean, met twee mogelijke waarden true en false. Deze waarden worden niet opgeslagen in verklaringen, maar veel expressies geven een boolean waarde terug, bijv. 2 < 3 (true) of "a" = "b" (false).
  • string, een tekst. Deze worden tussen dubbele aanhalingstekens geschreven.
  • eentalige tekst, een tekst met een bijbehorende tag voor de taal. In een tekst kunt u deze tag toevoegen na de tekst met het teken @, voorbeeld "Douglas Adams"@en.
  • nummers, of integers (1) of met decimalen (1.23).
  • datums. Deze worden geschreven met de ^^xsd:dateTime toegevoegd (hoofdletterafhankelijk – ^^xsd:datetime zal niet werken!) aan een ISO 8601 datumstring: "2012-10-29"^^xsd:dateTime.

Operatoren

De bekende wiskundige operatoren zijn beschikbaar: +, -, *, / om getallen toe te voegen, af te trekken, te vermenigvuldigen of te delen, <, >, =, <=, >= om ze te vergelijken. De ongelijkheidstest ≠ wordt geschreven als !=. Vergelijkingen wordt ook voor andere soorten gedefinieerd; bijvoorbeeld, "abc" < "abd" is waar (lexicale vergelijking), zoals "2016-01-01"^^xsd:dateTime > "2015-12-31"^^xsd:dateTime enwd:Q4653 != wd:Q283111. En booleaanse voorwaarden kunnen worden gecombineerd met && (logische en: a && b is waar als zowel a en b waar zijn) en || (logisch of: a || b is waar indien een (of beide) van a en b waar is).

FILTER

  Info Voor een soms sneller alternatief voor FILTER, kunt u ook kijken naar MINUS, zie voorbeeld.

FILTER(condition). is een clausule die u in een SPARQL query kunt gebruiken om de resultaten te filteren. Binnen de haakjes kunt u elke expressie met een booleaans type plaatsen, alleen die resultaten waar de expressie true is, worden gebruikt.

Om bijvoorbeeld een lijst te krijgen van alle mensen die in 2015 zijn geboren, halen we eerst alle mensen met hun geboortedatum op

SELECT ?person ?personLabel ?dob
WHERE
{
  ?person wdt:P31 wd:Q5;
          wdt:P569 ?dob.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } 
}

- en vervolgens filteren om alleen de resultaten terug te geven wanneer het jaar van de geboortedatum 2015 is. Er zijn twee manieren om dat te doen: het jaar met de YEAR functie eruit te halen of het testen of het jaar 2015 is

FILTER(YEAR(?dob) = 2015).

- of controleer of de datum tussen 1 januari 2015 (inclusief) en 1 januari 2016 (exclusief) is:

FILTER("2015-01-01"^^xsd:dateTime <= ?dob && ?dob < "2016-01-01"^^xsd:dateTime).

Ik zou zeggen dat het eerste eenvoudiger is, maar de tweede is veel sneller, dus laten we die gebruiken:

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!

Een ander mogelijk gebruik van FILTER is in verband met de labels. De service label is zeer nuttig als u alleen het label van een variabele wilt weergeven. Maar u iets met het label wilt doen, bijvoorbeeld: controleren of het begint met "Mr", dan blijkt dat dat niet werkt.

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!

Deze query vindt alle instanties van fictional human (Q15632617) en test of hun label begint met "Mr. " (STRSTARTSSTRSTARTS is afkorting voor "string begint [met]"; er zijn ook functies STRENDS en CONTAINS). De reden waarom dit niet werkt is dat de service label zijn variabelen heel laat toevoegt tijdens de evaluatie van de query; op het moment dat we proberen te filteren op 'humanLabel', heeft de service die variabele nog niet gemaakt.

Gelukkig is de service label niet de enige manier om het label van een item te krijgen. Labels worden ook opgeslagen als gewone tripels, met behulp van het predicaatrdfs:label. Dit betekent natuurlijk alle labels, niet alleen Engelse; als we alleen Engelse labels willen, moeten we filteren op de taal van het label:

FILTER(LANG(?label) = "en").

De functie LANG geeft de taal van een eentalige string terug en hier selecteren we alleen de labels die in het Engels zijn. De volledige query is:

SELECT ?human ?label
WHERE
{
  ?human wdt:P31 wd:Q15632617;
         rdfs:label ?label.
  FILTER(LANG(?label) = "[AUTO_LANGUAGE]").
  FILTER(STRSTARTS(?label, "Mr. ")).
}
Try it!

Wij krijgen het label met de ?human rdfs:label ?label tripel, beperkt tot de Engelse labels, en controleren daarna of dat begint met “Mr. ”.

Filter kan ook worden gebruikt met een reguliere expressie. In het volgende voorbeeld

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!

Als de formaatbeperking voor een ID [A-Za-z][-.0-9A-Za-z]{1,} is:

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!

Het is mogelijk om specifieke elementen als deze te filteren

FILTER ( ?item not in ( wd:Q4115189,wd:Q13406268,wd:Q15397819 ) )

Het is mogelijk om elementen te filteren die niet zijn gevuld:

FILTER ( NOT EXISTS { ?item  wdt:P21 [] } )


BIND, BOUND, IF

Deze drie functies worden vaak in combinatie gebruikt, dus zal ik eerst alle drie uitleggen en u dan een paar voorbeelden laten zien.

Een BIND(expression AS ?variable). clausule kan worden gebruikt om het resultaat van een expressie aan een variabele toe te wijzen (meestal een nieuwe variabele, maar u kunt ook een bestaande variabele overschrijven).

BOUND(?variable) test of een variabele is gebonden aan een waarde (resultaat is true of false). Het is vooral handig voor variabelen die worden ingevoerd in een clausule OPTIONAL.

IF(condition,thenExpression,elseExpression) evalueert naar thenExpression als condition wordt beoordeeld als true, en anders naar elseExpression. Dat wil zeggen, IF(true, "yes", "no") evalueert naar "yes", en IF(false, "great", "terrible") evalueert naar "terrible".

BIND kan worden gebruikt om de resultaten van een bepaalde berekening aan een nieuwe variabele te binden. Dit kan een tussentijdse uitkomst zijn van een grotere berekening of gewoon een rechtstreeks resultaat van de query. Bijvoorbeeld om de leeftijd van mensen met de doodstraf te krijgen:

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).
  # of, in een expressie:
  #BIND(FLOOR((?died - ?born)/365.2425) AS ?age).
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

BIND kan ook worden gebruikt om gewoon constante waarden aan variabelen te binden om de leesbaarheid te vergroten. Bijvoorbeeld een query die alle vrouwelijke priesters vindt:

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!

kan als volgt worden geschreven:

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!

Het deel van de query, van ?woman tot ?priest. , is nu waarschijnlijk beter leesbaar. Het blok BIND ervoor is echter nogal afleidend, dus deze techniek moet spaarzaam worden gebruikt. (In de WDQS-gebruikersinterface kunt u ook uw muis over een term zoals wd:Q123 of wdt:P123 gaan en het label en de beschrijving voor de entiteit zien, zodat ?female beter leesbaar is dan wd:Q6581072 als u die functie negeert.)

IF expressies worden vaak gebruikt met conditie-expressies gemaakt met BOUND. Stel dat u een query hebt die sommige mensen laat zien, en in plaats van alleen hun label weer te geven, wilt u hun pseudonym (P742) weergeven als ze er een hebben, en het label alleen gebruiken als er geen pseudoniem bestaat. Hiervoor selecteert u het pseudoniem in een clausule OPTIONAL (het moet optioneel zijn - u wilt geen resultaten weggooien die geen pseudoniem hebben) en vervolgens BIND(IF(BOUND(… gebruiken om het pseudoniem of het label te selecteren.

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!

Andere eigenschappen die op deze manier kunnen worden gebruikt, zijn nickname (P1449), posthumous name (P1786) en taxon common name (P1843) - alles waar een soort "fallback" zinvol is.

U kunt ook BOUND met FILTER combineren om ervoor te zorgen dat aan ten minste één van de verschillende OPTIONAL blokken is voldaan. Laten we bijvoorbeeld alle astronauten die naar de maan zijn geweest en de leden van Apollo 13 (Q182252) (nabij genoeg, toch?). Die beperking kan niet worden uitgedrukt als een enkel eigenschapspad, dus we hebben één clausule OPTIONAL nodig voor "lid van een maanmissie" en een andere voor "lid of Apollo 13". Maar we willen alleen de resultaten hebben waar aan ten minste één van deze voorwaarden voldaan is.

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

De functie COALESCE kan worden gebruikt als een afkorting van het patroon BIND(IF(BOUND(?x), ?x, ?y) AS ?z). voor fallbacks hierboven vermeld: het neemt een aantal expressies en retourneert de eerste die zonder fouten evalueert. Bijvoorbeeld de bovenstaande "pseudoniem" fallback

BIND(IF(BOUND(?pseudonym),?pseudonym,?writerLabel) AS ?label).

kan beknopt worden geschreven als

BIND(COALESCE(?pseudonym, ?writerLabel) AS ?label).

en het is ook gemakkelijk om nog een terugvallabel toe te voegen voor het geval het ?writerLabel ook niet is gedefinieerd:

BIND(COALESCE(?pseudonym, ?writerLabel, "<no label>") AS ?label).

Groeperen

Tot nu toe waren alle queries die we hebben laten zien queries die alle items aan bepaalde voorwaarden voldoen; in sommige gevallen hebben we ook extra velden bij het item opgenomen (schilderijen met materialen, Arthur Conan Doyle boeken met titel en illustrator).

Maar het is heel gebruikelijk dat we geen lange lijst van alle resultaten willen. In plaats daarvan kunnen we queries maken als deze:

  • Hoeveel schilderijen werden op doek / populierenhout / enz. geschilderd?
  • Wat is de hoogste bevolking van de steden van elk land?
  • Wat is het totaal aantal geweren dat door elke fabrikant is geproduceerd?
  • Wie publiceert gemiddeld de dikste boeken?

Bevolking van steden

Laten we nu eens kijken naar de tweede query. Het is vrij eenvoudig om een query te schrijven die alle steden vermeldt met hun bevolking en land, op volgorde van het land:

SELECT ?country ?city ?population
WHERE
{
  ?city wdt:P31/wdt:P279* wd:Q515;
        wdt:P17 ?country;
        wdt:P1082 ?population.
}
ORDER BY ?country
Try it!

(NB: deze query retourneert veel resultaten, wat problemen voor uw browser kan veroorzaken. Misschien wilt u een LIMIT-clausule toevoegen.)

Omdat we de resultaten op een land orderen, vormen alle steden die tot een land behoren één aangrenzend blok in de resultaten. Om de hoogste bevolking binnen dat blok te vinden, willen we het blok beschouwen als een groep en alle individuele populatiegehalten samenvoegen in één waarde: het maximum. Dit wordt gedaan met een clausule GROUP BY onder het blok WHERE en een aggregeer functie (MAX) in de clausule 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!

We hebben de ORDER BY vervangen door een GROUP BY. Het effect hiervan is dat alle resultaten met hetzelfde land nu worden gegroepeerd tot één resultaat. Dit betekent dat we ook de clausule SELECT moeten wijzigen. Als we de oude clausule SELECT ?country ?city ?population zouden behouden, welke ?city en ?population zouden dan worden teruggegeven? Vergeet niet dat er veel resultaten zijn in dit ene resultaat; ze hebben allemaal hetzelfde land, dus we kunnen dat selecteren, maar omdat ze allemaal een andere stad en bevolking kunnen hebben, moeten we WDQS vertellen welke van die waarden we moeten selecteren. Dat is de taak van de geaggregeer functie. In dit geval hebben we MAX gebruikt: uit alle ?population waarden selecteren we de maximale voor het groepsresultaat. (Die waarde moeten we met de constructie AS ook een nieuwe naam geven, maar dat is maar een klein detail.)

Dit is het algemene patroon voor het schrijven van groepsquery's: schrijf een normale query die de gewenste gegevens retourneert (niet gegroepeerd, met veel resultaten per "groep"), voeg vervolgens een GROUP BY clause toe en voeg een geaggregeerde functie toe aan alle niet-gegroepeerde variabelen met een SELECT clause.

Schildersmaterialen

Laten we het proberen met een andere query: Hoeveel schilderijen zijn op elk materiaal geschilderd? Schrijf eerst een query die alle schilderijen en hun schilderijmateriaal teruggeeft. Let er op dat u alleen die verklaringen met een kwalificatie gebruikt.

SELECT ?material ?painting
WHERE
{
  ?painting wdt:P31/wdt:P279* wd:Q3305213;
            p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
}
Try it!

Voeg vervolgens een GROUP BY clause toe aan het ?material en vervolgens een aggregaatfunctie aan de andere geselecteerde variabele (?painting). In dit geval zijn we geïnteresseerd in het aantal schilderijen; de geaggregeerde functie daarvoor is 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!

Een probleem hiermee is dat we het label niet hebben voor de materialen, dus de resultaten zijn een beetje ongemakkelijk te interpreteren. Als we die variabele toevoegen, krijgen we een fout:

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" is een foutbericht dat u waarschijnlijk veel zult zien wanneer u met groepsquery's werkt; Het betekent dat een van de geselecteerde variabelen een aggregeer functie nodig heeft, maar er geen heeft, of dat het een aggregeer functie heeft maar er geen zou moeten hebben. In dit geval denkt WDQS dat er meerdere ?materialLabel per ?material kunnen zijn (hoewel we weten dat dat niet kan gebeuren), en dus klaagt het dat u geen aggregeer functie voor die variabele opgeeft.

Een oplossing is om verschillende variabelen te groeperen. Als u meerdere variabelen in de GROUP BY clausule opschrijft, is er één resultaat voor elke combinatie van die variabelen, en kunt u al die variabelen selecteren zonder de samengestelde functie. In dit geval zullen we zowel op ?material als ?materialLabel pgroeperen.

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!

We zijn bijna klaar met de query, nog één verbetering: we willen graag eerst de meest gebruikte materialen zien. Gelukkig mogen we de nieuwe, geaggregeerde variabelen uit de SELECT-component (hier, ?count) gebruiken in een ORDER BY clausule, dus dit is heel eenvoudig:

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!

Als oefening doen we ook de andere queries.

Wapens per fabrikant

Wat is het totale aantal geweren dat door elke fabrikant wordt geproduceerd?

Uitgevers per aantal pagina's

Wat is het gemiddelde (functie: AVG) aantal pagina's van boeken per uitgever?

HAVING

Een kleine toevoeging bij die laatste query, als u naar de resultaten kijkt, merkt u misschien dat het hoogste resultaat een erg hoog gemiddelde heeft, meer dan tien keer dat van de tweede plaats. Een beetje onderzoek leert dat dit komt omdat die uitgever (UTET (Q4002388)) slechts één boek heeft gepubliceerd met een number of pages (P1104) verklaring, Grande dizionario della lingua italiana (Q3775610), wat de resultaten een beetje vertekent. Om dergelijke uitschieters te verwijderen, zouden we kunnen proberen alleen uitgevers te selecteren die ten minste twee boeken met number of pages (P1104) verklaringen op Wikidata hebben gepubliceerd.

Hoe doen we dat? Normaal gesproken beperken we resultaten met een FILTER clausule, maar in dit geval willen we het beperken op basis van de groep (het aantal boeken), niet een individueel resultaat. Dit wordt gedaan met een HAVING clausule, die direct na een GROUP BY clausule kan worden geplaatst en een expressie neemt net zoals GROUP BY doet:

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!

Samenvatting van aggregatie functies

Hier is een korte samenvatting van de beschikbare functies om te aggregeren:

  • COUNT: Telt het aantal elementen. U kunt ook COUNT(*) schrijven om alle resultaten eenvoudig te tellen.
  • SUM, AVG: de som of het gemiddelde van alle elementen respectievelijk. Als de elementen geen getallen zijn, krijgt u vreemde resultaten.
  • MIN, MAX: de minimale of maximale waarde van alle elementen respectievelijk. Dit werkt voor alle waardetypen; getallen worden als getal gesorteerd, teksten en andere typen lexicaal.
  • SAMPLE: elk element. Dit is soms nuttig als u weet dat er maar één resultaat is, of als het u niet uitmaakt welke resultaat u krijgt.
  • GROUP_CONCAT: voegt alle elementen samen. Het is bijvoorbeeld handig als u slechts één resultaat voor een item wilt hebben, maar u informatie voor een eigenschap wilt bevatten die verschillende vermeldingen voor dit item kan hebben, zoals de beroepen van een persoon. De verschillende beroepen kunnen worden vergroot en samengevoegd om in de resultaten in plaats van in meerdere regels in slechts één variabele te verschijnen. Als u nieuwsgierig bent, kunt u het zoeken in de SPARQL specificatie.

Bovendien kunt u een DISTINCT modifier toevoegen voor elk van deze functies om dubbele resultaten te elimineren. Als er bijvoorbeeld twee resultaten zijn, maar ze hebben allebei dezelfde waarde in ?var, dan retourneert COUNT(?var) 2, maar 2 retourneert alleen 1. U moet vaak DISTINCT gebruiken wanneer uw query hetzelfde item meerdere keren kan retourneren - dit kan bijvoorbeeld gebeuren als u DISTINCT class gebruikt en er meerdere paden zijn van ?item naar ?class : u krijgt een nieuw resultaat voor elk van die paden, ook al zijn alle waarden in het resultaat identiek. (Als u niet groepeert, kunt u deze dubbele resultaten ook verwijderen door de query te starten met SELECT DISTINCT in plaats van alleen SELECT.)

wikibase:Label en aggregaties

Een query als de volgende, die alle academische personen met meer dan twee landen van burgerschaps verwacht in Wikidata, laat de namen van die landen niet zien in de kolom ?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!

Om de ?citizenships te tonen, noem de ?personLabel en ?citizenshipLabel uitdrukkelijk in de wikibase:label aanroep als volgt:

  SERVICE wikibase:label { 
    bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". 
    ?citizenship rdfs:label ?citizenshipLabel .
    ?person      rdfs:label ?personLabel .
  }

De volgende query werkt zoals verwacht:

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

U kunt items selecteren op basis van een lijst van items:

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!

U kunt ook selecteren op basis van een lijst van waarden van een specifieke eigenschap:

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 kan ook meer doen en heeft mogelijkheden voor het opsommen van een paar (of een tuple) variabelen. Stel bijvoorbeeld dat u (bekende) aangepaste labels wilt gebruiken voor de personen die zijn vermeld in het eerste voorbeeld met een bepaalde waarde. Het is dan mogelijk om een VALUE clausule te gebruiken zoals VALUES (?item ?customItemLabel) { (wd:Q937 "Einstein") (wd:Q1339 "Bach") } die ervoor zorgt dat wanneer ?item waarde wd:Q937 in een resultaat heeft, ?customItemLabel eigen waarde is Einstein en wanneer ?item waarde wd:Q1339 heeft, ?customItemLabel waarde Bach is.

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!

En verder...

Deze gids eindigt hier. SPARQL omvat meer dan hier beschreven: er is nog veel wat ik u niet heb laten zien - ik heb nooit beloofd dat dit een complete gids zou zijn! Als u dit zo ver hebt bereikt, weet u al veel over WDQS en moet u in staat zijn om een aantal zeer krachtige vragen te schrijven. Maar als u nog meer wilt weten, kunt u hier wat dingen bekijken:

  • Subqueries. U kunt een andere complete query toevoegen in accolades ({ SELECT ... WHERE { ... } LIMIT 10 }), en de resultaten zijn zichtbaar in de externe vraag. (Als u bekend bent met SQL, moet u het concept een beetje opnieuw bedenken - SPARQL-subvragen zijn puur "bottom-up" en kunnen geen waarden van de externe vraag gebruiken, zoals in SQL dat bij "correlated subqueries" kan.)
  • MINUS laat u resultaten selecteren die "niet" passen bij een bepaald grafiekpatroon. FILTER NOT EXISTS is meestal gelijkwaardig (zie de SPARQL-specificatie voor een voorbeeld waar ze verschillen), maar - tenminste op WDQS - meestal een beetje langzamer.

Uw belangrijkste referentie voor deze en andere onderwerpen is de SPARQL specificatie.

U kunt ook een kijkje nemen op het SPARQL tutorial op Wikibooks en deze tutorial van data.world.

En natuurlijk ontbreken er ook nog enkele delen van Wikidata, zoals referenties, numerieke precisie (100±2,5), waarden met eenheden (twee kilogram), coördinaten, sitelinks, verklaringen over eigenschappen en meer. U kunt zien hoe die zijn gemodelleerd als tripels: RDF dumpformaat.

Zie ook