SPARQL Basics

Query patterns for Waratah, worked against the --load-demo dataset so every example below can be run as-is (see Getting Started). The dataset:

Subject Predicate Object

ex:alice

rdf:type

ex:Person

ex:alice

ex:name

"Alice"

ex:alice

ex:livesIn

ex:brisbane

ex:bob

rdf:type

ex:Person

ex:bob

ex:name

"Bob"

ex:bob

ex:livesIn

ex:hobart

ex:brisbane

rdf:type

ex:City

ex:brisbane

ex:hasWeather

"Sunny"

ex:hobart

rdf:type

ex:City

ex:hobart

ex:hasWeather

"Cold"

All examples share one prefix declaration:

PREFIX ex: <http://ex/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

The basic pattern

A WHERE block lists the statement shapes to match; variables (?name) are the slots to fill. Patterns that share a variable join, exactly like a join between tables:

SELECT ?person ?weather WHERE {
  ?person ex:livesIn ?city .
  ?city ex:hasWeather ?weather .
}

Result: alice/Sunny, bob/Cold. The shorthand a stands for rdf:type, so ?person a ex:Person matches people only.

FILTER: keep the rows you want

FILTER applies a condition to each solution:

SELECT ?city WHERE {
  ?city ex:hasWeather ?weather .
  FILTER(?weather = "Sunny")
}

Result: brisbane. Comparisons (=, !=, <, >, ⇐, >=) on typed literals use their datatypes — dates and date-times compare chronologically, numbers numerically — which is what makes Temporal Data range queries work.

OPTIONAL: match what is there

OPTIONAL keeps solutions even when the inner pattern finds nothing; the variables it would bind are simply left unbound:

SELECT ?city ?raining WHERE {
  ?city a ex:City .
  OPTIONAL { ?city ex:hasRaining ?raining }
}

Both cities come back; ?raining is empty in both rows because nothing in the dataset has that predicate. This is the standard shape for "attribute may be absent" data.

UNION: either shape

SELECT DISTINCT ?thing WHERE {
  { ?thing a ex:Person } UNION { ?thing a ex:City }
}

Result: alice, bob, brisbane, hobart.

Shaping results

ORDER BY sorts, LIMIT and OFFSET page, DISTINCT collapses duplicates:

SELECT DISTINCT ?name WHERE {
  ?person a ex:Person ;
          ex:name ?name .
}
ORDER BY ?name
LIMIT 10

ASK: does anything match?

ASK returns yes/no instead of rows:

ASK WHERE { ?person ex:livesIn ex:brisbane }

CONSTRUCT builds new RDF from matches and DESCRIBE returns the statements about one resource; both run against the same patterns.

Aggregates

GROUP BY with aggregate functions summarizes:

SELECT ?type (COUNT(?thing) AS ?count) WHERE {
  ?thing a ?type .
}
GROUP BY ?type

Result: ex:Person/2, ex:City/2.

Where queries run

Waratah’s documented deviations

A few SPARQL behaviors differ from the specification in deliberate, documented ways — most notably, ordering comparisons across the xsd:gYear family. The full list, and the --strict-sparql flag that enforces spec-strict behavior, is in Deviations from Spec.