Search code examples
sparql

Position of VALUES seems to matter when used in BIND in SPARQL


I'm trying to understand how VALUES behaves regarding the position of it in the query. The idea is to use this for easier parametrization of SPARQL queries that come from a template.

For most queries it worked as expected but I now ran into a case that I don't understand. Given the following query:

SELECT * WHERE {

  # Hard coded version: 

  #BIND( (2020 - 2) AS ?year )

  # Works as expected:
  
  #VALUES ?startYear { 2020 }
  #BIND( (?startYear -2 ) AS ?year )

  # Does not work as expected: 
  # Year is "1" in Fuseki, empty in Stardog. The "-2" does not seem to have any effect in that scenario
  BIND( (?startYear -2 ) AS ?year )
  VALUES ?startYear { 2020 }
} 

As you can see in the comments, the last one does not work. If the VALUES is before the BIND, everything seems as expected. In the SPARQL 1.1 spec, there is an example where they move VALUES around but I can't find an explanation for what I see here.

Why does it not work when VALUES is at the end of the query when I use it in a BIND? It does work well in FILTER in that case.


Solution

  • It's the position of BIND that matters, not VALUES. BIND in SPARQL always closes the group graph pattern, i.e. it goes on top of all patterns in the same {} block which precede it, see https://www.w3.org/TR/sparql11-query/#bind

    It becomes evident if you look at the query structure (the so-called "algebra") at http://www.sparql.org/query-validator.html:

    SELECT *
    WHERE {  
      VALUES ?startYear { 2020 }
      BIND( (?startYear -2 ) AS ?year )
    }
    

    has this algebra:

      9     (extend ((?year (- ?startYear 2)))
     10       (table (vars ?startYear)
     11         (row [?startYear 2020])
     12       ))))
    

    the table is your VALUES and the extend is your BIND. The BIND is evaluated on top of VALUES and thus ?startYear has a value when you deduct 2.

    SELECT *
    WHERE {  
      BIND( (?startYear -2 ) AS ?year )
      VALUES ?startYear { 2020 }
    }
    

    however, has the following algebra:

      9     (join
     10       (extend ((?year (- ?startYear 2)))
     11         (table unit))
     12       (table (vars ?startYear)
     13         (row [?startYear 2020])
     14       ))))
    

    Here nothing precedes BIND in its block and thus you see table unit as its argument (it's the so-called empty group). It doesn't bind any variables so ?startYear has no value when BIND is evaluated. The result of BIND is then joined with VALUES but it's too late since - 2 already happened.

    I always recommend using that online tool for checking semantics of your queries.