"OR" case
Simpler and faster:
SELECT DISTINCT pf.product_id
FROM product_features pf
LEFT JOIN features f USING (feature_id)
LEFT JOIN feature_groups fg USING (feature_group_id)
WHERE (f.feature_id = ANY (_my_arr)
AND fg.disjunction)
OR _my_arr = '{}';
... where _my_arr
can be '{11, 12, 13}'::int[]
or '{}'::int[]
. If _my_arr
would be NULL
use _my_arr IS NULL
instead.
Due to operator precedence
AND
binds beforeOR
and parentheses are not required. They may improve readability, though.DISTINCT
orGROUP BY
.. either is good here.AND fg.disjunction
.. since this is a boolean type, you can shorten the syntax.JOINs are generally faster than another
IN
clause.USING
is just a notational shortcut that works with your (useful!) naming convention.
Or, even faster (for more than 1 feature) - and simpler to split cases:
SELECT product_id
FROM products p
WHERE EXISTS (
SELECT 1
FROM product_features pf
JOIN features f USING (feature_id)
JOIN feature_groups fg USING (feature_group_id)
WHERE pf.product_id = p.product_id
AND f.feature_id = ANY (_my_arr)
AND fg.disjunction
)
OR _my_arr = '{}';
I would rather split the case in your app depending on input (with features / no features). Trivial to do with the second form.
"AND" case
This is a classical case of relational division. We have assembled a whole arsenal of query techniques to deal with it in under this related question:
How to filter SQL results in a has-many-through relation
Could be:
SELECT product_id
FROM product_features p1
JOIN product_features p2 USING (product_id)
JOIN product_features p3 USING (product_id)
...
WHERE p1.feature_id = 43
AND p2.feature_id = 53
AND p3.feature_id = 63
...
I am ignoring NOT feature_groups.disjunction
in the example since it is not in the question either. Add it if you need it.
I would select valid feature_id before building the query.