The following approach starts by putting the list to find in a table (well, a CTE) with each shape and a count of the number of times. Then, it does various checks.
The first is that each shape in shapes
appears the right number of times. This is where inner aggregation comes from.
Then, it counts the number of shapes that match and validates that this is the total number of different shapes in tofind
:
with tofind as (
select 40 as shape, 2 as cnt union all
select 45 as shape, 1 as cnt
)
select s.unit_id
from (select s.unit_id, tofind.cnt as tf_cnt, count(s.id) as s_cnt
from shapes s join
tofind
on s.shape = tofind.shape
group by s.unit_id, tofind.shape, tofind.cnt
) s
group by s.unit_id
having sum(case when s_cnt = tf_cnt then 1 else 0 end) = (select count(*) from tofind);
EDIT:
Here is a SQL Fiddle demonstrating that it works. However, the above code doesn't look for an exact match, because other shapes could be in the record. The following modification works only for an exact match:
with tofind as (
select 40 as shape, 2 as cnt union all
select 45 as shape, 1 as cnt
)
select s.unit_id
from (select s.unit_id, tofind.cnt as tf_cnt, count(s.id) as s_cnt
from shapes s left outer join
tofind
on s.shape = tofind.shape
group by s.unit_id, tofind.shape, tofind.cnt
) s
group by s.unit_id
having sum(case when s_cnt = tf_cnt then 1 else 0 end) = (select count(*) from tofind) and
count(*) = sum(case when s_cnt = tf_cnt then 1 else 0 end);
The difference is the left outer join
and the additional condition in the having
clause.