SPARQL: construct, ask, describe and federation

The three chapters before this one have used select, which returns a table. The other three query forms return something else, and one of them — construct — is the reason SPARQL is a transformation language and not only a query language.

This chapter also covers federation: querying data that lives on someone else's server as if it were your own.

It assumes SPARQL: the basics and graph patterns.

construct: a query that returns triples

construct matches a pattern like any other query, but instead of filling a table it builds triples. Because its output is a graph, that output can be loaded straight back into a dataset. Matching and writing use the same language.

There are two kinds.

construct where returns exactly what it matched. The graph that comes out has the same shape as the graph that went in:

prefix pokemon: <https://triplydb.com/academy/pokemon/id/pokemon/>

construct where {
  pokemon:pikachu ?p ?o.
}

That returns every direct property of Pikachu. Reach deeper by adding patterns — here, following into the audio resource as well:

prefix pokemon: <https://triplydb.com/academy/pokemon/id/pokemon/>
prefix schema:  <http://schema.org/>

construct where {
  pokemon:pikachu
    ?p ?o;
    schema:audio ?audio.
  ?audio ?y ?z.
}

A templated construct separates the two graphs. The where clause says what to match; the construct clause says what to build. They need not look alike, which is what makes this a translation:

prefix species: <https://triplydb.com/academy/pokemon/id/species/>
prefix vocab:   <https://triplydb.com/academy/pokemon/vocab/>

construct {
  ?pokemon a ?species.
}
where {
  ?pokemon vocab:species ?species.
  filter(?species in (species:dragon, species:mouse))
}

The source data relates a Pokémon to its species through vocab:species. The output states the same fact as a class membership. Nothing was edited; a new shape was derived from an old one.

This is the mechanism behind most data migration in linked data. When a model changes, you write a construct that reads the old shape and emits the new one.

ask: a yes or no

ask returns true or false: does anything match this pattern? It does not return the matches themselves, which makes it cheap.

Use it for checks. "Does any product lack a manufacturer?" is an ask query, and a scheduled ask is a serviceable data quality alarm.

For structural quality checking at scale, SHACL is the better tool.

describe: triples about a resource

describe takes an IRI and returns triples about it. It is the quickest way to find out what a dataset holds about something.

The catch is that what describe returns is not standardised. Every implementation decides how much of the surrounding graph to include, and that is intentional — the form exists as a lookup for IRIs you cannot otherwise resolve.

That matters as soon as you build on it. When you need to be certain which triples come back, use construct, which states exactly that.

And if you are reaching for describe to resolve an IRI, note that a plain HTTP request already does that when the IRI is set up properly:

curl -H "Accept: application/n-triples" -L https://triplydb.com/academy/pokemon/id/pokemon/pikachu

Which is a reason to make your own IRIs resolvable.

Changing stored data: SPARQL Update

construct returns a graph; it does not change one. SPARQL Update is the separate language for that, with insert data, delete data and delete/insert … where.

base <https://triplydb.com/>

insert data { <john> <knows> <mary>. }

Keep the distinction clear: construct derives, Update mutates. Where both are available, construct is usually the safer of the two because it leaves the source untouched.

Federation: querying someone else's data

A federated query runs partly on your endpoint and partly on another. The service keyword marks the part that travels:

prefix owl:     <http://www.w3.org/2002/07/owl#>
prefix pokemon: <https://triplydb.com/academy/pokemon/id/pokemon/>

construct {
  pokemon:pikachu ?p ?o.
}
where {
  pokemon:pikachu owl:sameAs ?dbpediaResource.
  service <https://dbpedia.org/sparql> {
    ?dbpediaResource ?p ?o.
    filter(isLiteral(?o))
  }
}
graph LR local["Your endpoint"] -- "owl:sameAs" --> bridge(("shared IRI")) bridge --> remote["Remote endpoint\n(service …)"] remote -- results --> local

The precondition is easy to miss: the two endpoints must share at least one term. Without an IRI or literal in common there is nothing to join on. In practice you create that overlap deliberately with owl:sameAs, which is exactly the "link" in linked data.

For a sense of what is reachable this way, see the Linked Open Data Cloud — every dot is a queryable dataset.

Two cautions before you build on federation. A federated query is only as available as the slowest endpoint in it, and remote endpoints impose their own result limits. Treat it as a way to explore and to prototype integrations, not as a substitute for loading data you depend on.

Where this appears in Triply products

  • Saved queries turn a construct query into an API that returns triples rather than a table. See Saved queries.

Where to next