Given this data, where each person may optionally have a "smart" predicate, and each department may have zero or more people, I need to find departments that contain only the smart people. The result should only include departments 1 and 2. Ideally, the result should also include the "smart" objects for each department. Thanks!
person:A p:type 'p' ;
p:smart 'yes' .
person:B p:type 'p' ;
p:smart 'maybe' .
person:C p:type 'p' .
department:1 p:type 'd' ;
p:has person:A, person:B .
department:2 p:type 'd' ;
p:has person:B .
department:3 p:type 'd' ;
p:has person:B, person:C .
department:4 p:type 'd' .
I have a feeling I've answered something similar before, but anyway there is a reasonably nice way to do this:
select ?dept
(count(?person) as ?pc) (count(?smart) as ?sc)
(group_concat(?smart; separator=',') as ?smarts)
{
?dept p:has ?person .
optional { ?person p:smart ?smart }
}
group by ?dept
having (?pc = ?sc)
That is: find the departments, people, and (where available) smart value. For each department find ones where the number of people matches the number of smart values.
-------------------------------------------------------------
| dept | pc | sc | smarts |
=============================================================
| <http://example.com/department#2> | 1 | 1 | "maybe" |
| <http://example.com/department#1> | 2 | 2 | "yes,maybe" |
-------------------------------------------------------------
When you want to get results for each object, matching some criteria, group by
/ having
is often the cleanest answer (in that you can separate out matching from filtering).