Search code examples
sparqlrdfblank-nodes

Produce the same blank node in CONSTRUCT across multiple solutions


I've come across several occasions where I've wanted to use blank nodes in a CONSTRUCT query, but I've needed to get the same blank node for all (or several) query solutions.

Let's say that we have in our dataset a list of authors and books they have written:

@prefix : <http://example.org#> .

:stephen_king a :Author ;
      :firstName "Stephen" ;
      :authorOf "The Shining", "Cujo".

:stephen_hawking a :Author ;
      :firstName "Stephen" ;
      :authorOf "A Brief History of Time" . 

:virginia_wolf a :Author ;
      :firstName "Virginia" ;
      :authorOf "Mrs Dalloway" . 

For example, let's say I'd like to bind all books written authors with the first name Stephen to a single blank node:

PREFIX : <http://example.org#>
CONSTRUCT {
   [ :containsBook ?book ]
}
WHERE {
   ?book ^:authorOf/:firstName "Stephen" .
}

Would return something like:

[ :containsBook "The Shining" ] .
[ :containsBook "A Brief History of Time" ] .
[ :containsBook "Cujo" ] .

but the desired outcome was:

[ :containsBook "The Shining" ;
  :containsBook "A Brief History of Time" ;
  :containsBook "Cujo" ] .

Any ideas on how to achieve this?


Solution

  • After having pondered this for some time I came up with something that I think is a general solution. I haven't tried it on many SPARQL implementations yet though so please provide feedback if you find that it isn't working for you. Basically, after realizing that we can't do anything about how SPARQL processes the data in the result part of the query we naturally start thinking about binding the blank node to a variable instead. So, let's say we try something like:

    PREFIX : <http://example.org#>
    CONSTRUCT {
       ?bnode :containsBook ?book
    }
    WHERE {
       ?book :hasAuthor/:firstName "Stephen" .
       BIND(BNODE() AS ?bnode)
    }
    

    No, that won't work either. Why is that? The query evaluates the entire query and for each solution the BIND function will be called again and we will end up with different blank nodes.

    Now here's the trick. Place the BIND part of the query within a group. That way, due to the way SPARQL works in terms of variable scoping, we will end up with a join after the query has been evaluated where the ?bnode part will have been called only once:

    PREFIX : <http://example.org#>
    CONSTRUCT {
       ?bnode :containsBook ?book 
    }
    WHERE {
       ?book :hasAuthor/:firstName "Stephen" .
       { BIND(BNODE() AS ?bnode) }
    }
    

    Hope someone finds this as useful as I did. Cheers.