Как выбрать отдельное значение столбца, соответствующее нескольким критериям - PullRequest
0 голосов
/ 05 марта 2020

У меня есть таблица, содержащая атрибуты со следующей структурой:

id: bigint unsigned autoincrement
product_id: bigint foreign key 
attribute_id: bigint foreign key
value:  varchar(100) 

Я могу запросить один критерий следующим образом:

SELECT DISTINCT product_id FROM product_attributes WHERE attribute_id = ? AND value = ?

Однако мне нужно найти продукты, которые соответствуют нескольким таким критериям и хотели бы избежать нескольких запросов к базе данных по соображениям производительности. Простое добавление большего количества критериев с AND не будет работать, так как они будут включать те же столбцы, например:

SELECT DISTINCT product_id FROM product_attributes WHERE attribute_id = 1 AND value = 'Blue'
INTERSECT
SELECT DISTINCT product_id FROM product_attributes WHERE attribute_id = 2 AND value = '36'
INTERSECT
SELECT DISTINCT product_id FROM product_attributes WHERE attribute_id = 3 AND value = 'slim'

Я читал об операторе INTERSECT, который, кажется, может работать, но я читал, что MySQL не поддерживает это, поиск по MySQL 8 документации не дал релевантного результата, и запрос, который я считаю правильным, приводит к ошибке MySQL.

Я также что-то читал подобное может быть достигнуто с помощью внутреннего соединения, но все примеры, которые я нашел, включают несколько таблиц. Также может быть еще лучший или более простой способ написать запрос, который мне не приходил в голову. Или, возможно, на самом деле лучше просто отправить несколько запросов и вычислить пересечение за пределами MySQL (хотя я был бы очень удивлен). Я очень ценю любую помощь от любого, кто делал что-то подобное в прошлом.

Ответы [ 2 ]

2 голосов
/ 05 марта 2020

Это проблема хранилища ключей / значений.

Это легкая боль в шее, чтобы делать то, что вы хотите. Используйте операции JOIN для поворота значений в строку. Вот так.

    SELECT p.product_id, 
           color.value AS color,
           size.value AS size,
           cut.value AS cut
      FROM ( SELECT DISTINCT product_id FROM product_attributes ) p
      LEFT JOIN product_attributes color ON color.product_id = p.product_id 
                                        AND color.attribute_id = 1
      LEFT JOIN product_attributes size  ON size.product_id = p.product_id 
                                        AND size.attribute_id = 2
      LEFT JOIN product_attributes cut   ON cut.product_id = p.product_id 
                                        AND cut.attribute_id = 3

Это создает набор результатов с одной строкой для комбинации продукта / цвета / размера / среза

Затем вы можете отфильтровать этот набор результатов следующим образом

SELECT * 
  FROM (
      SELECT p.product_id, 
             color.value AS color,
             size.value AS size,
             cut.value AS cut
        FROM ( SELECT DISTINCT product_id FROM product_attributes ) p
        LEFT JOIN product_attributes color ON color.product_id = p.product_id 
                                          AND color.attribute_id = 1
        LEFT JOIN product_attributes size  ON size.product_id = p.product_id 
                                          AND size.attribute_id = 2
        LEFT JOIN product_attributes cut   ON cut.product_id = p.product_id 
                                          AND cut.attribute_id = 3
       ) combinations
 WHERE color='Blue' AND size='36' AND cut='slim'
* 1010 Планировщик запросов *MySQL достаточно умен, чтобы он работал не так медленно, как вы можете догадаться, учитывая правильные индексы.

Предложение FROM генерирует полный список идентификаторов продуктов из вашего product_attributes таблица для присоединения к указанным c атрибутам. Если у вас есть другая таблица для продуктов, используйте ее вместо SELECT DISTINCT....

2 голосов
/ 05 марта 2020

Необходимо использовать агрегирование для подсчета количества совпадающих строк с набором условий и утверждения, что оно равно числу условий:

SELECT product_id
FROM product_attributes
WHERE (attribute_id, value) IN ((1, 'Blue'), (2, '36'), (3, 'slim'))
GROUP BY product_id
HAVING COUNT(*) = 3
...