thisisshantzz
thisisshantzz

Reputation: 1097

A SPARQL query to return entities that satisfy some of the conditions

I have a requirement to write a SPARQL query to return those entities that satisfy a fixed number of conditions (not always all of them). The idea is that I need to get all those entities that satisfy say 2 out of the 4 conditions provided. I am writing the following query

SELECT ?ent WHERE {
    BIND(0 as ?cnt).
    OPTIONAL {
       ?ent ns:age ?age.
       FILTER(?age > 20 && ?age <= 40).
       BIND(?cnt + 1, ?cnt).
    }.
    OPTIONAL {
        ?ent ns:livesIn nst:Mauritius.
        BIND(?cnt + 1, ?cnt).
    }.
    OPTIONAL {
        ?ent ns:maritalStatus nst:Married.
        BIND(?cnt + 1, ?cnt).
    }.
    OPTIONAL {
        ?ent ns:fatherIs/ns:age ?fatherAge.
        FILTER(?fatherAge > 55 && ?fatherAge <= 80).
        BIND(?cnt + 1, ?cnt).
    }
} HAVING(?cnt > 2)

Am I going about the problem in the correct way? Am I missing out on something? Is there a better way to solve my problem?

EDIT 1: The above mentioned query gave me an error. Now I am trying with this

SELECT ?ent ?cnt WHERE {
    BIND(0 as ?cnt1).
    BIND(0 as ?cnt2).
    BIND(0 as ?cnt3).
    BIND(0 as ?cnt4).
    OPTIONAL {
       ?ent ns:age ?age.
       FILTER(?age > 20 && ?age <= 40).
       BIND(1 as ?cnt1).
    }.
    OPTIONAL {
        ?ent ns:livesIn nst:Mauritius.
        BIND(1 as ?cnt2).
    }.
    OPTIONAL {
        ?ent ns:maritalStatus nst:Married.
        BIND(1 as ?cnt3).
    }.
    OPTIONAL {
        ?ent ns:fatherIs/ns:age ?fatherAge.
        FILTER(?fatherAge > 55 && ?fatherAge <= 80).
        BIND(1 as ?cnt4).
    }
    BIND((?cnt1 + ?cnt2 + ?cnt3 + ?cnt4) as ?cnt)
} ORDER BY DESC(?cnt)

This query returns a value of 0 under ?cnt for all records

Upvotes: 1

Views: 391

Answers (2)

Damyan Ognyanov
Damyan Ognyanov

Reputation: 786

Probably should elaborate more on the use of UNION that I suggested in the above comment. e.g. to retrieve everything that matches the first condition AND at lest one of the rest, OR the solutions where second condition holds AND at least one of the other two OR solutons where the last two are present together, so something like that:

SELECT ?ent WHERE {
{
    ?ent ns:age ?age.
    FILTER(?age > 20 && ?age <= 40).
    {
        ?ent ns:livesIn nst:Mauritius.
    } union {
        ?ent ns:maritalStatus nst:Married.
    } union {
            ?ent ns:fatherIs/ns:age ?fatherAge.
            FILTER(?fatherAge > 55 && ?fatherAge <= 80).
    }
} union {
    ?ent ns:livesIn nst:Mauritius.
    {
        ?ent ns:maritalStatus nst:Married.
    } union {
            ?ent ns:fatherIs/ns:age ?fatherAge.
            FILTER(?fatherAge > 55 && ?fatherAge <= 80).
    }    
} union {
    ?ent ns:maritalStatus nst:Married.
    ?ent ns:fatherIs/ns:age ?fatherAge.
    FILTER(?fatherAge > 55 && ?fatherAge <= 80).
  }
}

The other possible approach is to use GROUP BY over ?ent, but that will work only if the solutions per ?ent in each OPTIONAL block is exactly one, then the query is a simple UNION between all conditions and then using HAVING to filter out what is not desired:

SELECT ?ent WHERE {
{
    ?ent ns:age ?age.
    FILTER(?age > 20 && ?age <= 40).
} union {
    ?ent ns:livesIn nst:Mauritius.
} union {
    ?ent ns:maritalStatus nst:Married.
} union {
        ?ent ns:fatherIs/ns:age ?fatherAge.
        FILTER(?fatherAge > 55 && ?fatherAge <= 80).
}
} GROUP BY ?ent
HAVING (count(*) > 1)

Upvotes: 2

Stanislav Kralin
Stanislav Kralin

Reputation: 11459

I'd suggest the following naive approach.

SELECT ?ent ?cnt WHERE {
    ?ent a foaf:Person .
    OPTIONAL {
        ?ent ns:age ?age.
        FILTER(?age > 20 && ?age <= 40).
    }
    OPTIONAL {
        ?ent ns:livesIn ?livesIn.
        FILTER (?livesIn = nst:Mauritius)
    }
    OPTIONAL {
        ?ent ns:maritalStatus ?maritalStatus.
        FILTER (?maritalStatus = nst:Married).
    }
    OPTIONAL {
        ?ent ns:fatherIs/ns:age ?fatherAge.
        FILTER(?fatherAge > 55 && ?fatherAge <= 80).
    }
    BIND(xsd:integer(bound(?age)) +
         xsd:integer(bound(?livesIn)) +
         xsd:integer(bound(?maritalStatus)) +
         xsd:integer(bound(?fatherAge))
         AS ?cnt)
    FILTER (?cnt > 2)
} ORDER BY DESC(?cnt)

There are two problems with your recent query:

  • External BINDs override internal ones (or rather there is no join between them).
  • You're starting with OPTIONAL (see In SPARQL, order matters).

Upvotes: 3

Related Questions