SPARQL: querying real-world data

The first four chapters used a toy dataset, which is the right way to learn the language. Actual datasets have shapes of their own, and three of them come back so often in Dutch practice that they are worth knowing in advance: hierarchies, geometries and statistics.

Each has a standard model behind it, and each becomes straightforward once you recognise the model. This chapter is a field guide rather than a reference.

It assumes the four chapters before it, and property paths in particular.

Hierarchies

Taxonomies, classification schemes, organisational structures and asset breakdowns are all the same shape: things pointing at their parents. Four predicates cover most of what you will meet:

Predicate Relates
rdfs:subClassOf A class to a broader class
rdfs:subPropertyOf A property to a broader property
skos:broader A concept to a broader concept
skos:narrower A concept to a narrower concept

All four are transitive in intent: if A is under B and B is under C, then A is under C. Whether those indirect links exist as triples depends on whether anyone inferred them. This is why property paths matter here — skos:broader+ walks the chain whether or not the shortcuts were materialised.

SKOS distinguishes the two explicitly: skos:broader is the direct step, and skos:broaderTransitive is the closure. Querying with skos:broaderTransitive*/skos:notation "88" finds everything at or beneath the concept numbered 88, however deep.

The query that drives most hierarchy visualisations returns pairs — one row per child with its parent:

prefix skos: <http://www.w3.org/2004/02/skos/core#>

select distinct ?childLabel ?parentLabel
where {
  ?child
    skos:broaderTransitive ?parent;
    skos:prefLabel ?childLabel.
  ?parent skos:prefLabel ?parentLabel.
}

A tree, a treemap and a sunburst are all the same two columns drawn differently.

See SKOS for the model itself.

Geometries

Geospatial linked data follows GeoSPARQL, and its structure trips people up because there are three levels, not two:

graph LR f["Feature\n(a building, a road)"] -- "geo:hasGeometry" --> g[Geometry] g -- "geo:asWKT" --> wkt["WKT literal"] g -- "geo:asGML" --> gml["GML literal"]

A feature is the thing in the world. A geometry is its shape. A serialisation is that shape written down, in Well-Known Text (WKT) or GML. The split exists because one feature can have several shapes — a 2D footprint and a 3D model — and each shape can be written in more than one format.

So a query for shapes goes through the middle step:

prefix bag: <http://bag.basisregistraties.overheid.nl/def/bag#>
prefix geo: <http://www.opengis.net/ont/geosparql#>

select ?feature ?shape
where {
  ?feature
    a bag:Pand;
    geo:hasGeometry ?geometry.
  ?geometry geo:asWKT ?shape.
}
limit 1

The ?geometry variable is never used in the output, which is precisely the case for a property path and an anonymous node:

prefix bag: <http://bag.basisregistraties.overheid.nl/def/bag#>
prefix geo: <http://www.opengis.net/ont/geosparql#>

select ?shape
where {
  [ a bag:Pand;
    geo:hasGeometry/geo:asWKT ?shape ].
}
limit 1

Same query, two fewer moving parts.

A simpler predicate exists for point locations: geo:location carries coordinates directly, without the geometry step. Use it when a single point is all you have.

What happens to a WKT literal after it is returned — whether it lands on a map, gets a colour or a height — is a property of the tool displaying it, not of SPARQL. See Data views.

Statistics

Statistical data uses the RDF Data Cube vocabulary, which is a linked data expression of the same model behind SDMX. Three concepts carry it:

Term What it is
Observation One measured value — the equivalent of a single cell
Dimension What identifies that value: place, year, age group
Measure What was actually measured: life expectancy, population

An observation states its dimensions and its measure together:

observation:0007ddade4
  a qb:Observation;
  qb:dataSet dataset:countries;
  dimension:location country:Netherlands;
  dimension:year "2002"^^xsd:gYear;
  measure:lifeExpectancy 7.9696e1.

A fourth piece, the data structure definition, declares which dimensions and measures a dataset uses. Read it first when you meet an unfamiliar cube — it is the table of contents.

Querying a cube is nearly always the same move: fix the dimensions you are not interested in, vary the one you are, and return the measure. Fix the year and vary the country to compare places; fix the country and vary the year to see a trend.

prefix dimension: <...>
prefix measure:   <...>
prefix qb:        <http://purl.org/linked-data/cube#>
prefix rdfs:      <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd:       <http://www.w3.org/2001/XMLSchema#>

select ?countryName ?value
where {
  ?observation
    a qb:Observation;
    dimension:year "2007"^^xsd:gYear;
    dimension:location ?country;
    measure:lifeExpectancy ?value.
  ?country rdfs:label ?countryName.
}

That is a chart: one column of labels, one column of numbers.

Where this appears in Triply products

  • TriplyDB's Insights page builds its class hierarchy from rdfs:subClassOf, which is the first pattern on this page. See Data views.
  • These three shapes are the backbone of Triply's public-sector work: cadastral and building data, national statistics, and controlled vocabularies in the cultural heritage sector.

Where to next