2016-04-21 2 views
1

Вот упрощенная версия моей электронной коммерции схеме:Пересечения запроса атрибуты товара таблица

[products]: 
id 
Name 
Price 
URL 

[attributes]: 
id 
Description 
Value 

[products_attributes]: 
products_id 
attributes_id 

Это типичный многие-ко-многим.

Теперь мне нужно запросить все продукты «Красный» и «XXL».

SELECT p.* 
FROM products p 
INNER JOIN products_attributes pa ON pa.products_id = p.id 
INNER JOIN attributes pa ON pa.attributes_id = a.id 
WHERE a.Value IN ('Red','XXL'); 

Очевидно, что этот SQL-запрос извлекает ВСЕ «красные» продукты, даже те, у которых нет атрибута «XXL».

Как я могу получить только продукты, имеющие оба атрибута?

ответ

2

Вам нужно будет проверить каждый отдельно, что является одной из многих причин того, что модель EAV обычно плохой дизайн. Вы можете сделать это, выполнив два подзапроса для EXISTS:

SELECT 
    P.id, P.name, P.price, P.url -- Because we never use SELECT * except for EXISTS, COUNT(*), etc. 
FROM 
    Products P 
WHERE 
    EXISTS 
    (
     SELECT * 
     FROM 
      Products_Attributes PA_RED 
     INNER JOIN Attributes A_RED ON 
      A_RED.attribute_id = PA_RED.attribute_id AND 
      A_RED.description = 'Color' AND -- Have you thought about the possibility that "Red" might be a value for multiple attributes? 
      A_RED.value = 'Red' 
     WHERE 
      PA_RED.product_id = P.product_id 
    ) AND 
    EXISTS 
    (
     SELECT * 
     FROM 
      Products_Attributes PA_XXL 
     INNER JOIN Attributes A_XXL ON 
      A_XXL.attribute_id = PA_XXL.attribute_id AND 
      A_XXL.description = 'Size' AND 
      A_XXL.value = 'XXL' 
     WHERE 
      PA_XXL.product_id = P.product_id 
    ) 

Вы могли бы также JOIN к каждой из таблиц дважды:

SELECT 
    P.id, P.name, P.price, P.url 
FROM 
    Products P 
INNER JOIN Products_Attributes PA_RED ON PA_RED.product_id = P.product_id 
INNER JOIN Attributes A_RED ON 
    A_RED.attribute_id = PA_RED.attribute_id AND 
    A_RED.description = 'Color' AND 
    A_RED.value = 'Red' 
INNER JOIN Products_Attributes PA_XXL ON PA_XXL.product_id = P.product_id 
INNER JOIN Attributes A_XXL ON 
    A_XXL.attribute_id = PA_XXL.attribute_id AND 
    A_XXL.description = 'Size' AND 
    A_XXL.value = 'XXL' 

Конечно, думать о том, что запрос выглядит, когда вы хотите проверьте 5 разных атрибутов ...

Другим подходом было бы проверить количество совпадений как совокупность. Вы должны быть уверены, что один и тот же атрибут не может быть сопоставлен с одним и тем же продуктом дважды:

SELECT 
    P.id, P.name, P.price, P.url 
FROM 
    Products P 
INNER JOIN Products_Attributes PA ON PA.product_id = P.product_id 
INNER JOIN Attributes A ON 
    A.attribute_id = PA.attribute_id AND 
    (A.description = 'Color' AND A.value = 'Red') OR 
    (A.description = 'Size' AND A.value = 'XXL') 
GROUP BY 
    P.id, P.name, P.price, P.url 
HAVING 
    COUNT(*) = 2 
+0

У вас действительно появился человек! Спасибо! Вы говорите, что EAV может быть плохим выбором дизайна. Можете ли вы предложить мне лучше для этой цели, может быть? –

+0

Если вы знаете атрибуты своей продукции, то вы помещаете их в таблицу: Продукты будут иметь: цвет, размер и т. Д. Если ваши продукты являются широкими (вы - Amazon, и вы продаете книги, одежду, электронику и т. Д. Все с их собственными атрибутами), то вы, вероятно, не хотите использовать реляционную базу данных. Такие продукты, как MongoDB или NoSQL, могут быть более уместными. Это не моя область знаний, поэтому у других могут быть лучшие предложения. –

+1

Другая возможность, если у вас есть разрозненные продукты, но небольшое количество категорий, вы можете использовать модель с таблицей Products, а затем отношения 1: 1 с Product_Electronics, Product_Clothes и т. Д., Если эти подтипы имеют одинаковые атрибуты. –

1

Один из способов - использовать два соединения. Тем не менее, я предпочитаю агрегации и group by:

SELECT p.* 
FROM products p INNER JOIN 
    products_attributes pa 
    ON pa.products_id = p.id INNER JOIN 
    attributes pa 
    ON pa.attributes_id = a.id 
WHERE a.Value IN ('Red','XXL') 
GROUP BY p.id 
HAVING COUNT(DISTINCT a.Value) = 2; 

Примечание: это выбор p.* в запросе агрегации. В общем, я категорически не рекомендую это делать. Однако в этом случае агрегирование осуществляется с помощью первичного ключа в таблице products, поэтому выбор дополнительных столбцов из этой таблицы является безопасным. Фактически, включение дополнительных столбцов даже поддерживается ANSI SQL для таких случаев.

Смежные вопросы