PostgreSQL условное соединение и дизъюнкция в одном запросе
Как создать запрос, который выбирает продукты с заданными характеристиками, в которых оператор функции формируется условием "и" или "или" в зависимости от группы, к которой они принадлежат?
Описание ситуации
- Есть магазин с продуктами.
- Продукты могут иметь функции или нет.
- Покупатель ищет специфические особенности продуктов, что означает заполнение формы и отправку множества идентификаторов функций.
- В базе данных каждая функция принадлежит только одной группе функций.
- Первая группа (атрибут disjunction true, называется "ИЛИ") позволяет отображать продукт, если одна из функций соответствует какой-либо функции, предоставленной клиентом.
Пример: выбор форм: круг, квадрат, треугольник отображает продукты, которые представляют собой круги, квадраты или треугольники. - Вторая группа (атрибут disjunction false, называемый "И") позволяет отображать продукт, только если продукт обладает всеми функциями, представленными клиентом.
Пример: выбор цвета: красный, зеленый, синий отображает продукты красного, зеленого и синего цвета.
Тестовая среда
Запрос "ИЛИ"
Это работает за исключением тех продуктов, которые не имеют никаких функций.
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
Запрос "И"
Этот запрос не может быть использован, потому что число функций, для которых дизъюнкция ложна, неизвестно.
SELECT product_id FROM product_features
WHERE feature_id IN (43, 53, 63)
GROUP BY product_id
HAVING COUNT(DISTINCT feature_id) = 3
1 ответ
Случай "ИЛИ"
Проще и быстрее:
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 = '{}';
... где _my_arr
может быть '{11, 12, 13}'::int[]
или же '{}'::int[]
, Если _my_arr
было бы NULL
использование _my_arr IS NULL
вместо.
Из-за приоритета оператора
AND
связывает передOR
и скобки не требуются. Они могут улучшить читаемость, хотя.DISTINCT
или жеGROUP BY
.. либо здесь хорошоAND fg.disjunction
.. так как это логический тип, вы можете сократить синтаксис.СОЕДИНЕНИЯ, как правило, быстрее, чем другие
IN
пункт.USING
это просто обозначение, которое работает с вашим (полезным!) соглашением об именах.
Или даже быстрее (для более чем 1 функции) - и проще разделить случаи:
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 = '{}';
Я бы предпочел разделить случай в вашем приложении в зависимости от ввода (с функциями / без функций). Тривиально делать со второй формой.
Случай "И"
Это классический случай реляционного деления. Мы собрали целый арсенал методов запросов для решения этой проблемы:
Как отфильтровать результаты SQL в сквозном отношении
Может быть:
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
...
Я игнорирую NOT feature_groups.disjunction
в примере, так как это не в вопросе. Добавьте это, если вам это нужно.
Я бы выбрал действительный feature_id перед построением запроса.