Cheatsheets:SPARQL: Difference between revisions

From Wikibase
Jump to navigation Jump to search
Standardize: add copy button to block code snippets
 
(15 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{Cheatsheet}}
{{Cheatsheet}}


SPARQL 1.1 quick syntax reference — for people who already know what SPARQL is
If you are new to SPARQL, see [[FOSS:SPARQL|SPARQL101]].
and need reminders on syntax. All examples run as-is against the public
Wikidata endpoint <syntaxhighlight lang="text" inline>https://query.wikidata.org/sparql</syntaxhighlight>. Querying another Wikibase instance is similar, but small query adjustments may be necessary depending on how each instance is set up. See the documentation for your target instance for more info.


If you are new to SPARQL and would like a quick 101, see
<blockquote>
[https://www.wikidata.org/wiki/Wikidata:SPARQL_tutorial Wikidata SPARQL tutorial].
Examples featured in this cheatsheet run as-is against the public Wikidata endpoint <syntaxhighlight lang="text" inline>https://query.wikidata.org/sparql</syntaxhighlight>. Querying another Wikibase instance is similar, but small query adjustments may be necessary depending on how each instance is set up. See the documentation for your target instance for more info.
</blockquote>


== Running example: ==
== Basic example ==


Natural language question:
Natural language question:


<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
How many dogs and cats are featured in Wikidata?  
How many dogs and cats are featured in Wikidata?  
</syntaxhighlight>
</syntaxhighlight>
Line 18: Line 17:
SPARQL query:
SPARQL query:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?animal ?animalLabel (COUNT(?item) AS ?count) WHERE {
SELECT ?animal ?animalLabel (COUNT(?item) AS ?count) WHERE {
   VALUES ?animal { wd:Q144 wd:Q146 }        # dog, cat
   VALUES ?animal { wd:Q144 wd:Q146 }        # dog, cat
Line 29: Line 28:
Result as of 2026-08-19:
Result as of 2026-08-19:


<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
dog  553
dog  553
cat  239
cat  239
Line 36: Line 35:
== Building a query ==
== Building a query ==


A query is a list of '''triple patterns''':
A query is a list of '''triple patterns'''(
<syntaxhighlight lang="sparql" inline>subject predicate object .</syntaxhighlight>
<syntaxhighlight lang="sparql" inline>subject predicate object</syntaxhighlight>).
Knowing one or two elements, you can query the rest:
Knowing one or two elements, you can query the rest:


Line 49: Line 48:
| "What is known about Einstein?" || subject Einstein (wd:Q937) || <syntaxhighlight lang="sparql" inline>wd:Q937 ?predicate ?value .</syntaxhighlight>
| "What is known about Einstein?" || subject Einstein (wd:Q937) || <syntaxhighlight lang="sparql" inline>wd:Q937 ?predicate ?value .</syntaxhighlight>
|}
|}
Write the question in that order and the query writes itself:
<syntaxhighlight lang="sparql">
# "What is the population of France?"
SELECT ?population WHERE {
  wd:Q142 wdt:P1082 ?population .
}
</syntaxhighlight>
Result:
<syntaxhighlight lang="text">
68605616
</syntaxhighlight>
The three kinds of values a triple can hold, each shown in a query:
'''IRI''' — a resource: an entity (wd:Q142), a property (wdt:P1082), or
anything else. In the pattern above, France and its population property are
IRIs; the thing we don't know yet is a variable:
<syntaxhighlight lang="sparql">
SELECT ?population WHERE {
  wd:Q142 wdt:P1082 ?population .    # IRI  IRI  variable
}
</syntaxhighlight>
'''Literal''' — a value, not a resource: a typed number/date, or a string with
a language tag. Dog's English label is the literal <syntaxhighlight lang="sparql" inline>"dog"@en</syntaxhighlight>:
<syntaxhighlight lang="sparql">
SELECT ?label WHERE {
  wd:Q144 rdfs:label ?label .
  FILTER(LANG(?label) = "en")
}
</syntaxhighlight>
Result:
<syntaxhighlight lang="text">
dog
</syntaxhighlight>
Other literals: <syntaxhighlight lang="sparql" inline>"42"^^xsd:integer</syntaxhighlight>,
<syntaxhighlight lang="sparql" inline>"2026-08-19"^^xsd:date</syntaxhighlight>,
<syntaxhighlight lang="sparql" inline>"chien"@fr</syntaxhighlight>.
'''Blank node''' — "some unnamed thing", written <syntaxhighlight lang="sparql" inline>[]</syntaxhighlight> or
<syntaxhighlight lang="sparql" inline">_:x</syntaxhighlight>. Use it when a value exists but you don't care
what it is — here, dogs that have an image:
<syntaxhighlight lang="sparql">
SELECT ?dogLabel WHERE {
  ?dog wdt:P31/wdt:P279* wd:Q144 .
  ?dog wdt:P18 [] .          # has an image — any image will do
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
LIMIT 3
</syntaxhighlight>
Result:
<syntaxhighlight lang="text">
Diamond
Dog of Osu
Endal
</syntaxhighlight>
<blockquote>'''Gotcha:''' blank-node labels (<syntaxhighlight lang="sparql" inline>_:x</syntaxhighlight>) are local to one query — they are not IRIs and cannot be referenced across queries.</blockquote>
<blockquote>'''Gotcha:''' the prefixes <syntaxhighlight lang="sparql" inline>wd:</syntaxhighlight>/<syntaxhighlight lang="sparql" inline>wdt:</syntaxhighlight> are Wikidata's; other instances define their own (see [[Help:Contributing/query]]).</blockquote>


== Query forms ==
== Query forms ==
Line 135: Line 65:
'''SELECT''' — what is the population of France?
'''SELECT''' — what is the population of France?


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?population WHERE {
SELECT ?population WHERE {
   wd:Q142 wdt:P1082 ?population .
   wd:Q142 wdt:P1082 ?population .
Line 142: Line 72:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
68605616
68605616
</syntaxhighlight>
</syntaxhighlight>
Line 148: Line 78:
'''ASK''' — is there at least one dog in Wikidata?
'''ASK''' — is there at least one dog in Wikidata?


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
ASK WHERE { ?x wdt:P31 wd:Q144 . }
ASK WHERE { ?x wdt:P31 wd:Q144 . }
</syntaxhighlight>
</syntaxhighlight>


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
true
true
</syntaxhighlight>
</syntaxhighlight>
Line 160: Line 90:
triple <syntaxhighlight lang="sparql" inline>?x wdt:P31 wd:Q144</syntaxhighlight>:
triple <syntaxhighlight lang="sparql" inline>?x wdt:P31 wd:Q144</syntaxhighlight>:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
CONSTRUCT { ?x wdt:P31 wd:Q144 . }
CONSTRUCT { ?x wdt:P31 wd:Q144 . }
WHERE { ?x wdt:P31 wd:Q144 . } LIMIT 2
WHERE { ?x wdt:P31 wd:Q144 . } LIMIT 2
Line 166: Line 96:


Result (visualised, N-Triples style):
Result (visualised, N-Triples style):
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
Q186486  wdt:P31  wd:Q144
Q186486  wdt:P31  wd:Q144
Q280571  wdt:P31  wd:Q144
Q280571  wdt:P31  wd:Q144
Line 174: Line 104:
subject or object):
subject or object):


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
DESCRIBE wd:Q937
DESCRIBE wd:Q937
</syntaxhighlight>
</syntaxhighlight>


Result (visualised, N-Triples style, first few of thousands):
Result (visualised, N-Triples style, first few of thousands):
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
Q937  rdfs:label  "Albert Einstein"@en
Q937  rdfs:label  "Albert Einstein"@en
Q937  wdt:P569    1879-03-14
Q937  wdt:P569    1879-03-14
Line 188: Line 118:
== Solution modifiers ==
== Solution modifiers ==


Each modifier shown without / with, side by side; the highlighted line(s)
show what the modifier adds.


'''DISTINCT''' — drop duplicate rows. Einstein and Obama are both male, so the
'''DISTINCT''' — drop duplicate rows. Einstein and Obama are both male, so the
Line 198: Line 126:
|-
|-
|
|
<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?genderLabel WHERE {
SELECT ?genderLabel WHERE {
   VALUES ?person { wd:Q937 wd:Q76 }
   VALUES ?person { wd:Q937 wd:Q76 }
Line 207: Line 135:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
male
male
male
male
</syntaxhighlight>
</syntaxhighlight>
||
||
<syntaxhighlight lang="sparql" line highlight="1">
<syntaxhighlight lang="sparql" line highlight="1" copy>
SELECT DISTINCT ?genderLabel WHERE {
SELECT DISTINCT ?genderLabel WHERE {
   VALUES ?person { wd:Q937 wd:Q76 }
   VALUES ?person { wd:Q937 wd:Q76 }
Line 221: Line 149:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
male
male
</syntaxhighlight>
</syntaxhighlight>
Line 232: Line 160:
|-
|-
|
|
<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?countryLabel ?population WHERE {
SELECT ?countryLabel ?population WHERE {
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }  # China, US, India
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }  # China, US, India
Line 241: Line 169:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
United States        340110988
United States        340110988
China              1404890000
China              1404890000
Line 247: Line 175:
</syntaxhighlight>
</syntaxhighlight>
||
||
<syntaxhighlight lang="sparql" line highlight="6">
<syntaxhighlight lang="sparql" line highlight="6" copy>
SELECT ?countryLabel ?population WHERE {
SELECT ?countryLabel ?population WHERE {
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
Line 257: Line 185:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
China              1404890000
China              1404890000
India              1326093247
India              1326093247
Line 270: Line 198:
|-
|-
|
|
<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?countryLabel WHERE {
SELECT ?countryLabel WHERE {
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
Line 279: Line 207:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
United States
United States
China
China
Line 285: Line 213:
</syntaxhighlight>
</syntaxhighlight>
||
||
<syntaxhighlight lang="sparql" line highlight="6">
<syntaxhighlight lang="sparql" line highlight="6" copy>
SELECT ?countryLabel WHERE {
SELECT ?countryLabel WHERE {
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
Line 295: Line 223:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
United States
United States
China
China
Line 307: Line 235:
|-
|-
|
|
<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?countryLabel ?population WHERE {
SELECT ?countryLabel ?population WHERE {
   ?country wdt:P31 wd:Q6256 .
   ?country wdt:P31 wd:Q6256 .
Line 318: Line 246:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
China  1404890000
China  1404890000
</syntaxhighlight>
</syntaxhighlight>
||
||
<syntaxhighlight lang="sparql" line highlight="7">
<syntaxhighlight lang="sparql" line highlight="7" copy>
SELECT ?countryLabel ?population WHERE {
SELECT ?countryLabel ?population WHERE {
   ?country wdt:P31 wd:Q6256 .
   ?country wdt:P31 wd:Q6256 .
Line 333: Line 261:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
India  1326093247
India  1326093247
</syntaxhighlight>
</syntaxhighlight>
|}
|}


'''GROUP BY''' — covered with aggregation (see Aggregates).
== VALUES & BIND ==
 
VALUES restricts a variable to a list; BIND computes a new variable.
 
<syntaxhighlight lang="sparql" copy>
SELECT ?personLabel ?birthYear WHERE {
  VALUES ?person { wd:Q937 wd:Q76 }        # Einstein, Obama
  ?person wdt:P569 ?birth .
  BIND(YEAR(?birth) AS ?birthYear)
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
</syntaxhighlight>
 
Result:
<syntaxhighlight lang="text" copy>
Albert Einstein  1879
Barack Obama    1961
</syntaxhighlight>


== FILTER ==
== FILTER ==
Line 360: Line 305:
'''comparison''' — countries with more than 1 billion people:
'''comparison''' — countries with more than 1 billion people:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?countryLabel ?population WHERE {
SELECT ?countryLabel ?population WHERE {
   ?country wdt:P31 wd:Q6256 .
   ?country wdt:P31 wd:Q6256 .
Line 370: Line 315:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
China  1404890000
China  1404890000
India  1326093247
India  1326093247
Line 377: Line 322:
'''logical (AND)''' — countries with 300M–500M people:
'''logical (AND)''' — countries with 300M–500M people:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?countryLabel ?population WHERE {
SELECT ?countryLabel ?population WHERE {
   ?country wdt:P31 wd:Q6256 .
   ?country wdt:P31 wd:Q6256 .
Line 387: Line 332:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
United States  340110988
United States  340110988
</syntaxhighlight>
</syntaxhighlight>
Line 393: Line 338:
'''string (STRSTARTS)''' — countries whose English label starts with "S":
'''string (STRSTARTS)''' — countries whose English label starts with "S":


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?countryLabel WHERE {
SELECT ?countryLabel WHERE {
   ?country wdt:P31 wd:Q6256 .
   ?country wdt:P31 wd:Q6256 .
Line 403: Line 348:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
Saint Kitts and Nevis
Saint Kitts and Nevis
Somaliland
Somaliland
Line 411: Line 356:
'''numeric (ROUND)''' — France's population in millions, rounded:
'''numeric (ROUND)''' — France's population in millions, rounded:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?millions (ROUND(?millions) AS ?rounded) WHERE {
SELECT ?millions (ROUND(?millions) AS ?rounded) WHERE {
   wd:Q142 wdt:P1082 ?population .
   wd:Q142 wdt:P1082 ?population .
Line 419: Line 364:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
68.605616  69
68.605616  69
</syntaxhighlight>
</syntaxhighlight>
Line 425: Line 370:
'''date/time (YEAR)''' — Einstein's birth year:
'''date/time (YEAR)''' — Einstein's birth year:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?personLabel (YEAR(?birth) AS ?year) WHERE {
SELECT ?personLabel (YEAR(?birth) AS ?birthyear) WHERE {
   wd:Q937 wdt:P569 ?birth .
   wd:Q937 wdt:P569 ?birth .
   SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
   SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
Line 433: Line 378:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
Albert Einstein  1879
Albert Einstein  1879
</syntaxhighlight>
</syntaxhighlight>
Line 440: Line 385:
are entities (IRIs), not literals:
are entities (IRIs), not literals:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?o WHERE {
SELECT ?o WHERE {
   wd:Q144 wdt:P31 ?o .
   wd:Q144 wdt:P31 ?o .
Line 448: Line 393:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
http://www.wikidata.org/entity/Q55983715
http://www.wikidata.org/entity/Q55983715
http://www.wikidata.org/entity/Q136772238
http://www.wikidata.org/entity/Q136772238
</syntaxhighlight>
</syntaxhighlight>


<blockquote>'''Gotcha:''' an unbound variable in a FILTER makes the row fail (unbound ≠ false) — guard with <syntaxhighlight lang="sparql" inline>BOUND()</syntaxhighlight> or restructure with OPTIONAL.</blockquote>
<blockquote>'''Gotcha:''' an unbound variable in a FILTER makes the row fail (unbound ≠ false) — guard with <syntaxhighlight lang="sparql" inline>BOUND()</syntaxhighlight> or restructure with OPTIONAL.


== VALUES & BIND ==
The unbound FILTER fails silently. This returns '''no rows''', although
Einstein exists and was born in 1879:
 
<syntaxhighlight lang="sparql" copy>
SELECT ?personLabel WHERE {
  VALUES ?person { wd:Q937 }
  ?person rdfs:label ?personLabel .
  FILTER(LANG(?personLabel) = "en" && ?birthYear = 1879)  # ?birthYear never bound
}
</syntaxhighlight>
 
Result:
<syntaxhighlight lang="text" copy>
(no rows — the FILTER on an unbound variable dropped Einstein)
</syntaxhighlight>


VALUES restricts a variable to a list; BIND computes a new variable. Together:
Fix: bind the variable first, then filter:
birth years of two people, computed, not looked up:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?personLabel ?birthYear WHERE {
SELECT ?personLabel WHERE {
   VALUES ?person { wd:Q937 wd:Q76 }       # Einstein, Obama
   VALUES ?person { wd:Q937 }
   ?person wdt:P569 ?birth .
   ?person wdt:P569 ?birth .
   BIND(YEAR(?birth) AS ?birthYear)
   BIND(YEAR(?birth) AS ?birthYear)
   SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
   ?person rdfs:label ?personLabel .
  FILTER(LANG(?personLabel) = "en" && ?birthYear = 1879)
}
}
</syntaxhighlight>
</syntaxhighlight>


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
Albert Einstein 1879
Albert Einstein
Barack Obama    1961
</syntaxhighlight>
</syntaxhighlight>
</blockquote>


== OPTIONAL, UNION, MINUS ==
== UNION, OPTIONAL, MINUS ==


{| class="wikitable"
{| class="wikitable"
! Keyword !! Meaning
! Keyword !! Meaning
|-
|-
| OPTIONAL || left join: keep the row, leave the variable unbound when absent
| UNION || pattern A '''OR''' pattern B (the default between patterns is AND, not OR)
|-
|-
| UNION || alternatives (the default between patterns is AND, not OR)
| OPTIONAL || declare a statement optional: do not omit a result entity because it has no value for statement
|-
| MINUS || negation: search for things that do '''NOT''' satisfy a given pattern
|}
 
The default between triple patterns is '''AND (join)''': the same variable must
satisfy all patterns. The following query returns only dogs that also have an image:
 
<syntaxhighlight lang="sparql" copy>
SELECT ?itemLabel WHERE {
  ?item wdt:P31 wd:Q144 .    # ?item is a dog
  ?item wdt:P18 ?image .      # AND has an image (same ?item!)
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
LIMIT 3
</syntaxhighlight>
 
Result:
<syntaxhighlight lang="text" copy>
Laika
Hachikō
Pickles
</syntaxhighlight>
 
Because the default is AND, when you would like to find something that is/has either A "or" B, you need to use '''UNION'''
{| class="wikitable"
! Without UNION (AND) !! With UNION
|-
|-
| MINUS || remove rows that match the pattern
|
<syntaxhighlight lang="sparql" copy>
SELECT ?animalLabel (COUNT(?item) AS ?n) WHERE {
  ?item wdt:P31 wd:Q144 .                    # AND: ?item must be a dog
  ?item wdt:P31 wd:Q146 .                    # AND also a cat — impossible!
  BIND("dog" AS ?animalLabel)
}
GROUP BY ?animalLabel
</syntaxhighlight>
 
Result:
<syntaxhighlight lang="text" copy>
(no rows — nothing is both a dog and a cat)
</syntaxhighlight>
||
<syntaxhighlight lang="sparql" copy>
SELECT ?animalLabel (COUNT(?item) AS ?n) WHERE {
  { ?item wdt:P31 wd:Q144 . BIND("dog" AS ?animalLabel) }
  UNION
  { ?item wdt:P31 wd:Q146 . BIND("cat" AS ?animalLabel) }
}
GROUP BY ?animalLabel
</syntaxhighlight>
 
Result:
<syntaxhighlight lang="text" copy>
dog  553
cat  239
</syntaxhighlight>
|}
|}


'''OPTIONAL''' — Einstein (died 1955) vs Obama (alive); ?deathLabel is unbound
Use '''OPTIONAL''' when you would like to find out something when data is available, but don't care if the data is missing — Einstein (died 1955) vs Obama (alive); ?deathLabel is unbound
for Obama:
for Obama:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?personLabel ?deathLabel WHERE {
SELECT ?personLabel ?deathLabel WHERE {
   VALUES ?person { wd:Q937 wd:Q76 }        # Einstein, Barack Obama
   VALUES ?person { wd:Q937 wd:Q76 }        # Einstein, Barack Obama
Line 499: Line 512:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
Albert Einstein  1955-04-18
Albert Einstein  1955-04-18
Barack Obama    (unbound)
Barack Obama    (unbound)
</syntaxhighlight>
</syntaxhighlight>


'''MINUS''' — Nobel laureates minus the French ones:
If you do not declare '''OPTIONAL''', Barack Obama will not appear in the results, as there is logically no date of death on his record.
 
Use '''MINUS''' when you would like to find out about things that are/have NOT something
 
E.g., '''non-French''' Nobel laureates:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?personLabel WHERE {
SELECT ?personLabel WHERE {
   ?person wdt:P166 wd:Q7191 .
   ?person wdt:P166 wd:Q7191 .
Line 516: Line 533:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
Nobel Prize winner
Nobel Prize winner
Adam Bernau
Adam Bernau
Line 522: Line 539:
</syntaxhighlight>
</syntaxhighlight>


'''UNION''' — dogs AND cats in one result set:
<blockquote>'''Gotcha:''' <syntaxhighlight lang="sparql" inline>FILTER NOT EXISTS</syntaxhighlight> and <syntaxhighlight lang="sparql" inline>MINUS</syntaxhighlight> usually give the same result, but differ when a variable is unbound — prefer MINUS for set-difference semantics.
 
Both express "non-French Nobel laureates"; the results are identical:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?animalLabel (COUNT(?item) AS ?n) WHERE {
# FILTER NOT EXISTS
   { ?item wdt:P31 wd:Q144 . BIND("dog" AS ?animalLabel) }
SELECT ?personLabel WHERE {
  UNION
   ?person wdt:P166 wd:Q7191 .
   { ?item wdt:P31 wd:Q146 . BIND("cat" AS ?animalLabel) }
   FILTER NOT EXISTS { ?person wdt:P27 wd:Q142 . }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
}
GROUP BY ?animalLabel
LIMIT 3
</syntaxhighlight>
</syntaxhighlight>


Result:
<syntaxhighlight lang="sparql" copy>
<syntaxhighlight lang="text">
# MINUS
dog  553
SELECT ?personLabel WHERE {
cat  239
  ?person wdt:P166 wd:Q7191 .
  MINUS { ?person wdt:P27 wd:Q142 . }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
LIMIT 3
</syntaxhighlight>
</syntaxhighlight>


<blockquote>'''Gotcha:''' the default between triple patterns is '''AND (join)''' — use UNION for alternatives.</blockquote>
Result (both):
<syntaxhighlight lang="text" copy>
Nobel Prize winner
Adam Bernau
Santiago Carril
</syntaxhighlight>


<blockquote>'''Gotcha:''' <syntaxhighlight lang="sparql" inline>FILTER NOT EXISTS</syntaxhighlight> differs from <syntaxhighlight lang="sparql" inline>MINUS</syntaxhighlight> when variables are unbound — prefer MINUS for set-difference semantics.</blockquote>
The difference appears only when a variable inside the negated pattern is
unbound: FILTER NOT EXISTS tests per-row (an unbound variable never matches),
while MINUS subtracts whole patterns regardless of unbound variables.
</blockquote>


== Property paths ==
== Property paths ==
Line 548: Line 580:
! Path !! Meaning
! Path !! Meaning
|-
|-
| wdt:P40/wdt:P40 || two hops (sequence)
| wdt:P279/wdt:P279 || two hops (sequence)
|-
|-
| wdt:P40+ || one or more hops
| wdt:P279+ || one or more hops
|-
|-
| wdt:P40* || zero or more hops
| wdt:P279* || zero or more hops (transitive)
|-
|-
| wdt:P40? || zero or one hop
| wdt:P279? || zero or one hop
|-
|-
| wdt:P40|wdt:P41 || either property
| wdt:P279|wdt:P31 || either property
|-
|-
| ^wdt:P40 || inverse direction
| ^wdt:P279 || inverse direction
|-
|-
| !wdt:P40 || any property except P40
| !wdt:P279 || any property except P279
|}
|}


A path chains several properties into one pattern. The dog example chains
A path chains several properties into one pattern. The dog example uses
"instance of" (wdt:P31) with "subclass of" (wdt:P279) — first understand each
'''subclass of''' (wdt:P279). Compare the direct pattern with the transitive
step, then the combination:
(<syntaxhighlight lang="sparql" inline>*</syntaxhighlight> = zero or more hops):


{| class="wikitable"
{| class="wikitable"
! Pattern !! What it matches !! Live example !! Returns
! Pattern !! What it matches !! Live example !! Returns
|-
|-
| <syntaxhighlight lang="sparql" inline>?x wdt:P31 wd:Q144</syntaxhighlight> || individual things that are dogs (instance of) || <syntaxhighlight lang="sparql" inline>?x wdt:P31 wd:Q144</syntaxhighlight> || Daddy, Dempsey, Diamond
| <syntaxhighlight lang="sparql" inline>?x wdt:P279 wd:Q144</syntaxhighlight> || classes that are a '''direct''' kind of dog (one hop) || <syntaxhighlight lang="sparql" inline>?x wdt:P279 wd:Q144</syntaxhighlight> || Rottweiler, Swedish Vallhund — 314
|-
|-
| <syntaxhighlight lang="sparql" inline>?x wdt:P279 wd:Q144</syntaxhighlight> || classes that are kinds of dog (subclass of) || <syntaxhighlight lang="sparql" inline>?x wdt:P279 wd:Q144</syntaxhighlight> || Rottweiler, Swedish Vallhund
| <syntaxhighlight lang="sparql" inline>?x wdt:P279* wd:Q144</syntaxhighlight> || classes that are dog or any kind-of-kind-of-dog (any number of hops) || <syntaxhighlight lang="sparql" inline>?x wdt:P279* wd:Q144</syntaxhighlight> || adds sub-breeds like Norman Hound → scent hound → dog — 511
|-
| <syntaxhighlight lang="sparql" inline>?x wdt:P279* wd:Q144</syntaxhighlight> || classes that are dog or any kind-of-kind-of-dog (transitive) || <syntaxhighlight lang="sparql" inline>?x wdt:P279* wd:Q144</syntaxhighlight> || breeds + sub-breeds (Standard Schnauzer, …)
|-
| <syntaxhighlight lang="sparql" inline>?x wdt:P31/wdt:P279* wd:Q144</syntaxhighlight> || individual things that are dogs, of any breed (sequence: instance of, then subclass of*) || <syntaxhighlight lang="sparql" inline>?x wdt:P31/wdt:P279* wd:Q144</syntaxhighlight> || Theo, Tich, Tickle Em Jock
|}
|}


The combination <syntaxhighlight lang="sparql" inline>wdt:P31/wdt:P279*</syntaxhighlight> reads
The <syntaxhighlight lang="sparql" inline>*</syntaxhighlight> finds things several hops away that a
"instance of something that is a dog or a subclass of dog" — every individual
single triple cannot:
dog, whatever its breed:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?thing ?thingLabel WHERE {
SELECT ?thing ?thingLabel WHERE {
   ?thing wdt:P31/wdt:P279* wd:Q144 .
   ?thing wdt:P279* wd:Q144 .
   SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
   SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
}
Line 592: Line 619:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
Theo
Lapponian Herder
Tich
autism assistance dog
Tickle Em Jock
diabetic alert dog
</syntaxhighlight>
 
(These are sub-subclasses: Lapponian Herder → herding dog → dog.)
 
<blockquote>'''Gotcha:''' a variable that appears only inside a path is not bound, so it cannot be selected.
 
A two-hop path (<syntaxhighlight lang="sparql" inline>wdt:P279/wdt:P279</syntaxhighlight>) finds dogs two levels down (bloodhound, beagle) — but the intermediate hop is anonymous, so there is nothing to select for it:
 
<syntaxhighlight lang="sparql" copy>
# Path form: intermediate hop is anonymous — ?mid is not available
SELECT ?xLabel WHERE {
  ?x wdt:P279/wdt:P279 wd:Q144 .
  ?x rdfs:label ?xLabel . FILTER(LANG(?xLabel) = "en")
}
LIMIT 3
</syntaxhighlight>
 
Result:
<syntaxhighlight lang="text" copy>
bloodhound
beagle
Chesapeake Bay Retriever
</syntaxhighlight>
 
To select the intermediate value, write two explicit triples with a variable:
 
<syntaxhighlight lang="sparql" copy>
# Explicit form: ?mid is selectable
SELECT ?xLabel ?midLabel WHERE {
  ?x wdt:P279 ?mid .
  ?mid wdt:P279 wd:Q144 .
  ?x rdfs:label ?xLabel . FILTER(LANG(?xLabel) = "en")
  ?mid rdfs:label ?midLabel . FILTER(LANG(?midLabel) = "en")
}
LIMIT 3
</syntaxhighlight>
</syntaxhighlight>


<blockquote>'''Gotcha:''' variables used only inside a property path (e.g. <syntaxhighlight lang="sparql" inline>?s wdt:P40/wdt:P40 ?o</syntaxhighlight>) cannot be selected.</blockquote>
Result:
<syntaxhighlight lang="text" copy>
shepherd dog        pastoral dog
livestock guardian dog  pastoral dog
Canadian Eskimo Dog      inuit sledge dog
</syntaxhighlight>
</blockquote>


== Aggregates ==
== Aggregates ==
Line 608: Line 676:
animal; here: values of <syntaxhighlight lang="sparql" inline>wdt:P1082</syntaxhighlight> for the three countries):
animal; here: values of <syntaxhighlight lang="sparql" inline>wdt:P1082</syntaxhighlight> for the three countries):


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT (COUNT(?population) AS ?n) WHERE {
SELECT (COUNT(?population) AS ?n) WHERE {
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
Line 616: Line 684:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
3
3
</syntaxhighlight>
</syntaxhighlight>
Line 622: Line 690:
'''SUM''' — combined population of the three:
'''SUM''' — combined population of the three:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT (SUM(?population) AS ?total) WHERE {
SELECT (SUM(?population) AS ?total) WHERE {
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
Line 630: Line 698:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
3071094235
3071094235
</syntaxhighlight>
</syntaxhighlight>
Line 636: Line 704:
'''AVG''' — mean population of the three:
'''AVG''' — mean population of the three:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT (AVG(?population) AS ?avg) WHERE {
SELECT (AVG(?population) AS ?avg) WHERE {
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
Line 644: Line 712:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
1023698078.33
1023698078.33
</syntaxhighlight>
</syntaxhighlight>
Line 650: Line 718:
'''MIN / MAX''' — smallest and largest of the three:
'''MIN / MAX''' — smallest and largest of the three:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT (MIN(?population) AS ?min) (MAX(?population) AS ?max) WHERE {
SELECT (MIN(?population) AS ?min) (MAX(?population) AS ?max) WHERE {
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
Line 658: Line 726:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
340110988  1404890000
340110988  1404890000
</syntaxhighlight>
</syntaxhighlight>
Line 664: Line 732:
'''SAMPLE''' — one arbitrary value (useful to squash duplicates):
'''SAMPLE''' — one arbitrary value (useful to squash duplicates):


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT (SAMPLE(?countryLabel) AS ?anyCountry) WHERE {
SELECT (SAMPLE(?countryLabel) AS ?anyCountry) WHERE {
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
   VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
Line 674: Line 742:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
United States
United States
</syntaxhighlight>
</syntaxhighlight>
Line 680: Line 748:
'''GROUP_CONCAT''' — Einstein's awards in one cell:
'''GROUP_CONCAT''' — Einstein's awards in one cell:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?personLabel (GROUP_CONCAT(?awardLabel; SEPARATOR=", ") AS ?awards) WHERE {
SELECT ?personLabel (GROUP_CONCAT(?awardLabel; SEPARATOR=", ") AS ?awards) WHERE {
   VALUES ?person { wd:Q937 }
   VALUES ?person { wd:Q937 }
Line 690: Line 758:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
Albert Einstein  Nobel Prize in Physics, …
Albert Einstein  Nobel Prize in Physics, …
</syntaxhighlight>
</syntaxhighlight>
Line 697: Line 765:
than 400 instances:
than 400 instances:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?animalLabel (COUNT(?item) AS ?n) WHERE {
SELECT ?animalLabel (COUNT(?item) AS ?n) WHERE {
   VALUES ?animal { wd:Q144 wd:Q146 }
   VALUES ?animal { wd:Q144 wd:Q146 }
Line 708: Line 776:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
dog  553
dog  553
</syntaxhighlight>
</syntaxhighlight>


<blockquote>'''Gotcha:''' aggregates need GROUP BY for every non-aggregated variable; forgetting one mixes unrelated rows.</blockquote>
<blockquote>'''Gotcha:''' aggregates need GROUP BY for every non-aggregated variable. Forgetting one is a '''query error''', not a wrong result.
 
Missing GROUP BY for <syntaxhighlight lang="sparql" inline>?animalLabel</syntaxhighlight>:
 
<syntaxhighlight lang="sparql" copy>
SELECT ?animalLabel (COUNT(?item) AS ?n) WHERE {
  VALUES ?animal { wd:Q144 wd:Q146 }
  ?item wdt:P31 ?animal .
  ?animal rdfs:label ?animalLabel .
  FILTER(LANG(?animalLabel) = "en")
}
</syntaxhighlight>
 
Result:
<syntaxhighlight lang="text" copy>
ERROR: Bad aggregate — every non-aggregated variable needs GROUP BY
</syntaxhighlight>
</blockquote>


== Subqueries ==
== Subqueries ==
Line 720: Line 805:
'''The most populous country, computed via a subquery:'''
'''The most populous country, computed via a subquery:'''


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SELECT ?countryLabel ?max WHERE {
SELECT ?countryLabel ?max WHERE {
   {
   {
Line 735: Line 820:


Result:
Result:
<syntaxhighlight lang="text">
<syntaxhighlight lang="text" copy>
China  1404890000
China  1404890000
</syntaxhighlight>
</syntaxhighlight>
Line 744: Line 829:
queries above:
queries above:


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
# then reference ?xLabel for every ?x in the query
# then reference ?xLabel for every ?x in the query
</syntaxhighlight>
</syntaxhighlight>


<syntaxhighlight lang="sparql">
<syntaxhighlight lang="sparql" copy>
# Federating to another endpoint (needs the remote's own IRIs, e.g. via owl:sameAs)
# Federating to another endpoint (needs the remote's own IRIs, e.g. via owl:sameAs)
SELECT ?cityLabel WHERE {
SELECT ?cityLabel WHERE {

Latest revision as of 09:45, 23 August 2026

Quick reference for smart people — part of our dev cheatsheets collection.

If you are new to SPARQL, see SPARQL101.

Examples featured in this cheatsheet run as-is against the public Wikidata endpoint https://query.wikidata.org/sparql. Querying another Wikibase instance is similar, but small query adjustments may be necessary depending on how each instance is set up. See the documentation for your target instance for more info.

Basic example

Natural language question:

How many dogs and cats are featured in Wikidata?

SPARQL query:

SELECT ?animal ?animalLabel (COUNT(?item) AS ?count) WHERE {
  VALUES ?animal { wd:Q144 wd:Q146 }        # dog, cat
  ?item wdt:P31 ?animal .                   # ?item is an instance of ?animal
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
GROUP BY ?animal ?animalLabel

Result as of 2026-08-19:

dog  553
cat  239

Building a query

A query is a list of triple patterns( subject predicate object). Knowing one or two elements, you can query the rest:

Question Known parts Pattern
"What is the population of France?" subject France (wd:Q142) + predicate population (wdt:P1082) wd:Q142 wdt:P1082 ?population .
"Which things are dogs?" predicate instance-of (wdt:P31) + object dog (wd:Q144) ?x wdt:P31 wd:Q144 .
"What is known about Einstein?" subject Einstein (wd:Q937) wd:Q937 ?predicate ?value .

Query forms

Form Returns Use when
SELECT table of variable bindings you want rows of data
ASK true/false you only need to know whether something exists
CONSTRUCT an RDF graph (triples) you want the result as RDF, not a table
DESCRIBE a graph describing the resource you want everything known about an entity

SELECT — what is the population of France?

SELECT ?population WHERE {
  wd:Q142 wdt:P1082 ?population .
}

Result:

68605616

ASK — is there at least one dog in Wikidata?

ASK WHERE { ?x wdt:P31 wd:Q144 . }

Result:

true

CONSTRUCT — every dog as RDF (first two). Each result row becomes a triple ?x wdt:P31 wd:Q144:

CONSTRUCT { ?x wdt:P31 wd:Q144 . }
WHERE { ?x wdt:P31 wd:Q144 . } LIMIT 2

Result (visualised, N-Triples style):

Q186486  wdt:P31  wd:Q144
Q280571  wdt:P31  wd:Q144

DESCRIBE — everything known about Einstein (all triples with wd:Q937 as subject or object):

DESCRIBE wd:Q937

Result (visualised, N-Triples style, first few of thousands):

Q937  rdfs:label   "Albert Einstein"@en
Q937  wdt:P569     1879-03-14
Q937  wdt:P570     1955-04-18
Q937  wdt:P21      wd:Q6581097   # male

Solution modifiers

DISTINCT — drop duplicate rows. Einstein and Obama are both male, so the gender repeats:

Without With DISTINCT
SELECT ?genderLabel WHERE {
  VALUES ?person { wd:Q937 wd:Q76 }
  ?person wdt:P21 ?gender .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}

Result:

male
male
SELECT DISTINCT ?genderLabel WHERE {
  VALUES ?person { wd:Q937 wd:Q76 }
  ?person wdt:P21 ?gender .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}

Result:

male

ORDER BY + LIMIT — the 3 most populous countries, sorted descending:

Without (arbitrary order) With ORDER BY DESC + LIMIT
SELECT ?countryLabel ?population WHERE {
  VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }   # China, US, India
  ?country wdt:P1082 ?population .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}

Result:

United States        340110988
China               1404890000
India               1326093247
SELECT ?countryLabel ?population WHERE {
  VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
  ?country wdt:P1082 ?population .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
ORDER BY DESC(?population)

Result:

China               1404890000
India               1326093247
United States        340110988

LIMIT — at most n rows:

Without (all 3) With LIMIT 2
SELECT ?countryLabel WHERE {
  VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
  ?country wdt:P1082 ?population .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}

Result:

United States
China
India
SELECT ?countryLabel WHERE {
  VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
  ?country wdt:P1082 ?population .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
LIMIT 2

Result:

United States
China

OFFSET — skip rows (paging). The 2nd most populous country:

With LIMIT 1 (first) With LIMIT 1 OFFSET 1 (second)
SELECT ?countryLabel ?population WHERE {
  ?country wdt:P31 wd:Q6256 .
  ?country wdt:P1082 ?population .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
ORDER BY DESC(?population)
LIMIT 1

Result:

China  1404890000
SELECT ?countryLabel ?population WHERE {
  ?country wdt:P31 wd:Q6256 .
  ?country wdt:P1082 ?population .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
ORDER BY DESC(?population)
LIMIT 1 OFFSET 1

Result:

India  1326093247

VALUES & BIND

VALUES restricts a variable to a list; BIND computes a new variable.

SELECT ?personLabel ?birthYear WHERE {
  VALUES ?person { wd:Q937 wd:Q76 }        # Einstein, Obama
  ?person wdt:P569 ?birth .
  BIND(YEAR(?birth) AS ?birthYear)
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}

Result:

Albert Einstein  1879
Barack Obama     1961

FILTER

Category Operators / functions
comparison = < > <= >= !=
logical && || !
string STR() CONTAINS() STRSTARTS() STRENDS() REGEX()
numeric ABS() ROUND() FLOOR() CEIL()
date/time YEAR() MONTH() DAY() NOW()
term tests isIRI() isBlank() isLiteral() LANG() DATATYPE()

comparison — countries with more than 1 billion people:

SELECT ?countryLabel ?population WHERE {
  ?country wdt:P31 wd:Q6256 .
  ?country wdt:P1082 ?population .
  FILTER(?population > 1000000000)
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}

Result:

China   1404890000
India   1326093247

logical (AND) — countries with 300M–500M people:

SELECT ?countryLabel ?population WHERE {
  ?country wdt:P31 wd:Q6256 .
  ?country wdt:P1082 ?population .
  FILTER(?population > 300000000 && ?population < 500000000)
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}

Result:

United States  340110988

string (STRSTARTS) — countries whose English label starts with "S":

SELECT ?countryLabel WHERE {
  ?country wdt:P31 wd:Q6256 .
  ?country rdfs:label ?countryLabel .
  FILTER(LANG(?countryLabel) = "en" && STRSTARTS(?countryLabel, "S"))
}
LIMIT 3

Result:

Saint Kitts and Nevis
Somaliland
Saint Vincent and the Grenadines

numeric (ROUND) — France's population in millions, rounded:

SELECT ?millions (ROUND(?millions) AS ?rounded) WHERE {
  wd:Q142 wdt:P1082 ?population .
  BIND(?population / 1000000 AS ?millions)
}

Result:

68.605616  69

date/time (YEAR) — Einstein's birth year:

SELECT ?personLabel (YEAR(?birth) AS ?birthyear) WHERE {
  wd:Q937 wdt:P569 ?birth .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}

Result:

Albert Einstein  1879

term test (isIRI) — the values of wdt:P31 on a dog are entities (IRIs), not literals:

SELECT ?o WHERE {
  wd:Q144 wdt:P31 ?o .
  FILTER(isIRI(?o))
}

Result:

http://www.wikidata.org/entity/Q55983715
http://www.wikidata.org/entity/Q136772238

Gotcha: an unbound variable in a FILTER makes the row fail (unbound ≠ false) — guard with BOUND() or restructure with OPTIONAL.

The unbound FILTER fails silently. This returns no rows, although Einstein exists and was born in 1879:

SELECT ?personLabel WHERE {
  VALUES ?person { wd:Q937 }
  ?person rdfs:label ?personLabel .
  FILTER(LANG(?personLabel) = "en" && ?birthYear = 1879)   # ?birthYear never bound
}

Result:

(no rows — the FILTER on an unbound variable dropped Einstein)

Fix: bind the variable first, then filter:

SELECT ?personLabel WHERE {
  VALUES ?person { wd:Q937 }
  ?person wdt:P569 ?birth .
  BIND(YEAR(?birth) AS ?birthYear)
  ?person rdfs:label ?personLabel .
  FILTER(LANG(?personLabel) = "en" && ?birthYear = 1879)
}

Result:

Albert Einstein

UNION, OPTIONAL, MINUS

Keyword Meaning
UNION pattern A OR pattern B (the default between patterns is AND, not OR)
OPTIONAL declare a statement optional: do not omit a result entity because it has no value for statement
MINUS negation: search for things that do NOT satisfy a given pattern

The default between triple patterns is AND (join): the same variable must satisfy all patterns. The following query returns only dogs that also have an image:

SELECT ?itemLabel WHERE {
  ?item wdt:P31 wd:Q144 .     # ?item is a dog
  ?item wdt:P18 ?image .      # AND has an image (same ?item!)
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
LIMIT 3

Result:

Laika
Hachikō
Pickles

Because the default is AND, when you would like to find something that is/has either A "or" B, you need to use UNION

Without UNION (AND) With UNION
SELECT ?animalLabel (COUNT(?item) AS ?n) WHERE {
  ?item wdt:P31 wd:Q144 .                    # AND: ?item must be a dog
  ?item wdt:P31 wd:Q146 .                    # AND also a cat — impossible!
  BIND("dog" AS ?animalLabel)
}
GROUP BY ?animalLabel

Result:

(no rows — nothing is both a dog and a cat)
SELECT ?animalLabel (COUNT(?item) AS ?n) WHERE {
  { ?item wdt:P31 wd:Q144 . BIND("dog" AS ?animalLabel) }
  UNION
  { ?item wdt:P31 wd:Q146 . BIND("cat" AS ?animalLabel) }
}
GROUP BY ?animalLabel

Result:

dog  553
cat  239

Use OPTIONAL when you would like to find out something when data is available, but don't care if the data is missing — Einstein (died 1955) vs Obama (alive); ?deathLabel is unbound for Obama:

SELECT ?personLabel ?deathLabel WHERE {
  VALUES ?person { wd:Q937 wd:Q76 }         # Einstein, Barack Obama
  OPTIONAL { ?person wdt:P570 ?death . }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}

Result:

Albert Einstein  1955-04-18
Barack Obama     (unbound)

If you do not declare OPTIONAL, Barack Obama will not appear in the results, as there is logically no date of death on his record.

Use MINUS when you would like to find out about things that are/have NOT something

E.g., non-French Nobel laureates:

SELECT ?personLabel WHERE {
  ?person wdt:P166 wd:Q7191 .
  MINUS { ?person wdt:P27 wd:Q142 . }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
LIMIT 3

Result:

Nobel Prize winner
Adam Bernau
Santiago Carril

Gotcha: FILTER NOT EXISTS and MINUS usually give the same result, but differ when a variable is unbound — prefer MINUS for set-difference semantics.

Both express "non-French Nobel laureates"; the results are identical:

# FILTER NOT EXISTS
SELECT ?personLabel WHERE {
  ?person wdt:P166 wd:Q7191 .
  FILTER NOT EXISTS { ?person wdt:P27 wd:Q142 . }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
LIMIT 3
# MINUS
SELECT ?personLabel WHERE {
  ?person wdt:P166 wd:Q7191 .
  MINUS { ?person wdt:P27 wd:Q142 . }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
LIMIT 3

Result (both):

Nobel Prize winner
Adam Bernau
Santiago Carril

The difference appears only when a variable inside the negated pattern is unbound: FILTER NOT EXISTS tests per-row (an unbound variable never matches), while MINUS subtracts whole patterns regardless of unbound variables.

Property paths

Path Meaning
wdt:P279/wdt:P279 two hops (sequence)
wdt:P279+ one or more hops
wdt:P279* zero or more hops (transitive)
wdt:P279? zero or one hop
wdt:P31 either property
^wdt:P279 inverse direction
!wdt:P279 any property except P279

A path chains several properties into one pattern. The dog example uses subclass of (wdt:P279). Compare the direct pattern with the transitive (* = zero or more hops):

Pattern What it matches Live example Returns
?x wdt:P279 wd:Q144 classes that are a direct kind of dog (one hop) ?x wdt:P279 wd:Q144 Rottweiler, Swedish Vallhund — 314
?x wdt:P279* wd:Q144 classes that are dog or any kind-of-kind-of-dog (any number of hops) ?x wdt:P279* wd:Q144 adds sub-breeds like Norman Hound → scent hound → dog — 511

The * finds things several hops away that a single triple cannot:

SELECT ?thing ?thingLabel WHERE {
  ?thing wdt:P279* wd:Q144 .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
LIMIT 3

Result:

Lapponian Herder
autism assistance dog
diabetic alert dog

(These are sub-subclasses: Lapponian Herder → herding dog → dog.)

Gotcha: a variable that appears only inside a path is not bound, so it cannot be selected.

A two-hop path (wdt:P279/wdt:P279) finds dogs two levels down (bloodhound, beagle) — but the intermediate hop is anonymous, so there is nothing to select for it:

# Path form: intermediate hop is anonymous — ?mid is not available
SELECT ?xLabel WHERE {
  ?x wdt:P279/wdt:P279 wd:Q144 .
  ?x rdfs:label ?xLabel . FILTER(LANG(?xLabel) = "en")
}
LIMIT 3

Result:

bloodhound
beagle
Chesapeake Bay Retriever

To select the intermediate value, write two explicit triples with a variable:

# Explicit form: ?mid is selectable
SELECT ?xLabel ?midLabel WHERE {
  ?x wdt:P279 ?mid .
  ?mid wdt:P279 wd:Q144 .
  ?x rdfs:label ?xLabel . FILTER(LANG(?xLabel) = "en")
  ?mid rdfs:label ?midLabel . FILTER(LANG(?midLabel) = "en")
}
LIMIT 3

Result:

shepherd dog        pastoral dog
livestock guardian dog   pastoral dog
Canadian Eskimo Dog      inuit sledge dog

Aggregates

Aggregates collapse many rows into one per group. All examples use the same three countries (China, US, India).

COUNT — how many values (the running example counts instances per animal; here: values of wdt:P1082 for the three countries):

SELECT (COUNT(?population) AS ?n) WHERE {
  VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
  ?country wdt:P1082 ?population .
}

Result:

3

SUM — combined population of the three:

SELECT (SUM(?population) AS ?total) WHERE {
  VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
  ?country wdt:P1082 ?population .
}

Result:

3071094235

AVG — mean population of the three:

SELECT (AVG(?population) AS ?avg) WHERE {
  VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
  ?country wdt:P1082 ?population .
}

Result:

1023698078.33

MIN / MAX — smallest and largest of the three:

SELECT (MIN(?population) AS ?min) (MAX(?population) AS ?max) WHERE {
  VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
  ?country wdt:P1082 ?population .
}

Result:

340110988  1404890000

SAMPLE — one arbitrary value (useful to squash duplicates):

SELECT (SAMPLE(?countryLabel) AS ?anyCountry) WHERE {
  VALUES ?country { wd:Q148 wd:Q30 wd:Q668 }
  ?country wdt:P1082 ?population .
  ?country rdfs:label ?countryLabel .
  FILTER(LANG(?countryLabel) = "en")
}

Result:

United States

GROUP_CONCAT — Einstein's awards in one cell:

SELECT ?personLabel (GROUP_CONCAT(?awardLabel; SEPARATOR=", ") AS ?awards) WHERE {
  VALUES ?person { wd:Q937 }
  ?person wdt:P166 ?award .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
GROUP BY ?personLabel

Result:

Albert Einstein  Nobel Prize in Physics, …

GROUP BY + HAVING — the running example, filtered to groups with more than 400 instances:

SELECT ?animalLabel (COUNT(?item) AS ?n) WHERE {
  VALUES ?animal { wd:Q144 wd:Q146 }
  ?item wdt:P31 ?animal .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
GROUP BY ?animal ?animalLabel
HAVING (COUNT(?item) > 400)

Result:

dog  553

Gotcha: aggregates need GROUP BY for every non-aggregated variable. Forgetting one is a query error, not a wrong result.

Missing GROUP BY for ?animalLabel:

SELECT ?animalLabel (COUNT(?item) AS ?n) WHERE {
  VALUES ?animal { wd:Q144 wd:Q146 }
  ?item wdt:P31 ?animal .
  ?animal rdfs:label ?animalLabel .
  FILTER(LANG(?animalLabel) = "en")
}

Result:

ERROR: Bad aggregate — every non-aggregated variable needs GROUP BY

Subqueries

A query inside a query — useful for "the X with the max Y" patterns.

The most populous country, computed via a subquery:

SELECT ?countryLabel ?max WHERE {
  {
    SELECT (MAX(?population) AS ?max) WHERE {
      ?c wdt:P31 wd:Q6256 .
      ?c wdt:P1082 ?population .
    }
  }
  ?country wdt:P31 wd:Q6256 .
  ?country wdt:P1082 ?max .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}

Result:

China  1404890000

SERVICE (labels, federation)

The label service turns entity IDs into human-readable labels — used in most queries above:

SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
# then reference ?xLabel for every ?x in the query
# Federating to another endpoint (needs the remote's own IRIs, e.g. via owl:sameAs)
SELECT ?cityLabel WHERE {
  wd:Q64 wdt:P36 ?city .
  ?city owl:sameAs ?dbpediaCity .
  SERVICE <https://dbpedia.org/sparql> {
    ?dbpediaCity rdfs:label ?cityLabel .
    FILTER(LANG(?cityLabel) = "en")
  }
}

Further reading