2016-01-12 5 views
0

У меня есть таблица product с полями, такими как title (varchar) или availability (bool).INNER JOIN with NOT IN возвращает неправильные результаты

Пользователь может искать продукты по названию (product.title), но также может применять определенные фильтры (такие как тип, производитель и т. Д.). Я решил это, используя SELECT рядом с INNER JOIN. Пример сгенерированного запроса выглядит так:

SELECT z.* 
FROM product z 
WHERE (z.title LIKE "%bread%") 
AND z.availability = 1 
AND z.category IN (4,5,6); 

Это работает отлично. Тем не менее, продукты также маркируются «тегами аллергии» (например, клейковины). К сожалению, продукт может содержать любое количество «аллергии тегов», таким образом, я должен был использовать сшивающий таблицу (crosslink__productXproduct_allergy_tag):

id int not null primary key auto_increment 
productId int 
allergyTagID int 

теги хранятся в таблице product_allergy_tag, используя идентификатор в качестве первичного ключа, и название тега. Естественно, что при поиске продуктов, которые являются глютен, сгенерированный запрос будет выглядеть следующим образом:

SELECT z.* 
FROM product z 
INNER JOIN crosslink__productXproduct_allergy_tag a ON z.id = a.productId 
WHERE (z.title LIKE "%bread%") 
AND z.availability = 1 
AND a.allergyTagId IN (15) 

(С 15 является идентификатор глютена тега).

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

Так что я подумал: я просто нужно обратить логику:

Вместо:

a.allergyTagId IN (15) 

было бы:

a.allergyTagId NOT IN (15) 

Но тем не менее, по причинам, я не понимаю , запрос по-прежнему возвращает некоторые продукты с этим тегом аллергии, хотя он не возвращает все из них.

Я попытался понять суть этого, тестируя различные варианты и сравнивая результаты, но безрезультатно.

Ходят слухи, что я не SQL-класс.

Любая поддержка по этому вопросу была бы полезна. Спасибо!

+1

Ваш запрос возвращает все продукты, для которых существует аллерген, который не является клейковиной. Это не вернет ничего без аллергенов, он будет возвращать продукты с несколькими аллергенами более одного раза, и он все равно будет возвращать клейковину, если у них также есть второй (или третий) аллерген. – Thilo

+0

Поскольку вы делаете внутреннее соединение в crosslink__productXproduct_allergy_tag. Вы выбираете из таблицы продуктов с каким-то аллергеном. Затем вы сужаетесь к тем, у кого есть «хлеб» в названии, доступен и что аллергия не является клейковиной. Так как некоторые продукты могут иметь несколько аллергенов, вы, по сути дела, присоединяетесь к случайному приходу продукта, часто это не будет клейковиной, и поэтому он будет выбран. Ясно, что это полностью назад от того, что вам нужно. Я предлагаю начать снова, делая логические объединения и рассматривая эффект каждого добавления. –

ответ

1

Проблема заключается в том, как объединение работает, он будет присоединиться к нему с каждым матчем, так вы получите:

Продукт А - аллерген Продукт А - аллерген B Продукт B - аллерген C

Вы также не получаете никаких продуктов, у которых нет аллергенов, так как INNER JOIN требует, чтобы выбран хотя бы один аллерген.

Что вы хотите, присоединитесь и проверьте, что аллерген - NULL. Что-то вроде:

SELECT z.* FROM product z LEFT JOIN crosslink__productXproduct_allergy_tag a 
    ON z.id = a.productId AND a.allergyTagId IN (15) WHERE (z.title LIKE 
"%bread%") AND z.availability = 1 AND a.allergyTagId IS NULL 

Это проверка аллергенов во время СОЕДИНЕНИЯ. Если есть совпадения, он будет содержать строку для каждого совпадения с параметром allergyTagId с идентификатором, который соответствует, и если он не соответствует ни одному, он будет иметь NULL в этом столбце. Затем фильтруйте столбец NULL, чтобы получить продукты, которые не попадают на какие-либо аллергены.

+0

Сначала это имело смысл для меня, но, глядя в него, я понимаю, что он не учитывает исключение выбранных тегов для аллергии? Кроме того, он, похоже, запрашивает записи, в которых установлен идентификатор тега аллергии AND, где идентификатор тега allergy равен нулю. Это ничего не вернет? – SquareCat

+0

@SquareCat запрос действителен, когда вы понимаете семантику: 't1 LEFT JOIN t2 ... WHERE t2.c1 IS NULL' означает« включать только строки из t1, где левое соединение не находило соответствующую строку в t2 ». Мы не ищем нули * в * t2, ищем нули в * соединяемой строке * в t2 ..., которая истинна, только если никакие строки не содержат значения, которые мы хотим доказать, не существует, пока t2.c1 не является оплачиваемым столбцом ... что именно вы спрашиваете. –

+1

Логически эквивалентен этому ответу и, возможно, более интуитивно понятен: 'SELECT z. * FROM product z WHERE (z.title LIKE «% хлеб% ») И z.availability = 1 И НЕ СУЩЕСТВУЕТ (SELECT * FROM crosslink__productXproduct_allergy_tag a ГДЕ a.productId = z.id AND a.allergyTagId IN (15)); ' –

2
SELECT z.* FROM product z where z.title like "%bread%" and z.availability = 1 and z.id not in (Select productId from crosslink__productXproduct_allergy_tag a where a.allergyTagId = 15) 

Edit: То, что вы хотите, список продуктов, за исключением тех, в списке продуктов с allergytag из 15. подзапрос находит список продуктов с allergytag 15, а основной запрос выбирает все из которые не являются таковыми.

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