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?
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 rdflib 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.