I have front-end search form that allows users to search for Contacts given a number of search fields (name, address, email, categories, etc). I now have the requirement to implement ‘negative’ category searches. In other words, they can search for a Contact belonging to one category, but not another. An example would be to return all Contacts assigned to “category_1” and not assigned to “category_2”. They do this by entering in a front-end html form field like so: “category_1, -category_2” (notice the minus sign).
I am attempting to use the NOT EXIST function in MySQL because I need the “negative filter” in the WHERE clause (for several reasons). Below is my attempt to implement. Any idea why this is producing zero results?
For clarity, my table arrangement is as follows (many-to-many):
Contact -> ContactCategories <- Category
SELECT DISTINCT contact.* FROM Contact contact INNER JOIN ContactCategory categories ON contact.ContactId = categories.ContactId INNER JOIN Category category ON categories.CategoryId = category.CategoryId WHERE LOWER(category.Name) = 'category_1' AND NOT EXISTS ( SELECT DISTINCT contact_temp.* FROM Contact contact_temp INNER JOIN ContactCategory categories_temp ON contact_temp.ContactId = categories_temp.ContactId INNER JOIN Category category_temp ON categories_temp.CategoryId = category_temp.CategoryId WHERE LOWER(category_temp.Name) = 'category_2' )
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
You need to correlate the subquery with the main query.
There’s no need to select from Contact
in the subquery, just select from ContactCategory
and correlate that with categories_temp.ContactId = contact.ContactId
.
SELECT DISTINCT contact.* FROM Contact contact INNER JOIN ContactCategory categories ON contact.ContactId = categories.ContactId INNER JOIN Category category ON categories.CategoryId = category.CategoryId WHERE LOWER(category.Name) = 'category_1' AND NOT EXISTS ( SELECT * FROM Contact contact_temp INNER JOIN ContactCategory categories_temp ON contact_temp.ContactId = categories_temp.ContactId INNER JOIN Category category_temp ON categories_temp.CategoryId = category_temp.CategoryId WHERE contact_temp.ContactId = contact.ContactId AND LOWER(category_temp.Name) = 'category_2' )
Method 2
The majority of your code is repeated. For that reason I wouldn’t use NOT EXISTS()
Instead you can just use a single (non-correlated) sub-query to check both conditions, and join the results on to the the contact
table.
Note: I’ve also removed the DISTINCT
keyword. It’s often used to hide either faulty queries or faulty data; fix the fault, don’t fudge the query.
SELECT contact.* FROM Contact contact INNER JOIN ( SELECT categories.ContactId FROM ContactCategory categories INNER JOIN Category category ON categories.CategoryId = category.CategoryId WHERE LOWER(category.Name) IN ('category_1', 'category_2') GROUP BY categories.ContactId HAVING MAX(CASE WHEN LOWER(category.Name) = 'category_1' THEN 1 END) = 1 AND MAX(CASE WHEN LOWER(category.Name) = 'category_2' THEN 1 END) = 0 ) category_search ON contact.ContactId = category_search.ContactId
Also, be aware that using LOWER(category.Name)
inhibits the use of indexes on that table. You’re better off having lower_name
as an indexed column and searching that directly, without embedding the LOWER()
in your query.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0