Search code examples
sparql

SPARQL returning empty result when passing MIN(?date1) from subquery into outer query, with BIND((YEAR(?minDate) - YEAR(?date2)) AS ?diffDate)


<This question is now resolved, see comment by Valerio Cocchi>

I am trying to pass a variable from a subquery, that takes the minimum date of a set of dates ?date1 belonging to ?p and passes this to the outer query, which then takes another date ?date2 belonging to ?p (there can be at most 1 ?date2 for every ?p) and subtracts ?minDate from ?date2 to get an integer value for the number of years between. I am getting a blank value for this, i.e. ?diffDate returns no value.

I am using Fuseki version 4.3.2. Here is an example of the query:

SELECT ?p ?minDate ?date2 ?diffDate 
  {        
  ?p a abc:P;
    abc:hasAnotherDate ?date2.      
    BIND((YEAR(?minDate) - YEAR(?date2)) AS ?diffDate)      
  {
  SELECT ?p (MIN(?date1) as ?minDate) 
  WHERE 
  { 
  ?p a abc:P;
    abc:hasDate ?date1. 

  } group by ?p 
    }                                 
  } 

and an example of the kind of result I am getting:

|-?p----|-----------------?minDate-------------|-----------------?date2------------- |?diffDate|

|<123>|20012-11-22T00:00:00"^^xsd:dateTime|2008-08-18T00:00:00"^^xsd:dateTime| |

I would expect that ?diffDate would give me an integer value. Am I missing something fundamental about how subqueries work in SPARQL?


Solution

  • It seems you have encountered quite an obscure part of the SPARQL spec, namely how BIND works.

    Normally SPARQL is evaluated without regard for the position of atoms, i.e.

    SELECT *
    WHERE {
    ?a :p1 ?b .
    ?b :p2 ?c .}
    

    is the same query as:

    SELECT *
    WHERE {
    ?b :p2 ?c .
    ?a :p1 ?b .}
    

    However, BIND is position dependent, so e.g.:

    SELECT *
    WHERE {
    ?a :p1 ?b .
    BIND(:john AS ?a)}
    

    is not a valid query, whereas:

    SELECT *
    WHERE {
    BIND(:john AS ?a)
    ?a :p1 ?b .
    }
    

    is entirely valid. The same applies to variables used inside of the BIND, which must be declared before the BIND appears. See here for more.

    To go back to your problem, your BIND is using the ?minDate variable before it has been bound, which is why it fails to produce a value for ?diffDate.

    This query should do the trick:

    SELECT ?p ?minDate ?date2 ?diffDate 
      {        
      ?p a abc:P;
        abc:hasAnotherDate ?date2.      
           
      {
      SELECT ?p (MIN(?date1) as ?minDate) 
      WHERE 
      { 
      ?p a abc:P;
        abc:hasDate ?date1. 
    
      } group by ?p 
        }
    BIND((YEAR(?minDate) - YEAR(?date2)) AS ?diffDate)   #Put the BIND after all the variables it uses are bound.
                                 
      } 
    

    Alternatively, you could evaluate the difference in the SELECT, like so:

    SELECT ?p ?minDate ?date2 (YEAR(?minDate) - YEAR(?date2) AS ?diffDate)
      {        
      ?p a abc:P;
        abc:hasAnotherDate ?date2.      
    
      {
      SELECT ?p (MIN(?date1) as ?minDate) 
      WHERE 
      { 
      ?p a abc:P;
        abc:hasDate ?date1. 
    
      } group by ?p 
        }
    
      }