How to create a query which selects products of given features where feature statement is formed by "and" or "or" condition depending on a group they belong to?
Description of the situation
- There is a store with products.
- Products may have features or not.
- A customer looks for specific features of products which means filling out the form and sending an array of feature ids.
- In the database, each feature belongs to only one group of features.
- The first group (disjunction attribute is true, called "OR") allows to display the product if one of the features matches any feature submitted by the customer.
Example: selecting shapes: circle, square, triangle displays products which are circles or squares or triangles. - The second group (disjunction attribute is false, called "AND") allows to display the product only if the product has all of the features submitted by the customer.
Example: selecting colors: red, green, blue displays products which are red and green and blue.
Test environment
http://sqlfiddle.com/#!12/f4db7
"OR" Query
It works except for those product which have no features.
SELECT product_id
FROM product_features
WHERE product_features.feature_id IN (
SELECT feature_id FROM features
LEFT JOIN feature_groups
ON features.feature_group_id = feature_groups.feature_group_id
WHERE feature_id IN (11, 12, 13) AND feature_groups.disjunction = TRUE
)
GROUP BY product_id
"AND" Query
This query cannot be used because the number of features where disjunction is false is not known.
SELECT product_id FROM product_features
WHERE feature_id IN (43, 53, 63)
GROUP BY product_id
HAVING COUNT(DISTINCT feature_id) = 3
"OR" case
Simpler and faster:
... where
_my_arr
can be'{11, 12, 13}'::int[]
or'{}'::int[]
. If_my_arr
would beNULL
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:
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:
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.
-> SQLfiddle demo.