Wikidata:SPARQL tutorial/nl
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:
- Wikidata is een kennisdatabase. Het bevat miljoenen verklaringen, zoals "de hoofdstad van Canada is Ottawa", of "de Mona Lisa is in olieverf op populierenhout geschilderd", of "goud smelt op 1.064,18 graden Celsius".
- SPARQL is een taal voor het formuleren van een query (of meerdere queries) voor kennisdatabases. Met de juiste database kan een SPARQL-navraag vragen beantwoorden als "wat is de meest populaire toon in muziek?" of "welk personage werd door de meeste acteurs afgebeeld?" of "wat is de verdeling van bloedgroepen?" of welke werken van auteurs zijn dit jaar in het publieke domein gekomen?
- WDQS, de Wikidata Query Service, brengt de twee samen: u voert een SPARQL-query in, het wordt uitgevoerd op de dataset van Wikidata en laat het resultaat zien.
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.
}
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]". }
}
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]". }
}
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]". }
}
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]". }
}
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]". }
}
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]". }
}
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]". }
}
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]". }
}
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]". }
}
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]". }
}
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:
- We nemen niet langer items op die rechtstreeks voorbeelden van kunstwerken zijn.
- 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]". }
}
(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]". }
}
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]". }
}
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):
- oil paint (Q296955), het hoofdmateriaal;
- poplar wood (Q291034), met de kwalificatie applies to part (P518)painting support (Q861259) – het materiaal waarop de Mona Lisa is geschilderd; en
- 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]". }
}
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 LIMIT
gebruiken 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
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.
Tip |
---|
De relevante items en eigenschappen zijn Arthur Conan Doyle (Q35610), author (P50). |
Voorbeeld oplossing |
---|
SELECT ?book ?bookLabel
WHERE
{
?book wdt:P50 wd:Q35610.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
|
Chemische elementen
Schrijf een query die alle chemische elementen retourneert met hun elementsymbool en atoomnummer, in volgorde van hun atoomnummer.
Tip |
---|
De relevante items en eigenschappen zijn chemical element (Q11344), element symbol (P246), atomic number (P1086). |
Voorbeeld oplossing |
---|
SELECT ?element ?elementLabel ?symbol ?number
WHERE
{
?element wdt:P31 wd:Q11344;
wdt:P246 ?symbol;
wdt:P1086 ?number.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
ORDER BY ?number
|
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...)
Tip |
---|
De relevante items en eigenschappen zijn Mississippi River (Q1497), mouth of the watercourse (P403). |
Voorbeeld oplossing |
---|
SELECT ?river ?riverLabel
WHERE
{
?river wdt:P403 wd:Q1497.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
|
Rivers die in de Mississippi stromen II
Schrijf een query die alle rivieren geeft die direct of indirect in de Mississippi rivier stromen.
Tip |
---|
Deze query is bijna identiek aan de vorige. Het verschil is dat u deze keer een pad nodig hebt in plaats van een tripel. (Als u het gedeelte over paden hebt overgeslagen, sla deze oefening dan ook over.) |
Voorbeeld oplossing |
---|
SELECT ?river ?riverLabel
WHERE
{
?river wdt:P403+ wd:Q1497.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
|
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]". }
}
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]". }
}
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]". }
}
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". }
}
- 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
enfalse
. 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]". }
}
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. ")).
}
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. ")).
}
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]"))
}
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,}$"))
}
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]". }
}
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]". }
}
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]". }
}
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).
}
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]". }
}
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
(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
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 ].
}
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
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
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
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)
Als oefening doen we ook de andere queries.
Wapens per fabrikant
Wat is het totale aantal geweren dat door elke fabrikant wordt geproduceerd?
Tip |
---|
De relevante items en eigenschappen zijn firearm (Q12796), manufacturer (P176), total produced (P1092). |
Voorbeeld oplossing |
---|
SELECT ?manufacturer ?manufacturerLabel (SUM(?produced) AS ?totalProduced)
WHERE
{
?model wdt:P31?/wdt:P279* wd:Q12796;
wdt:P176 ?manufacturer;
wdt:P1092 ?produced.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?manufacturer ?manufacturerLabel
ORDER BY DESC(?produced)
|
Uitgevers per aantal pagina's
Wat is het gemiddelde (functie: AVG
) aantal pagina's van boeken per uitgever?
Tip |
---|
De relevante items en eigenschappen zijn publisher (P123), number of pages (P1104). |
Voorbeeld oplossing |
---|
SELECT ?publisher ?publisherLabel (AVG(?pages) AS ?avgPages)
WHERE
{
?book wdt:P123 ?publisher;
wdt:P1104 ?pages.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?publisher ?publisherLabel
ORDER BY DESC(?avgPages)
|
HAVING
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)
Samenvatting van aggregatie functies
Hier is een korte samenvatting van de beschikbare functies om te aggregeren:
COUNT
: Telt het aantal elementen. U kunt ookCOUNT(*)
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)
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)
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". }
}
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". }
}
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". }
}
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.