2

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?

1 Answers1

5

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.

  • 1
    Why it works with braces `{ }` is because that one denotes a basic graph pattern, and for blank nodes the following holds: *"When using blank nodes of the form _:abc, labels for blank nodes are scoped to the basic graph pattern. "* – UninformedUser Sep 11 '17 at 14:33
  • 1
    So, the BIND is executed once in the separate BGP, thus, you have a single blank node, and this is then joined with the outer BGB. – UninformedUser Sep 11 '17 at 14:51
  • 3
    Or put the `BIND` first: ```WHERE { BIND(BNODE() AS ?bnode) ?book :hasAuthor/:firstName "Stephen" . }```. With BIND, order does matter - it binds to what goes before it. – AndyS Sep 11 '17 at 19:12
  • I didn't consider that the order would matter for `BIND`. Is the order of execution of the query strict, or can query optimization algorithms mess this up? (i.e. are optimizers *allowed* to mess with the order?) – Robin Keskisarkka Sep 15 '17 at 09:13