Search code examples
rdfshacl

SHACL to compare values on two different nodes?


I am trying to write a SHACL constraint for a date comparison where the start date must be less than or equal to an end date. When the dates are attached to the same node using the :beginDate and :endDate predicates, the constraint is straight forward:

:StartEndRuleShape a :PropertyShape  ;
  sh:path              :beginDate ;
  sh:lessThanOrEquals  :endDate ;
  sh:message "Begin Date is after End Date." .

The real world model is more complex. In the attached diagram, note how an :AnimalSubject hasReferenceInterval. ReferenceInterval IRIs have a :ReferenceBegin and a :ReferenceEnd, which are in turn assigned the date value using the time:inXSDDate predicate. How can I apply the constraint in this case to ensure the ReferenceBegin value is equal to or less than ReferenceEnd value? Is this a case for using SHACL-SPARQL, or sequencePath ? I've been unable to find good examples of either. Cheers! enter image description here

I tested the following SHACL-SPARQL on data that violates the constraint: ReferenceBegin = "2016-12-07", ReferenceEnd = "2016-12-06", but the Validation Report does not detect the violation. If I run the SPARQL on its own, it does pick out the observation. Any thoughts on why? I am using Stardog/Stardog Studio and have also posted on their user support platform.

@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix sh:  <http://www.w3.org/ns/shacl#> .
@prefix time: <http://www.w3.org/2006/time#> .
@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .
@prefix :     <http://foo.bar.org/> .

:IntervalShape a sh:NodeShape ;
 sh:targetClass :ReferenceInterval ;
 sh:sparql [
  a sh:SPARQLConstraint ;
  sh:message "End Date must be greater than or equal to Begin Date";
  sh:prefixes [
    sh:declare [
      sh:prefix "time" ;
      sh:namespace "http://www.w3.org/2006/time#"^^xsd:anyURI ;
    ] 
  ] ;
 sh:select
  """SELECT $this (?beginDate AS ?intervalStart) (?endDate AS ?intervalEnd)
    WHERE {
      $this     :hasReferenceInterval ?interval .
      ?interval :ReferenceBegin       ?beginIRI ;
                :ReferenceEnd         ?endIRI .
      ?beginIRI time:inXSDDate        ?beginDate .
      ?endIRI   time:inXSDDate        ?endDate .
      FILTER  (! (?endDate >= ?beginDate ))
    }""" ;
] .

Solution

  • It looks like your constraint has two issues:

    1. You declare the time prefix in your SHACL, but not the base prefix. You want:

      sh:prefixes [
          sh:declare [
            sh:prefix "time" ;
            sh:namespace "http://www.w3.org/2006/time#"^^xsd:anyURI ;
          ], [
            sh:prefix "" ;
            sh:namespace "http://foo.bar.org/"^^xsd:anyURI ;
          ]
        ] ;
      
    2. Your focus node is :ReferenceInterval, however the way your query is written, $this will only ever be bound to entities that :hasReferenceInterval [a :ReferenceInterval]. I rewrote the query so that ?interval is now $this as follows:

      sh:select
        """SELECT $this (?beginDate AS ?intervalStart) (?endDate AS ?intervalEnd)
          WHERE {
            $this     :ReferenceBegin       ?beginIRI ;
                      :ReferenceEnd         ?endIRI .
            ?beginIRI time:inXSDDate        ?beginDate .
            ?endIRI   time:inXSDDate        ?endDate .
            FILTER  (! (?endDate >= ?beginDate ))
          }""" ;
      

      Adding this constraint, I was able to see a violation.