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

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 |

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 |
-------------------------------------

Related

Is there a "DISTINCT ON" equivalent in SPARQL?

My data is basically an event log in RDF. I have cases and events, the latter belong to the former. Events have timestamps and an actor who triggered them.
For each case I now need the latest event, when it happened, and who triggered it.
This is roughly my current query:
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ex: <http://example.org/>
SELECT ?case ?event ?timestamp ?actor
WHERE {
?case rdf:type ex:Case ;
ex:hasEvent ?event .
?event ex:timestamp ?timestamp ;
ex:hasActor ?actor .
}
ORDER BY ASC(?case) DESC(?timestamp)
Which yields something like this:
| case | event | timestamp | actor |
=================================================================================
| ex:case1 | ex:event1 | "2020-01-01T02:00:00Z"^^xsd:dateTimeStamp | ex:Alice |
| ex:case1 | ex:event2 | "2020-01-01T01:00:00Z"^^xsd:dateTimeStamp | ex:Bob |
| ex:case2 | ex:event3 | "2020-01-01T03:00:00Z"^^xsd:dateTimeStamp | ex:Charlie |
| ex:case2 | ex:event4 | "2020-01-01T02:00:00Z"^^xsd:dateTimeStamp | ex:Dan |
However I would like to only get the first and third row, as they correspond to the latest events for this case. Like this:
| case | event | timestamp | actor |
=================================================================================
| ex:case1 | ex:event1 | "2020-01-01T02:00:00Z"^^xsd:dateTimeStamp | ex:Alice |
| ex:case2 | ex:event3 | "2020-01-01T03:00:00Z"^^xsd:dateTimeStamp | ex:Charlie |
In order to achieve this I tried to use SELECT ?case ?event (MAX(?timestamp) AS ?latest) ?actor combined with GROUP BY ?case however SPARQL complains I need to group by ?event and ?actor as well which is not what I want of course.
I am aware that PostgreSQL has DISTINCT ON which would solve my problem, but I need to do it in SPARQL. Is there a nice way to achieve this?
Self answer based on #UninformedUser's comment:
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ex: <http://example.org/>
SELECT ?case ?event (?latest as ?timestamp) ?actor WHERE {
?case ex:hasEvent ?event .
?event ex:timestamp ?latest ;
ex:hasActor?actor .
{ SELECT ?case (MAX(?timestamp) AS ?latest) {
?case rdf:type ex:case ;
ex:hasEvent ?event .
?event ex:timestamp ?timestamp }
group by ?case }
}

SPARQL limit the result for each value of a varible

This is the minimum data required to reproduce the problem
#prefix : <http://example.org/rs#>
#prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
#prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
:artist1 rdf:type :Artist .
:artist2 rdf:type :Artist .
:artist3 rdf:type :Artist .
:en rdf:type :Language .
:it rdf:type :Language .
:gr rdf:type :Language .
:c1
rdf:type :CountableClass ;
:appliedOnClass :Artist ;
:appliedOnProperty :hasArtist
.
:c2
rdf:type :CountableClass ;
:appliedOnClass :Language ;
:appliedOnProperty :hasLanguage
.
:i1
rdf:type :RecommendableClass ;
:hasArtist :artist1 ;
:hasLanguage :en
.
:i2
rdf:type :RecommendableClass ;
:hasArtist :artist1 ;
:hasLanguage :en
.
:i3
rdf:type :RecommendableClass;
:hasArtist :artist1 ;
:hasLanguage :it
.
:i4
rdf:type :RecommendableClass;
:hasArtist :artist2 ;
:hasLanguage :en
.
:i5
rdf:type :RecommendableClass;
:hasArtist :artist2 ;
:hasLanguage :it
.
:i6
rdf:type :RecommendableClass;
:hasArtist :artist3 ;
:hasLanguage :gr
.
:ania :likes :i1 .
:ania :likes :i3 .
:ania :likes :i4 .
This is my query
PREFIX : <http://example.org/rs#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rs: <http://spektrum.ctu.cz/ontologies/radio-spectrum#>
SELECT ?item ?count ?value
WHERE
{ ?item rdf:type :RecommendableClass
{ SELECT ?countableProperty ?value (count(*) AS ?count)
WHERE
{ VALUES ?user { :ania }
VALUES ?countableConfiguration { :c1 }
?user :likes ?x .
?countableConfiguration :appliedOnProperty ?countableProperty .
?countableConfiguration :appliedOnClass ?countableClass .
?x ?countableProperty ?value .
?value rdf:type ?countableClass
}
GROUP BY ?countableProperty ?value
ORDER BY DESC(?count)
LIMIT 3
}
FILTER NOT EXISTS {?user :likes ?item}
}
This is the result:
As you see, there're three items that have value artist1 and three other that have artist2
is there any way so i can limit the result to just 2 for each them
First some minimal data, with three artists, and some items for each one. I always stress the point of minimal data on Stack Overflow, because it's important for isolating the problem. In this case, you've still provided a relatively large query and a lot more data that we need. Since we know the problem is in how to group artists that are each related to a number of items, all the data needs here is some artists that are related to a number of items. Then we can retrieve them easily, and group them easily.
#prefix : <urn:ex:> .
:artist1 :p :a1, :a2, :a3, :a4 .
:artist2 :p :b2, :b2, :b3, :b4, :b5 .
:artist3 :p :c2 .
Now, you can select artists and their items, and you can determine an index for each item. This method checks for each item how many other items there are that are less than equal to it (there's always at least one equal to it (itself), so the counts are essentially a 1-based index).
prefix : <urn:ex:>
select ?artist ?item (count(?item_) as ?pos){
?artist :p ?item_, ?item .
filter (str(?item_) <= str(?item))
}
group by ?artist ?item
-------------------------
| artist | item | pos |
=========================
| :artist1 | :a1 | 1 |
| :artist1 | :a2 | 2 |
| :artist1 | :a3 | 3 |
| :artist1 | :a4 | 4 |
| :artist2 | :b2 | 1 |
| :artist2 | :b3 | 2 |
| :artist2 | :b4 | 3 |
| :artist2 | :b5 | 4 |
| :artist3 | :c2 | 1 |
-------------------------
Now you can use having to filter on the position, so that you get at most two per artist:
prefix : <urn:ex:>
select ?artist ?item {
?artist :p ?item_, ?item .
filter (str(?item_) <= str(?item))
}
group by ?artist ?item
having (count(?item_) < 3)
-------------------
| artist | item |
===================
| :artist1 | :a1 |
| :artist1 | :a2 |
| :artist2 | :b2 |
| :artist2 | :b3 |
| :artist3 | :c2 |
-------------------
References
Doing "n per each x" queries in SPARQL is kind of challenge, and there's no great solution for it yet. Some related reading that might help (be sure to check the comments on these questions and answers, too), include:
SPARQL using subquery with limit (subqueries with limits can sometimes be helpful)
How to select first N row of each group (canonical question, in my opinion, but has no answer, since there's no general answer)
Find the two nearest neighbors of points (recent question with a "hack" answer)

extracting a chain of instances connecting two instances through a relatio

I want to extract a chain of instances between two instances of my ontology by asking a SPARQL query. for example in the following figure if I want to know how A is connected to E, the result of query should be something like a list of A, B, D, F, E.
how the ontology should be designed and query should be built?
Is it even possible?
This isn't too hard. In RDF, your data can be something as simple as a direct encoding of the graph:
#prefix : <urn:ex:>
:A :connectedTo :B .
:B :connectedTo :C, :D .
:D :connectedTo :F .
:F :connectedTo :E, :G .
Then, using SPARQL property paths, you can find every node such that there's a path of connectedTo properties from A to it and from it to E, including A and E themselves:
prefix : <urn:ex:>
select ?mid where {
:A :connectedTo* ?mid .
?mid :connectedTo* :E .
}
-------
| mid |
=======
| :D |
| :F |
| :B |
| :A |
| :E |
-------
If you want to get those in order, you can additionally count how many things are between A and the "mid-node". (This is described in my answer to Is it possible to get the position of an element in an RDF Collection in SPARQL?)
prefix : <urn:ex:>
select ?mid (count(?premid) as ?i) where {
:A :connectedTo* ?premid .
?premid :connectedTo* ?mid .
?mid :connectedTo* :E .
}
group by ?mid
-----------
| mid | i |
===========
| :D | 3 |
| :F | 4 |
| :E | 5 |
| :B | 2 |
| :A | 1 |
-----------
If you actually want a single result that looks more or less like "A, B, C, D, E, F", then you adapt these queries using the techniques from my answer to Aggregating results from SPARQL query, which shows how to concatenate these into a single string.

The SPARQL query - the closest blond antecedor

Let us consider we two classes: Person and its subclass BlondePerson.
Let us consider a relationship: isParent where a Person is parent of another person.
Let us define the relationship: isAncestor where there is a sequence of isParent relationships.
There might be many BlondPersons ancestor of me.
My question: how to write a SPARQL query so I learn the closest ancestor who is blond. The closest means my parent if possible, if not the grandparents, otherwise grandgrandparents and so on.
How to compose a SPARQL query for that? How to assure that I will get the ancestor who it the closest one?
Thank you.
This isn't too hard; you can use the same technique demonstrated in Is it possible to get the position of an element in an RDF Collection in SPARQL?. The idea is essentially to treat the ancestry as a sequence, from which you can get the "closeness" of each ancestor, and select the closest ancestor from a given class. If we create some sample data, we end up with something like this:
#prefix : <urn:ex:>
:a a :Person ; :hasParent :b .
:b a :Person ; :hasParent :c .
:c a :Person, :Blond ; :hasParent :d .
:d a :Person, :Blond ; :hasParent :e .
:e a :Person .
prefix : <urn:ex:>
select distinct
?person
?ancestor
(count(distinct ?mid) as ?closeness)
?isBlond
where {
values ?person { :a }
?a :hasParent+ ?mid .
?mid a :Person .
?mid :hasParent* ?ancestor .
?ancestor a :Person .
bind( if( exists { ?ancestor a :Blond }, true, false ) as ?isBlond )
}
group by ?person ?ancestor ?isBlond
order by ?person ?closeness
-------------------------------------------
| person | ancestor | closeness | isBlond |
===========================================
| :a | :b | 1 | false |
| :a | :c | 2 | true |
| :a | :d | 3 | true |
| :a | :e | 4 | false |
-------------------------------------------
That's actually more information than we needed, I just included it to show how this works. Now we can actually just require that ?ancestor is blond, order by closeness, and limit the results to the first (and thus the closest):
prefix : <urn:ex:>
select distinct
?person
?ancestor
(count(distinct ?mid) as ?closeness)
where {
values ?person { :a }
?a :hasParent+ ?mid .
?mid a :Person .
?mid :hasParent* ?ancestor .
?ancestor a :Person, :Blond .
}
group by ?person ?ancestor
order by ?person ?closeness
limit 1
---------------------------------
| person | ancestor | closeness |
=================================
| :a | :c | 2 |
---------------------------------

How to rank values in SPARQL?

I would like to create a ranking of observations using SPARQL. Suppose I have:
#prefix : <http://example.org#> .
:A :value 60 .
:B :value 23 .
:C :value 89 .
:D :value 34 .
The ranking should be: :C = 1 (the highest), :A = 2, :D = 3, :B = 4. Up until now, I was able solve it using the following query:
prefix : <http://example.org#>
prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT ?x ?v ?ranking {
?x :value ?v .
{ SELECT (GROUP_CONCAT(?x;separator="") as ?ordered) {
{ SELECT ?x {
?x :value ?v .
} ORDER BY DESC(?v)
}
}
}
BIND (str(?x) as ?xName)
BIND (strbefore(?ordered,?xName) as ?before)
BIND ((strlen(?before) / strlen(?xName)) + 1 as ?ranking)
} ORDER BY ?ranking
But that query only works if the URIs for ?x have the same length. A better solution would be to have a function similar to strpos in PHP or isIndexOf in Java, but as far as I know, they are not available in SPARQL 1.1. Are there simpler solutions?
One way of doing this is to take as the ranking for a value the number of values which are less than or equal to it. This might be inefficient for larger data sets, since for each value it has to check all the other values. It doesn't require string manipulation though.
PREFIX : <http://example.org#>
SELECT ?x ?v (COUNT(*) as ?ranking) WHERE {
?x :value ?v .
[] :value ?u .
FILTER( ?v <= ?u )
}
GROUP BY ?x ?v
ORDER BY ?ranking
---------------------
| x | v | ranking |
=====================
| :C | 89 | 1 |
| :A | 60 | 2 |
| :D | 34 | 3 |
| :B | 23 | 4 |
---------------------