Wikidata:SPARQL query service/query optimization/nl

This page is a translated version of the page Wikidata:SPARQL query service/query optimization and the translation is 100% complete.

Query optimalisatie probeert de meest efficiënte manier te vinden om een bepaalde query uit te voeren uit de verschillende mogelijke opties. Blazegraph heeft een ingebouwde query optimizer die vaak goed werkt. Soms is het echter niet zo succesvol; in dergelijke gevallen moeten de queries mogelijk handmatig worden geoptimaliseerd.

Optimalisatiestrategieën

 
Query Optimalisatie (symbolische afbeelding)

Vaste waarden en bereiken

Het zoeken naar vaste waarden, bijvoorbeeld wdt:P31 wd:Q5, is de goedkoopste optie in de query service, maar het zoeken naar een waardenbereik is ook meestal vrij efficiënt en heeft vaak een voorkeur boven andere opties. Bijvoorbeeld, bij het zoeken naar mensen die in 1978 zijn geboren, is FILTER(YEAR(?dateOfBirth) = 1978) niet zo efficiënt als het volgende:

SELECT ?item ?dateOfBirth WHERE {
  ?item wdt:P31 wd:Q5;
        wdt:P569 ?dateOfBirth.
  FILTER("1978-00-00"^^xsd:dateTime <= ?dateOfBirth &&
         ?dateOfBirth < "1979-00-00"^^xsd:dateTime)
}
Try it!

U kunt dit verder optimaliseren door de query service te informeren dat wdt:P569 een bereikveilig predicaat is:

SELECT ?item ?dateOfBirth WHERE {
  ?item wdt:P31 wd:Q5;
        wdt:P569 ?dateOfBirth. hint:Prior hint:rangeSafe true.
  FILTER("1978-00-00"^^xsd:dateTime <= ?dateOfBirth &&
         ?dateOfBirth < "1979-00-00"^^xsd:dateTime)
}
Try it!

Dit stelt de optimizer in kennis dat ?dateOfBirth geen data, teksten, integers of andere gegevenstypen mengt, wat de vergelijking van het bereik vereenvoudigt. Dit is bijna altijd waar op Wikidata, dus de belangrijkste reden om deze optimalisatie-inleiding niet altijd te gebruiken is dat het een beetje ongemakkelijk is. (Het is waarschijnlijk ook niet goed om het te gebruiken als u werkt met onbekende waarden.)

Eigenschap paden

De optimizer gaat er van uit dat eigenschapspaden duurder zijn in gebruik dan eenvoudig zoeken op waarden, en dat is gewoonlijk correct: probeer een paar eenvoudige tripels toe te voegen om de hoeveelheid waarden te verminderen die tegen het pad moeten worden gecontroleerd. Soms kunnen paden echter vrij efficiënt zijn, en het is goed om de optimizer daarover te informeren. Denk bijvoorbeeld aan de volgende query voor alle gemeenten in de Duitse staat Niedersachsen:

SELECT ?item ?itemLabel WHERE {
  ?item wdt:P31/wdt:P279* wd:Q262166;
        wdt:P131+ wd:Q1197.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Try it!

Zoals geschreven, zal dit uitlopen omdat de query service zal proberen te beginnen met alle instance of (P31)municipality in Germany (Q262166) (inclusief subclasses) voordat ze worden beperken tot located in the administrative territorial entity (P131)Lower Saxony (Q1197) (recursief). Het is veel efficiënter om het andersom te doen.

SELECT ?item ?itemLabel WHERE {
  hint:Query hint:optimizer "None".
  ?item wdt:P131+ wd:Q1197;
        wdt:P31/wdt:P279* wd:Q262166.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Try it!

U kunt ook doorgeven in welke richting een pad moet worden overgestoken. Stel dat u bijvoorbeeld wilt testen of Moby-Dick (Q174596) een creative work (Q17537576) is:

ASK {
  wd:Q174596 wdt:P31/wdt:P279* wd:Q17537576.
}
Try it!

Dit duurt enkele seconden omdat de optimizer besluit om te beginnen bij creative work (Q17537576) en de links subclass of (P279) naar achteren te doorlopen. U kunt aangeven om de andere kant te gaan, te beginnen bij Moby-Dick (Q174596), na een instance of (P31) link en vervolgens de subclass of (P279) link naar voren te doorlopen:

ASK {
  wd:Q174596 wdt:P31/wdt:P279* wd:Q17537576.
  hint:Prior hint:gearing "forward".
}
Try it!

Dit is veel efficiënter. Om de andere kant te gaan, gebruik "reverse" in plaats van "forward"

Omgekeerde eigenschap paden

Een andere techniek om de optimizer op te dragen hoe te werken bij het leveren van resultaten is het omkeren van het in de query gebruikte eigenschapspad; mogelijk bijzonder nuttig bij het behandelen van tijdsbestekken van rapporten met wdt:P31/wdt:P279* constructies. Denk aan de volgende voorbeelden - de eerste optie geeft een time-out, de tweede werkt snel:

# Museums in Northern Ireland - TIMES OUT
SELECT distinct ?museum ?museumLabel WHERE {
  ?museum wdt:P131* wd:Q26 .
  ?museum wdt:P31/wdt:P279* wd:Q33506 .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Try it!
# Museums in Northern Ireland - SUCCEEDS
SELECT distinct ?museum ?museumLabel WHERE {
  ?museum wdt:P131* wd:Q26 .
  wd:Q33506 ^wdt:P279*/^wdt:P31 ?museum .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Try it!

Meer informatie over het eigenschapspad vindt u in SPARQL-standaard.

Verbeteren volgorde

We hebben de optimizer al een paar keer uitgeschakeld, toen het de query verkeerd probeerde te herorganiseren. U kunt ook een beetje meer precisie controle toepassen op de optimizer door hem te informeren welke verbindingen het het eerst of het laatste moet uitvoeren, met behulp van de tips hint:Prior hint:runFirst true of hint:Prior hint:runLast true.

Een belangrijk punt om te onthouden is dat deze de eerste of laatste join controleren, wat min of meer de . tussen twee tripels is - niet een tripel rechtstreeks. U kunt deze hint niet achter de eerste tripel in een groep zetten, noch (denk ik) na een tripel met een eigenschapspad. Zie ook de delen over benoemde subqueries hieronder voor meer opties om de volgorde van de uitvoering van de query te controleren.

Aanroep van de label service uitstellen

De label service (SERVICE wikibase:label { }) kan queries veel minder efficiënt maken - bij het werken met een dure query is het vaak een goed idee om de service eerst uit te schakelen, de querry zoveel mogelijk te optimaliseren en dan weer aan te zetten. Met wat geluk zal de query efficiënt genoeg zijn dat de service nu geen time-out meer krijgt.

Als de query zonder de service van het label werkt, maar bij toevoeging een time-out geeft, kan het helpen om het grootste deel van de query in een subquery te doen en de service van de label alleen aan het einde toe te passen. Dit kunt u doen met een gewone SPARQL-subquery:

SELECT ?foo ?fooLabel ?bar ?barLabel  WHERE {
  { # Wrapper voor subquery zonder label
    SELECT ?foo ?bar  WHERE {
      
    }
  }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Try it!

Dit kan vooral belangrijk zijn als u LIMIT gebruikt om snel resultaten te krijgen van een query die anders erg duur zou zijn.

De label service probeert de laatste in uitvoering te zijn, wat betekent dat Blazegraph probeert "alle" resultaten van een query te realiseren voordat hij de labels toevoegt, en pas dan de LIMIT toepast. Dit kan worden vermeden als het dure deel van de query in een subquery wordt geplaatst, zoals hierboven, en de LIMIT in de subquery is geplaatst. De labels worden dan alleen voor die paar resultaten opgezocht.

Een andere manier om het gebruik van de label service te verminderen is met rdfs:label. Om alle eiwitten te krijgen en hun labels:

SELECT ?item ?itemLabel WHERE {
  ?item wdt:P31 wd:Q8054 .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en" . }
}
Try it!

will timeout because 1 million hits the limit for the label service. Instead get all proteins with labels:

SELECT ?item ?itemLabel WHERE {
  ?item wdt:P31 wd:Q8054 .
  ?item rdfs:label ?itemLabel. FILTER( LANG(?itemLabel)="en" )
}
Try it!

Merk op dat dit alleen de items zal geven die een Engels label hebben.

Benoemde subqueries

Zoals hieronder vermeld, probeert de optimizer soms de hele query te optimaliseren over subqueries, dus het plaatsen van een deel van de query in een subquery helpt niet altijd. U kunt dit oplossen door een extensie BlazeGraph te gebruiken, genaamd named subqueries:

SELECT ?item ?itemLabel 

WITH {
  SELECT ?item  WHERE {
     
  }
} AS %results 

WHERE {
  INCLUDE %results.
     
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Try it!

De subqueries met naam worden slechts één keer uitgevoerd, op eigen kracht, wat we hier precies willen.

Verder gebruik van benoemde subqueries

Het bovenstaande voorbeeld gebruikt slechts één subquery, maar u kunt elk aantal van hen gebruiken. Dit kan nuttig zijn om een algemene volgorde of structuur op de query te leggen, terwijl de optimizer de instructies binnen elke subquery nog steeds opnieuw kan ordenen zoals hij het best weet.

U kunt de optimizer ook alleen in één subquery uitschakelen, terwijl u hem de rest van de query laat optimaliseren: hiervoor moet u hint:SubQuery hint:optimizer "None" in die subquery in plaats van de gebruikelijke hint:Query hint:optimizer "None". vermelden

Zie deze pagina voor een voorbeeld met behulp van deze techniek.

Benoemde subqueries zijn ook handig wanneer u de resultaten meerdere keren wilt groeperen, of dezelfde subquery-resultaten meer dan één keer in verschillende contexten wilt gebruiken - u kunt de dezelfde subqueries met dezelfde naam in meerdere andere opnemen, indien nodig (en deze zal slechts één keer worden uitgevoerd). Zie deze pagina voor een voorbeeld hiervan.

Labels zoeken

Als u alle items wilt vinden die een bepaald woord hebben, zeg "Frankfurt" in een label, dan kunt u een query als deze proberen:

SELECT ?item ?label
WHERE
{
  ?item rdfs:label ?label.
  FILTER CONTAINS(?label, "Frankfurt")
}
Try it!

Maar die query zal uitlopen omdat de zoekmachine honderden miljoenen labels zou moeten lezen om het woord te vinden. Maar de normale zoekfunctie van Wikidata heeft alle woorden in de labels echter geïndexeerd en kan de opdracht zeer snel uitvoeren. Het is mogelijk voor een query om toegang te krijgen tot de zoekfunctie van Wikidata met de MediaWiki API Query Service De onderstaande query vindt ook items met het woord "Frankfurt" in een label, maar gebruikt de MediaWiki API, en kan daarom zonder time-out worden uitgevoerd:

SELECT ?item ?label
WHERE
{
  SERVICE wikibase:mwapi
  {
    bd:serviceParam wikibase:endpoint "www.wikidata.org";
                    wikibase:api "Generator";
                    mwapi:generator "search";
                    mwapi:gsrsearch "inlabel:Frankfurt";
                    mwapi:gsrlimit "max".
    ?item wikibase:apiOutputItem mwapi:title.
  }
  ?item rdfs:label ?label.
  FILTER CONTAINS(?label, "Frankfurt")
}
Try it!

Met MWAPI-zoeken kan ook in geselecteerde talen worden gezocht naar labels, en verklaringen en kwalificaties en meer zoeken. Zie de beschrijving van de zoekopties.

Gebruik COUNT (*) wanneer het mogelijk is, en snel bereik telt

Als u wilt weten hoeveel mensen er in Wikidata zijn vermeld, kunt u deze query stellen:

SELECT (COUNT(?item) AS ?count)
{
  ?item wdt:P31 wd:Q5 .
}
Try it!

Het lijkt simpel, maar het duurt lang om uit te voeren. De SPARQL-engine moet controleren of ?item gebonden is en een foutloos waarde heeft van meer dan 8 miljoen keer, dus de query duurt meestal 20-30 seconden om te worden uitgevoerd. Maar we weten dat ?item altijd gebonden is en dat de waarden altijd items zijn, dus het is beter om gewoon het aantal resultaten zonder voorwaarden te tellen met deze query:

SELECT (COUNT(*) AS ?count)
{
  ?item wdt:P31 wd:Q5 .
}
Try it!

Hier hoeft de engine niet elk resultaat te onderzoeken om de telling te maken, en de query kost minder dan een seconde om uitgevoerd te worden.

Hoewel het tellen van het aantal resultaten sneller is dan het tellen of het aantal van een specifieke binding in het algemeen, is de reden dat dit voorbeeld bijzonder snel is, dat de optimizer een telling over een enkel tripelpatroon zal herschrijven om snel bereik tellen te gebruiken. Deze optimalisatie zal niet werken als u een set probeert te tellen die bestaat uit meer dan één enkel tripelpatroon.

Verschillende voorwaarden scan, en groeperingen en count optimisatie

Als u probeert om unieke predicaten te vermelden als deze, zal de uitvoering eindigen in een time-out.

SELECT ?p
WHERE { [] ?p [] . }
GROUP BY ?p
Try it!

Als u deze query echter enigszins wijzigt, wordt deze door de optimizer opnieuw geschreven om een afzonderlijke term te gebruiken, wat vrij snel is. Dit werkt alleen op een enkel tripel patroon.

SELECT DISTINCT ?p
WHERE { [] ?p [] . }
Try it!

Er is ook een eenvoudige groep door en met tel optimalisatie die zowel de verschillende term scan en de snel bereik met tel optimalisering, de laatste voor elk predicaat zal combineren. Dit is een bliksemsnelle manier om alle verschillende predicaten te tellen. Als u alleen een subset van de predicaten nodig hebt, is het waarschijnlijk het snelst om hiermee te beginnen, het onveranderd in een subquery te wrappen, en vervolgens de set te reduceren tot wat u wilt.

SELECT ?p (COUNT(?p) AS ?count)
WHERE { [] ?p [] . }
GROUP BY ?p
Try it!

Deze optimalisatie moet werken voor elk onderdeel van het tripel.

SELECT ?o (COUNT(*) as ?count)
WHERE { ?s wdt:P31 ?o }
GROUP BY ?o
Try it!


Tellen grootte van grote subsets van intersecties met WikibaseCirrusSearch

Blazegraph slaagt vaak niet bij het tellen van de grootte van intersecties van enorme subsets, bijvoorbeeld is het onmogelijk om het aantal mensen te tellen zonder geslachtseigenschappen met alleen SPARQL-gebaseerde optimalisaties. Echter met gebruik van MWAPI en WikibaseCirrusSearch, werkt het extreem snel.

SELECT ?label ?count WHERE {
  {
    BIND("People without gender" AS ?label)
    SERVICE wikibase:mwapi {
      bd:serviceParam wikibase:endpoint "www.wikidata.org";
                      wikibase:api "Search"; wikibase:limit "once" ;
                      mwapi:srsearch "haswbstatement:P31=Q5 -haswbstatement:P21" ;
                      mwapi:srlimit "1" ; mwapi:srprop "" ; mwapi:srsort "none" ; mwapi:srnamespace "0" .
      ?count wikibase:apiOutput '//searchinfo[1]/@totalhits'.
    }
  } UNION {
    BIND("Male" AS ?label)
    SERVICE wikibase:mwapi {
      bd:serviceParam wikibase:endpoint "www.wikidata.org";
                      wikibase:api "Search"; wikibase:limit "once" ;
                      mwapi:srsearch "haswbstatement:P31=Q5 haswbstatement:P21=Q6581097" ;
                      mwapi:srlimit "1" ; mwapi:srprop "" ; mwapi:srsort "none" ; mwapi:srnamespace "0" .
      ?count wikibase:apiOutput '//searchinfo[1]/@totalhits'.
    }
  } UNION {
    BIND("Female" AS ?label)
    SERVICE wikibase:mwapi {
      bd:serviceParam wikibase:endpoint "www.wikidata.org";
                      wikibase:api "Search"; wikibase:limit "once" ;
                      mwapi:srsearch "haswbstatement:P31=Q5 haswbstatement:P21=Q6581072" ;
                      mwapi:srlimit "1" ; mwapi:srprop "" ; mwapi:srsort "none" ; mwapi:srnamespace "0" .
      ?count wikibase:apiOutput '//searchinfo[1]/@totalhits'.
    }
  } UNION {
    BIND("Other" AS ?label)
    SERVICE wikibase:mwapi {
      bd:serviceParam wikibase:endpoint "www.wikidata.org";
                      wikibase:api "Search"; wikibase:limit "once" ;
                      mwapi:srsearch "haswbstatement:P31=Q5 haswbstatement:P21 -haswbstatement:P21=Q6581097 -haswbstatement:P21=Q6581072" ;
                      mwapi:srlimit "1" ; mwapi:srprop "" ; mwapi:srsort "none" ; mwapi:srnamespace "0" .
      ?count wikibase:apiOutput '//searchinfo[1]/@totalhits'.
    }
  }
}
Try it!

Services

GAS Service

De Gather, Apply en Scatter (GAS) service biedt 'graph traversal', 'graph mining' en soortgelijke algoritmenclasses voor SPARQL. In praktische termen, het maakt het mogelijk een reeks relaties te volgen via de grafiek; bijvoorbeeld de keten van vader, grootvader, overgrootvader &c - het traceren van de lijn van een onderwerp, of een keten van verbonden spoorwegstations via adjacent station (P197) verklaringen. Het is hier en hier gedocumenteerd.

In wezen gebruikt GAS twee parameters: gas:in wd:Qnnn - het knooppunt of het item waar van te beginnen en gas:linkType wdt:Pnnn - de eigenschap die moet worden gevolgd. Het geeft gas:out - het volgende item; gas:out1 - het aantal hops van het invoer item en gas:out2 - het item dat direct voorafgaat aan het gas:out item in de gevraagde keten.

# Male lineage of Elizabeth II (Q9682)
SELECT ?father ?fatherLabel ?child ?childLabel ?depth WHERE
 {
  SERVICE gas:service {
       gas:program gas:gasClass "com.bigdata.rdf.graph.analytics.BFS" ; gas:in wd:Q9682 ; gas:linkType wdt:P22 ; gas:out ?father ; gas:out1 ?depth ; gas:out2 ?child . }

SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
} order by ?depth
Try it!
# UK railway stations connected from Topsham railway station (Q1990205) 
#defaultView:Map{"hide":["?cds","?line","?layer"],"layer":"?trackLabel"}
SELECT ?station ?stationLabel ?cds ?line ?trackLabel ?track ?image {
{SELECT * {
  SERVICE gas:service {
       gas:program gas:gasClass "com.bigdata.rdf.graph.analytics.BFS" ; gas:in wd:Q1990205 ; gas:linkType wdt:P197 ; gas:out ?station ; gas:out1 ?depth ; gas:out2 ?pred . }
  FILTER(?station != wd:Q1990205)
                      ?station wdt:P625 ?cds ;
                               wdt:P17 wd:Q145 .
                             
  ?station p:P197 ?adjST .
  ?adjST ps:P197 ?adj ;
                pq:P81 ?track .

  ?adj p:P625/psv:P625/wikibase:geoLatitude ?lat1 ; p:P625/psv:P625/wikibase:geoLongitude ?lon1 .
  ?station p:P625/psv:P625/wikibase:geoLatitude ?lat2 ; p:P625/psv:P625/wikibase:geoLongitude ?lon2 .
  BIND(CONCAT('LINESTRING(', STR(?lon1), ' ', STR(?lat1), ',', STR(?lon2), ' ', STR(?lat2), ')') AS ?str) . BIND(STRDT(?str, geo:wktLiteral) AS ?line)}
 
}
  OPTIONAL { ?station wdt:P18 ?image .}
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
Try it!

Het is mogelijk om naar omgekeerde paden van een onderwerp te kijken, bijvoorbeeld mensen in een matrilineale (vrouwelijke) lijn die afstammen van Elizabeth II (Q9682), door de parameter gas:traversalDirection "Reverse" toe te voegen.

# Female lines descended from Elizabeth II (Q9682)
SELECT ?child ?childLabel ?mother ?motherLabel ?depth WHERE
 {
  SERVICE gas:service {
       gas:program gas:gasClass "com.bigdata.rdf.graph.analytics.BFS" ; gas:in wd:Q9682 ; gas:linkType wdt:P25 ; gas:traversalDirection "Reverse"; gas:out ?child ; gas:out1 ?depth ; gas:out2 ?mother . 
  }

SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
} order by ?depth
Try it!

Het is ook mogelijk om beide richtingen te volgen, van en naar het item, met behulp van gas:traversalDirection "Undirected", hoewel dit, zoals in het voorbeeld hieronder, resulteert in verwarring in de kolommen ouder & kind.

# Female line leading to, and descended from, Elizabeth II (Q9682)
SELECT ?child ?childLabel ?mother ?motherLabel ?depth WHERE
 {
  SERVICE gas:service {
       gas:program gas:gasClass "com.bigdata.rdf.graph.analytics.BFS" ; gas:in wd:Q9682 ; gas:linkType wdt:P25 ; gas:traversalDirection "Undirected"; gas:out ?child ; gas:out1 ?depth ; gas:out2 ?mother . 
  }

SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
} order by ?depth
Try it!

De documentatie bespreekt enkele extra parameters en GAS-algoritmen.

Automatische optimalisatie -- overzicht achtergronden

De basisaanpak van queryoptimalisatie is om te proberen de oplossingsset zo snel mogelijk zo klein mogelijk te maken in de uitvoering van de query, om het werk dat nodig is voor volgende joins zo klein mogelijk te maken. Een volgend doel kan zijn om de mogelijkheid van leidingen en parallelisering te maximaliseren. Dit is wat de ingebouwde optimizer van Blazegraph probeert te bereiken.

Hier is bijvoorbeeld een query om een lijst te geven van Amerikaanse presidenten en hun echtgenoten:

SELECT ?pres ?presLabel ?spouse ?spouseLabel WHERE {
   ?pres wdt:P31 wd:Q5 .
   ?pres wdt:P39 wd:Q11696 .
   ?pres wdt:P26 ?spouse .
   SERVICE wikibase:label {
    bd:serviceParam wikibase:language "en" .
   }
 }
Try it!

De details van hoe Blazegraph een bepaalde query heeft uitgevoerd (en waarom) kunnen worden verkregen via de EXPLAIN-modus van de zoekmachine. Dit kan worden gedaan door het vervangen van het volgende aan het begin van de URL van de query-editor, :https://query.wikidata.org/# met :https://query.wikidata.org/bigdata/namespace/wdq/sparql?explain&query= om de query rechtstreeks naar het SPARQL-endpoint te sturen (niet via de query editor), met een extra explain& in de URL vóór de query=-string.

De resulterende URL, die de bovenstaande query rechtstreeks naar het eindpunt stuurt, met de optie 'explain', produceert dit rapport over de query en de optimalisatie ervan.

Standaard wordt een query uitgevoerd van boven naar beneden, met oplossings-sets gevoegd in de gegeven volgorde. Dit wordt in het verslag aangetoond als het "Original AST" plan. De query-engine schatte echter (per september 2015) in dat er

Het concludeert daarom dat de meest efficiënte manier om de query te doen, in plaats van de gespecificeerde volgorde, is om eerst de presidenten (76) te vinden, dan te kijken hoeveel van hen echtgenoten (51 oplossingen) hebben, dan te zien hoeveel mensen (47 oplossingen), voordat deze oplossing wordt verzonden naar de label service. Deze opbouw is typisch voor een succesvolle optimalisatie.

Een query die moeilijkheden bevat

De volgende query probeert alle op de website van "Le Figaro" verwezen verklaringen te vinden en hun onderwerp, predicaat en object terug te geven. Maar zoals het nu is geschreven, komt er een time-out.

SELECT ?statement ?subject ?subjectLabel ?property ?propertyLabel ?object ?objectLabel ?refURL WHERE {
  ?statement prov:wasDerivedFrom ?ref .
  ?ref pr:P854 ?refURL 
  FILTER (CONTAINS(str(?refURL),'lefigaro.fr')) .       
  ?subject ?p ?statement .
  ?property wikibase:claim ?p .
  ?property wikibase:statementProperty ?ps .
  ?statement ?ps ?object
  SERVICE wikibase:label {
    bd:serviceParam wikibase:language "en" .
  }
}
Try it!

De reden hiervoor kan worden onderzocht door te kijken naar het rapport explain voor de query.

Het lijkt erop dat de optimizer wordt verleid door de lage cardinaliteit van de verklaringen ?property wikibase:claim ?p en ?property wikibase:statementProperty ?ps, met slechts 1756 eigenschappen die items als hun objecten nemen, zonder te anticiperen dat (i) de tweede voorwaarde niets zal doen om de oplossing van de eerste voorwaarde te verminderen; en (ii) deze voorwaarden zullen zeer weinig doen om de cardinaliteit te verminderen van de verklaring ?subject ?p ?statement (764.223.907), die de optimizer voorstelt te joinen voordat hij naar ?statement prov:wasDerivedFrom ?ref en dan ?ref pr:P854 ?refURL kijkt.

De optimizer kan ook proberen de test van de verklaring FILTER (CONTAINS(str(?refURL),'lefigaro.fr')) zo lang mogelijk uit te stellen, omdat dit de materialisatie van de werkelijke stringwaarden vereist, in plaats van uitsluitend te gaan met de joins op basis van of items in de verklaringen bestaan of niet.

Update: in 2020 zijn er meer dan 47.000.000 url referenties in Wikidata beschikbaar. Omdat Blazegraph in de huidige configuratie geen volledige tekstindexering voor tekstwaarden uitvoert, is er geen manier om een snelle "native" query te maken zonder extra filters toe te voegen (bijv. "zoek alle verklaringen die naar de "Le Figaro" verwijzen, maar alleen "voor presidenten"). Gelukkig is er een out-of-the-box oplossing die betrekking heeft op MWAPI. NB deze query is iets anders dan het oorspronkelijke verzoek: het geeft links terug voor domeinen die overeenkomen met *.lefigaro.fr, het zal geen links zoals http://web.archive.org/web/20200904002919/http://www.lefigaro.fr/ vinden. Deze query ziet er zo uit:

SELECT ?statement ?subject ?subjectLabel ?property ?propertyLabel ?object ?objectLabel ?refURL WITH
{
  SELECT DISTINCT ?subject WHERE {
    {
      SERVICE wikibase:mwapi {
        bd:serviceParam wikibase:endpoint "www.wikidata.org"; wikibase:api "Generator"; mwapi:generator "exturlusage"; mwapi:geulimit "500"; mwapi:geuquery "*.lefigaro.fr"; mwapi:geuprotocol "http"; mwapi:geunamespace "0" .
        ?title wikibase:apiOutput mwapi:title.
      }
    } UNION {
      SERVICE wikibase:mwapi {
        bd:serviceParam wikibase:endpoint "www.wikidata.org"; wikibase:api "Generator"; mwapi:generator "exturlusage"; mwapi:geulimit "500"; mwapi:geuquery "*.lefigaro.fr"; mwapi:geuprotocol "https"; mwapi:geunamespace "0" .
        ?title wikibase:apiOutput mwapi:title.
      }
    }
    BIND(IRI(CONCAT(STR(wd:), ?title)) AS ?subject)
  }
} AS %items {
  INCLUDE %items .
  
  hint:Query hint:optimizer "None".
  
  ?subject ?p ?statement .
  ?statement prov:wasDerivedFrom/pr:P854 ?refURL .
  FILTER (CONTAINS(str(?refURL), 'lefigaro.fr')) .
  
  ?property wikibase:claim ?p .
  ?property wikibase:statementProperty ?ps .
  ?statement ?ps ?object .
  
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
}
Try it!

Andere optimalisatiemodus

Blazegraph heeft een aantal andere richtlijnen voor de optimalisatie, waaronder de duurdere "looptijdoptimalisatie", die gebaseerd is op steekproefneming om de waarschijnlijke ontwikkeling van de grootte van de oplossing die door een bepaalde reeks joins wordt bepaald, nauwkeuriger te schatten. De volgorde van de joins kan dan worden aangegeven en dus afgedwongen.

Echter, bij het onderzoeken van het overeenkomstige query report blijkt dat de runtime optimalisatie is voortgegaan met dezelfde query sequentie als de statische optimalisatie, en vergelijkbare time-outs geeft.

Wat de "runFirst"-directive betreft, heb ik nog niet kunnen uitzoeken hoe deze moet worden toegepast.

Andere queries waar de optimizer het moeilijk mee heeft

Prestaties nemen sterk af wanneer het aantal mogelijk belangrijke records een bepaalde drempel overschrijdt

Dit is ontdekt na een query om mensen te vinden met blijkbaar overeenkomende geboortedatums en sterfdatums, voor mensen uit een bepaald land

  • Frankrijk (N=125.223): tinyurl.com/obxk9av -- rapport (gelukt)
  • Duitsland (N=194.098): tinyurl.com/pwthc9d rapport (time-out)

Wat hier verkeerd gaat, gebeurt wanneer het aantal items met de gegeven waarde van country of citizenship (P27), bijvoorbeeld 194.098 voor Duitsland, de items (tijd-waarde nodes) het aantal items overschrijdt met wikibase:timePrecision "11"^^xsd:integer (169.902).

Voor Frankrijk is er met N=125.223 een gestage afname in de grootte van het resultaat: het vereisen van een datum van overlijden, dan een dag-specifieke precisie, dan een geboortedatum, dan een datum-specificieke precisie brengt dit aantal tot 48.537 terug, wat vervolgens wordt verminderd tot 10 door het vereisen dat de geboortedatum en de datum van overlijden overeenkomen (een zeer snelle operatie).

Voor Duitsland probeert de optimizer echter met N=194.098 te beginnen met de 169.902 datum-nodes met dag-specifieke data; maar de query explodeert toen het systeem deze probeert te koppelen aan alle mensen met dergelijke dag-specialische geboortedatums (1.560.806), een aantal dat vermindert wanneer het nationaliteitsfilter in gang komt; maar dat toch de beschikbare zoektijd overschrijdt.

(Nadat we de prestatiegegevens hebben bekeken en gezien hoe snel Blazegraph alles tegen alles kan matchen voor heel grote sets, blijkt dat het systeem snel genoeg is om dit soort queries voor alles uit te voeren -- zie tinyurl.com/pn9xyye -- door eerst te matchen op datum, wat de grootte van het resultaat kleiner maakt, en niet tot dan de vereiste controleert dat de datum specifiek is).

Het toevoegen van een naam-wrapper en naamlabels-wrapper aan een query voor levensgebeurtenissen veroorzaakt een time-out

Het toevoegen van een schijnbaar triviale wrapper, om de naam en het naamlabel te vinden, zorgt ervoor dat de vraag in het onmiddellijk vorige gedeelte wordt gedaan wat tot een time-out leidt, zelfs wanneer zonder de wrapper het zonder moeite werd uitgevoerd.

  • Query zoals gepresenteerd: tinyurl.com/p8wzb74 -- rapport
  • Handmatige query: tinyurl.com/o7z95vb -- rapport (gelukt)
  • Vraag zonder de naam op te zoeken: tinyurl.com/obxk9av -- rapport (gelukt)

Met de outer-layer op zijn plaats begint de inner-layer (zelfs voor Frankrijk) door de geboorte-nodes die zijn verbonden met geboortedata te onttrekken en vervolgens die met precisie = 11 te bewaren.

De situatie kan worden opgelost door de optimizer uit te schakelen en de query handmatig te doen.

Het lijkt erop dat dit een algemeen kenmerk kan zijn van queries met subselect clausules: zowel hier als in de volgende query wil de optimizer de subquery starten met een vereiste die een van de variabelen bevat die naar de outer-layer wordt geprojecteerd.

Het toevoegen van een eigenschapslabels-wrapper aan een query voor eigenschappen veroorzaakt een time-out

  • Query zoals gepresenteerd: tinyurl.com/oex59qr -- rapport
  • Handmatige query: tinyurl.com/ppkmb3v -- rapport (gelukt)
  • Query zonder opzoeken eigenschap-label: tinyurl.com/qe4ukbx -- rapport (gelukt)

Hetzelfde als hierboven: voor de query met opzoeken van eigenschappen, wil de optimizer blijkbaar beginnen met alle tripels ?a ?p ?b en vervolgens de join ?b wdt:P31 wd:Q4167410 doen, in plaats van omgekeerd (wat hij met succes voor de kleinere query zonder opzoeken eigenschap doet).

Dit lijkt niet op een artefact veroorzaakt door BIND, noch door de volgorde waarin de clausules staan - zelfs als deze worden gewijzigd, loopt de query loopt nog steeds langzaam met de ingebouwde optimizer

  • Alternatieve query: tinyurl.com/q97a8sh -- rapport (mislukt nog steeds)

Het is nog steeds de voorkeur om te beginnen met de bewering ?a ?p ?b, hoewel dit minder specifiek is - blijkbaar omdat ?p een van de variabelen is die wordt geprojecteerd uit de interne SELECT-clausule.

Queries met FILTER NOT EXISTS

Om goed te werken, lijken queries met "filter not exists" vaak opnieuw te moeten worden geschreven.

Deze clausule lijkt vaak ondoeltreffend om onduidelijke redenen. Bijvoorbeeld een query als deze, met artikelen in frwiki die helemaal niet bestaat op enwiki geeft een time-out als de query de clausule "filter not exists" bevat

select ?articleFr ?item ?itemLabel ?some ?someLabel {
    select * {?articleFr schema:about ?item ;
             wikibase:badge ?some ;
             schema:isPartOf <https://fr.wikipedia.org/>
  
    filter not exists {
      ?articleEn schema:about ?item ;
               schema:isPartOf <https://en.wikipedia.org/>
    }
   }
}
Try it!

maar het is prima om te herschrijven met een 'optional' met filtering op een 'unbound' variabele

select ?articleFr ?item ?itemLabel ?some ?someLabel {
  {
    select * {?articleFr schema:about ?item ;
             wikibase:badge ?some ;
             schema:isPartOf <https://fr.wikipedia.org/>
  
      optional { ?articleEn schema:about ?item ;
               schema:isPartOf <https://en.wikipedia.org/> .
      }
      filter (!bound(?articleEn))
   }
  }
}
Try it!


Het gebruik van MINUS() werkt ook goed in dit geval. Deze drie methoden om resultaten te verwijderen doen allemaal iets vergelijkbaars, maar net iets anders. Er is geen enkele methode die consequent beter/slechter is, ze zullen allemaal anders presteren gezien de omstandigheden. Met andere woorden, u moet ze allemaal proberen als u aandacht aan de prestaties wilt besteden.

Vuistregels

Dit is mijn uitleg van de situatie:

  • FILTER NOT EXISTS moet een voorwaarde op elk resultaat van de subquery controleren. Deze voorwaarden worden in volgorde gecontroleerd, niet door de join
  • OPTIONAL brengt een extra tripel patroon aan met betrekking tot elk resultaat. Dit wordt gedaan door samen te werken, d.w.z. allemaal tegelijk. De FILTER NOT BOUND kan dan snel elke rij van dit uitgebreide resultaat controleren
  • MINUS berekent de tweede subquery en vindt vervolgens het vastgestelde verschil tussen de eerste en de tweede subquery. Als beide relatief klein zijn, kan het verschil snel worden bepaald

Een vuistregel is:

  • Gebruik FILTER NOT EXISTS wanneer de negatieve voorwaarde om te controleren een enorm resultaat oplevert, zodat de NOT EXISTS minder duur kan zijn
  • Gebruik OPTIONAL... FILTER NOT BOUND wanneer de negatieve voorwaarde het positieve resultaat met slechts enkele rijen verder uitbreidt (of dezelfde rijen behoudt)
  • Gebruik MINUS wanneer de negatieve toestand, als ze alleen wordt genomen, een klein resultaat oplevert.

De behandeling van queries in de cache

Deze post kan nuttig zijn voor mensen die willen zorgen dat queries vers worden uitgevoerd:

Korte samenvatting: om de cache van de vraag te uitschakelen, voeg de cache-control: no-cache header toe. Als alternatief kunt u gewoon de GET-methode vervangen door POST. De resultaten van POST-verzoeken worden nooit in de cache opgeslagen: als u een antwoord uit de cache wilt krijgen, schakel dan van POST naar GET.

Meer... ?

Voeg hier meer use-cases toe