Search code examples
pythonsparqlowl

Recursive SPARQL query for a combination of triples


I have the following query that I run recursively in Python using ontospy:

SELECT ?c WHERE {
    ?c rdfs:subClassOf ?restriction .
    ?restriction owl:onProperty :has_part ; owl:someValuesFrom ?p .
    VALUES ?p { <some_uri> }
}

Basically, I take the values returned from that and re-run the query to follow a hierarchy of "has part some" relationships within an ontology. I am hoping to avoid making multiple SPARQL queries by adding the recursion to the query itself. I know how to do this for a single triple using rdfs:subClassOf* but cannot figure out the syntax for combining the two triples:

?c rdfs:subClassOf ?restriction .
?restriction owl:onProperty :has_part ; owl:someValuesFrom ?p .

Is this possible?


Solution

  • I can't give a formal proof, but this looks impossible. This is not what property paths were designed for, and why some extensions exist (1, 2).

    Under certain commitments (e.g. with tree-like structures), it is possible to figure out something using FILTER NOT EXISTS, however, this is not a general solution.

    The idea is to do this in two queries. In essence, this is SELECT over CONSTRUCT. By the way, such SPARQL extension has already been proposed.


    Let's use on which Ontospy is based, because

    Ontospy does not offer any ontology-editing features, nor it can be used to interrogate a triplestore.

    Input (ontology.ttl)

    @prefix : <http://www.example.org/ontology#> .
    @prefix owl: <http://www.w3.org/2002/07/owl#> .
    @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
    @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
    @base <http://www.example.org/ontology> .
    
    <http://www.example.org/ontology> rdf:type owl:Ontology .
    
    :hasPart rdf:type owl:ObjectProperty .
    
    :Country rdf:type owl:Class ;
             rdfs:subClassOf [ rdf:type owl:Restriction ;
                               owl:onProperty :hasPart ;
                               owl:someValuesFrom :State
                             ] .
    
    :State rdf:type owl:Class ;
           rdfs:subClassOf [ rdf:type owl:Restriction ;
                             owl:onProperty :hasPart ;
                             owl:someValuesFrom :City
                           ] .
    
    :City rdf:type owl:Class .
    

    Python code

    import rdflib
    
    g = rdflib.Graph()
    g.parse("ontology.ttl", format="n3")
    
    qres = g.update(
        """PREFIX : <http://www.example.org/ontology#> 
           INSERT { ?c :hasSome ?p } 
           WHERE  { ?c rdfs:subClassOf [ owl:onProperty :hasPart ; 
                                         owl:someValuesFrom ?p ] }""")
    
    qres = g.query(
        """PREFIX : <http://www.example.org/ontology#> 
           SELECT ?a ?b WHERE {?a :hasSome+ ?b }""")
    
    for row in qres:
        print("%s :hasSome+ %s" % row)
    
    qres = g.update(
        """PREFIX : <http://www.example.org/ontology#> 
           DELETE { ?s :hasSome ?o } WHERE { ?s :hasSome ?o }""")
    

    Output

    :Country :hasSome+ :State
    :State   :hasSome+ :City
    :Country :hasSome+ :City
    

    If you don't want to modify initial RDFLib graph, just create another one:

    import rdflib
    
    g1 = rdflib.Graph()
    g1.parse("ontology.ttl", format="n3")
    
    qres = g1.query(
        """PREFIX : <http://www.example.org/ontology#> 
           CONSTRUCT {?c :hasSome ?p } WHERE {
               ?c rdfs:subClassOf [ owl:onProperty :hasPart ;
                                    owl:someValuesFrom ?p  ] }""")
    
    g2 = rdflib.Graph();
    for triple in qres: # quite a few triples
        g2.add(triple)
    
    qres = g2.query(
        """PREFIX : <http://www.example.org/ontology#> 
           SELECT ?a ?b WHERE { ?a :hasSome+ ?b }""")
    
    for row in qres:
        print("%s :hasSome+ %s" % row)
    

    Probably you can use transitiveClosure() or transitive_objects() instead of the second query in both cases.