Search code examples
sparqlowlprotege

How can retrieve relation between 2 class with object property using sparql query


Assume we have an OWL file which contains follows the following properties with domains and ranges:

Domain   Property    Range
----------------------------
tour     hascountry  country
country  hascity     city
city     hasward     ward
ward     hashouse    house

Using SPARQL, how can I get results "between" the Tour and House classes? That is, properties whose domains and ranges such there's a "path" from Tour to the domain and from the range to House. With just these two classes, how could we find results like the following? It seems like some kind of loop might be necessary, but I don't know how to do that in SPARQL.

|tour    -------- (hascountry) ----- country|
|country -------- (hascity)    ----- city   |
|city    -------- (hasward)    ----- ward   |
|ward    -------- (hashouse)   ----- house  |

Solution

  • First, it's always easier to work with some real data. We can't write real SPARQL queries against data that we don't have. In the future, please be sure to provide some sample that we can work with. For now, here's some sample data that describes the domains and ranges of the properties that you mentioned. Also note that properties don't connect classes; properties connect individuals. Properties can have domains and ranges, and that provides us with a way to infer additional information about the individuals that are related by the property. Anyhow, here's the data:

    @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
    @prefix : <https://stackoverflow.com/q/29737549/1281433/> .
    
    :hascountry rdfs:domain :tour    ; rdfs:range :country .
    :hascity    rdfs:domain :country ; rdfs:range :city .
    :hasward    rdfs:domain :city    ; rdfs:range :ward .
    :hashouse   rdfs:domain :ward    ; rdfs:range :house .
    

    Now, note that you could get from :tour to :country if you follow the rdfs:domain property backward to :hascountry, and then follow the rdfs:range property forward to :country. In SPARQL, you can write that as a property path:

    :tour ^rdfs:domain/rdfs:range :country
    

    If you can follow chains of that property path, you can find all the properties that are "between" :tour and :house:

    prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
    prefix : <https://stackoverflow.com/q/29737549/1281433/>
    
    select ?domain ?property ?range  where {
      #-- find ?properties that have a domain
      #-- and range...
      ?property rdfs:domain ?domain ;
                rdfs:range ?range .
    
      #-- where there's a ^rdfs:domain to rdfs:range
      #-- chain from :tour to ?domain...
      :tour (^rdfs:domain/rdfs:range)* ?domain .
    
      #-- and from ?range to :house.
      ?range (^rdfs:domain/rdfs:range)* :house .
    }
    

    -------------------------------------
    | domain   | property    | range    |
    =====================================
    | :ward    | :hashouse   | :house   |
    | :city    | :hasward    | :ward    |
    | :country | :hascity    | :city    |
    | :tour    | :hascountry | :country |
    -------------------------------------
    

    Getting the results "in order"

    If you want the properties "in order" from the start class to the end class, you can compute the distance from the start class to each property and order by that. You can do that using the technique in my answer to Is it possible to get the position of an element in an RDF Collection in SPARQL?. Here's what it looks like as a SPARQL query:

    prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
    prefix : <https://stackoverflow.com/q/29737549/1281433/>
    
    select ?domain ?property ?range
           (count(?mid) as ?dist)
    where {
      #-- find ?properties that have a domain
      #-- and range...
      ?property rdfs:domain ?domain ;
                rdfs:range ?range .
    
      #-- where there's a ^rdfs:domain to rdfs:range
      #-- chain from :tour to ?domain...
      :tour (^rdfs:domain/rdfs:range)* ?domain .
    
      #-- and from ?range to :house.
      ?range (^rdfs:domain/rdfs:range)* :house .
    
      #-- then, compute the "distance" from :tour
      #-- to the property.  This is based on binding
      #-- ?mid to each class in between them and
      #-- taking the number of distinct ?mid values
      #-- as the distance.
      :tour (^rdfs:domain/rdfs:range)* ?mid .
      ?mid (^rdfs:domain/rdfs:range)* ?domain .
    }
    group by ?domain ?property ?range
    order by ?dist
    

    --------------------------------------------
    | domain   | property    | range    | dist |
    ============================================
    | :tour    | :hascountry | :country | 1    |
    | :country | :hascity    | :city    | 2    |
    | :city    | :hasward    | :ward    | 3    |
    | :ward    | :hashouse   | :house   | 4    |
    --------------------------------------------
    

    I included ?dist in the select just so we could see the values. You don't have to select it in order to sort by it. You can do this too:

    select ?domain ?property ?range {
      #-- ...
    }
    group by ?domain ?property ?range 
    order by count(?mid)
    

    -------------------------------------
    | domain   | property    | range    |
    =====================================
    | :tour    | :hascountry | :country |
    | :country | :hascity    | :city    |
    | :city    | :hasward    | :ward    |
    | :ward    | :hashouse   | :house   |
    -------------------------------------