Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 18: Asking Questions

Friday afternoon. The board wanted numbers: how many patients of each species, which vets had no one on their list, which dog owners could be reached by email. The data was all there, typed, checked and linked. Dr. Portbridge just needed a way to ask.


The problem

Facts and mappings fill the graph. Rules add to it. But nothing so far reads it back. In Chapter 12 a downstream system found the senior cats with a SPARQL query, written by hand, in another language. Dolfin has its own way to ask: the query.

A first query


query all_cats:
  ?cat a Cat
    name ?name
    age ?age

This reads like the shape it looks for: a cat, with a name and an age. Every cat that has both gives one row, with columns cat, name and age.

Video 3.3 · Dolfin queries, opening (0:04–0:55). Watch on PeerTube

A query is translated to SPARQL. This one becomes:

SELECT ?cat ?name ?age
WHERE {
  ?cat a Cat .
  ?cat name ?name .
  ?cat age ?age .
}

The indented lines all talk about the subject above them, so ?cat is written once instead of on every line. Patterns are combined with implicit AND, as in the match: block of a rule.

Choosing the columns

The board wants the five oldest cats past ten, oldest first. A constraint filters, and a return block shapes the result:


query senior_cats:
  ?cat a Cat
    name ?name
    age [?age >= 10]
  return
    ?name
    ?age
      order desc
    limit 5
  • age [?age >= 10] binds the age to ?age and keeps only ages of 10 and more.
  • return lists the columns. ?cat is not in it, so it is not returned.
  • order desc under a column sorts on it; limit 5 keeps five rows.

Without a return block, every variable is returned. A variable that starts with ?_ is silent: it is used for matching but never returned.

Owners are not animals: they hang off the owner property. A [ … ] block opens a nested subject, without naming it:


query owners_of_dogs:
  a Dog
    name ?dogName
    owner [
      lastName ?ownerName
      optional email ?email
    ]

The dog has no variable either: a Dog alone is enough when nothing else refers to it. email is optional in the schema, so the line is marked optional: an owner without an email still gives a row, with an empty email column. Without the keyword, those owners would vanish from the result.

Links can be followed backwards. treatedBy goes from an animal to a vet; to start from the vet, read it with is … of:


?vet is treatedBy of ?animal
Video 3.3 · Dolfin queries, chapter “Read a property backwards” (0:54–1:40). Watch on PeerTube

Something, or nothing

Which vets have no patients? This is a question about something that is not there. A none: block keeps a row only if its content never matches:


query vets_without_patients:
  ?vet a Veterinarian
    name ?name
  none:
    ?vet is treatedBy of ?_animal

some: is the opposite: the row stays if the content matches at least once. Inside both, ?vet is the same vet as outside; ?_animal is local.

And when either of two conditions will do, either … or says so:


query heavy_or_old:
  ?pet a Animal
    name ?name
    either weight [ > 30.0 ]
    or age [ > 12 ]

Counting

How many patients of each species? A group by block groups the rows and computes one value per group:


query animals_per_species:
  ?animal a Animal
    species ?species
  group by ?species
    count ?animal as ?total
    ?total >= 2
  return
    ?species
    ?total
      order desc

count ?animal as ?total counts the animals in each species. The line ?total >= 2 drops species with a single patient. sum, average, min and max work the same way as count. count distinct ?x counts each value of ?x once: count distinct ?owner as ?owners counts owners, not the animals that lead to them.

To count per animal while keeping one row per animal, put the count in the body, with what it counts indented below it:


query vaccine_count:
  ?animal a Animal
    name ?name
  count ?shot as ?shots
    ?animal vaccinations ?shot

?animal appears above the count, so each animal gets its own ?shots.

Building on other queries

A query can use another one. Write the question once, reuse it:


query cats_with_vet:
  ?cat a Cat
    treatedBy ?vet
  return
    ?cat
    ?vet

query cat_vet_names:
  cats_with_vet as [
    cat ?c
    vet ?v
  ]
  ?c name ?catName
  ?v name ?vetName

cats_with_vet as [ … ] runs the first query and binds its cat and vet columns to ?c and ?v. A query with a single column binds in one line: some_query as ?x.

Summary

WriteTo
query name:Declare a query
?x a Type / a TypeLook for instances, named or not
prop ?vRead a property
prop [?v > 3]Read and filter it
optional prop ?vRead it if it is there
prop [ … ]Follow a link
is prop of ?xFollow a link backwards
some: / none:Require that something exists, or does not
either … orAccept any of several conditions
group by ?x + count ?y as ?nCount, sum, average per group
returnChoose, rename, sort and limit the columns
other_query as [ … ]Reuse a query

The syntax reference has every form, and the SPARQL each one becomes.


The board meeting lasted twenty minutes. Dr. Portbridge answered every question by running a query she had written that morning. When the treasurer asked for one more number, she wrote a new one on the spot, four lines long.