2016-02-22 4 views
0

У меня есть таблица products. (идентификатор, наименование, цена), таблица называется properties (идентификатор, название, тип) и таблицу с именем product_properties (идентификатор, PROPERTY_ID, product_id, значение).SQL: Фильтровать продукты по списку

Теперь я могу фильтровать продукты следующим образом:

  1. Start со списком product_ids.
  2. Для каждого (свойство, значение), мы хотим, чтобы фильтровать по:
    1. product_ids = SELECT 'product_properties'.'product_id' FROM 'product_properties' WHERE 'product_properties'.'product_id' IN [list,of,product,ids] AND 'product_properties'.'property_id' = property_id AND 'product_properties'.'value' = 'some value here'
  3. Повторите шаг 2, пока мы не фильтруются все хотели свойства.
  4. Наконец, выберите все соответствующие продукты, используя SELECT * FROM 'products' WHERE 'products'.'id' IN [list,of,product,ids]

Этот метод требует N + 1 запросов к базе данных для N фильтров. Я надеюсь, что это можно объединить в одном запросе.

Я попытался следующие, который не работает:

  • INNER JOIN/OUTER JOIN -> Отменяет все product_properties указывая на продукт строк, кроме первой, а затем пытается обеспечить выполнение всех N идентификаторами и значения на нем.
  • UNION -> Это возвращает список продуктов, которые соответствуют один или несколько фильтров, вместо все фильтры.

Как это может быть выполнено с помощью одного запроса базы данных (или как можно меньше)?

Я работаю с Ruby on Rails, а это значит, что я хотел бы, чтобы результат работал как в MySQL, так и в SQLite.

Спасибо!

ответ

2

Это, как я хотел бы попытаться решить:

WITH FILTERS (property_id, filter_value) AS 
(
      SELECT 1, 'foo' 
UNION ALL SELECT 2, 'bar' 
UNION ALL SELECT 3, 'baz' 
) 
SELECT prods.id 
    FROM products prods 
    JOIN product_properties props 
    ON (props.product_id = prods.id) 
    JOIN filters f 
    ON (f.property_id = props.property_id AND f.filter_value = props.value) 
GROUP BY prods.id 
HAVING COUNT(1) = (SELECT COUNT(1) FROM filters) 

Вот что это делает:

  • Во-первых, предоставить список фильтров как часть «С» п так что он может быть в одном запросе. Я гипотетически добавляю 3 фильтра на 3 разных свойствах, имеющих значения «foo», «bar» и «baz».
  • Во-вторых, выполните запрос, который соответствует фильтрам для продуктов. Это то, что делают SELECT через JOIN.
  • В-третьих, выполните идентификатор продукта GROUP BY и COUNT() результирующие строки. В предложении HAVING проверьте, совпадает ли количество товаров с общим количеством фильтров. Если да, значит, он должен соответствовать всем им.

ПРИМЕЧАНИЕ: Вы можете поочередно использовать UNION подход, а затем выполнить GROUP BY и HAVING COUNT() по результатам, чтобы увидеть, если он работает. Просто убедитесь, что вы используете UNION ALL на идентификаторах, а не только UNION.

+0

OP запросил решение, совместимое с MySQL и SQLite. Хотя в начале 2014 года SQLite 3.8.3 предоставляет CTE, MySQL этого не делает, поэтому 'With()' не будет работать. – Parfait

+0

Ничего себе! Метод UNION ALL ... GROUP BY ... HAVING COUNT() 'отлично работает. Очень хороший трюк. Большое спасибо! – Qqwy