Reputation: 349
I started here: MySQL select one field from table WHERE condition is in multiple rows
This works fine - thank you!
The additional complexity is that I need to search within multiple attributes in a single search.
Here's a data snapshot. The attribute_ids are:
1 - language
18 - phone1
19 - phone2
20 - phone3
Sample data
+-----+------------+--------------+------------------------+ | id | contact_id | attribute_id | stored_attribute_value | +-----+------------+--------------+------------------------+ | 15 | 1 | 1 | english | | 83 | 5 | 1 | english | | 153 | 9 | 1 | english | | 197 | 11 | 1 | english | | 250 | 3 | 1 | english | | 267 | 13 | 1 | tagalog | | 303 | 15 | 1 | spanish | | 374 | 19 | 1 | spanish | | 469 | 17 | 1 | spanish | | 490 | 21 | 1 | spanish | | 507 | 7 | 1 | english | | 9 | 1 | 18 | 983-296-3660 | | 77 | 5 | 18 | 123-300-3985 | | 147 | 9 | 18 | 215-857-7105 | | 191 | 11 | 18 | 123-216-8501 | | 244 | 3 | 18 | 478-786-4450 | | 261 | 13 | 18 | 802-118-7211 | | 297 | 15 | 18 | 998-370-4612 | | 367 | 19 | 18 | 203-435-4023 | | 463 | 17 | 18 | 945-519-5355 | | 481 | 21 | 18 | 425-675-8912 | | 501 | 7 | 18 | 123-712-6946 | | 11 | 1 | 19 | 123-653-3722 | | 79 | 5 | 19 | 396-609-5772 | | 149 | 9 | 19 | 261-899-1470 | | 193 | 11 | 19 | 673-452-9545 | | 246 | 3 | 19 | 760-700-5826 | | 263 | 13 | 19 | 123-701-7931 | | 299 | 15 | 19 | 123-445-5874 | | 369 | 19 | 19 | 711-657-8183 | | 465 | 17 | 19 | 123-130-2816 | | 483 | 21 | 19 | 123-391-1234 | | 503 | 7 | 19 | 123-568-1263 | | 485 | 21 | 20 | 123-428-6610 | +-----+------------+--------------+------------------------+
So if I were to search for all contacts with language 'english' and phone1 like '123%', the query would be:
SELECT `contact_id`
FROM (`contact_attribute_value`)
WHERE (`attribute_id` = '18' AND `stored_attribute_value` LIKE '123%')
OR (`attribute_id` = '1' AND `stored_attribute_value` = 'english')
GROUP BY `contact_id` HAVING COUNT(*) = 2
And I would get 3 results returned: 5, 7, and 11 which is correct.
The challenge is that I want to create a generic phone field in the search interface so that if a user searches for a phone number, they search all three phone fields simultaneously.
So, I wrote the following query:
SELECT `contact_id`
FROM (`contact_attribute_value`)
WHERE (`attribute_id` = '18' AND `stored_attribute_value` LIKE '123%')
OR (`attribute_id` = '19' AND `stored_attribute_value` LIKE '123%')
OR (`attribute_id` = '20' AND `stored_attribute_value` LIKE '123%')
OR (`attribute_id` = '1' AND `stored_attribute_value` = 'english')
GROUP BY `contact_id` HAVING COUNT(*) = 2
Conceptually that works, but there are conditions in which it breaks.
The first condition is when a contact has language of 'english' and two phone numbers that match like '123%'. Here the contact gets a count of 3 and does not show up in the results.
The second condition is when a contact has a language not equal to 'english' and also has two phone numbers that match like '123%'. In this case, the contact gets a count of 2 and shows up in the results, but that's not what is desired.
I'm sure there's a "hard coded" way of trapping for these conditions in this scenario, but the set of attributes and possible searches is quite large so I need a generalizable solution.
Thanks in advance!
Upvotes: 2
Views: 2734
Reputation: 3123
Just taking into account your example and the conditions you explained, I would say that the easiest way to solve this without doing two queries, is with a subquery:
SELECT DISTINCT contact_id
FROM contact_attribute_value AS c
WHERE c.attribute_id IN (18, 19, 20)
AND c.stored_attribute_value LIKE '123%'
AND EXISTS (SELECT *
FROM contact_attribute_value AS c1
WHERE c1.contact_id = c.contact_id AND c1.attribute_id = 1
AND c1.stored_attribute_value = 'english')
With this query, first we check if the contact haves any number that starts with 123, and then the subquery takes care of checking if the contact haves english as his language.
The DISTINCT
keyword removes dupliciates, so no need for grouping anymore.
Upvotes: 3
Reputation: 92785
If I understand you correctly try
SELECT c1.contact_id
FROM contact_attribute_value c1 LEFT JOIN contact_attribute_value c2
ON c1.contact_id = c2.contact_id
AND c2.attribute_id = '18' LEFT JOIN contact_attribute_value c3
ON c1.contact_id = c3.contact_id
AND c3.attribute_id = '19' LEFT JOIN contact_attribute_value c4
ON c1.contact_id = c4.contact_id
AND c4.attribute_id = '20'
WHERE c1.attribute_id = '1' AND c1.stored_attribute_value = 'english'
AND (c2.stored_attribute_value LIKE '123%'
OR c3.stored_attribute_value LIKE '123%'
OR c4.stored_attribute_value LIKE '123%')
UPDATE Improved version with HAVING
that uses conditional count
SELECT `contact_id`
FROM `contact_attribute_value`
GROUP BY `contact_id`
HAVING SUM(CASE WHEN `attribute_id` = '1'
AND `stored_attribute_value` = 'english' THEN 1 ELSE 0 END) = 1
AND (SUM(CASE WHEN `attribute_id` = '18'
AND `stored_attribute_value` LIKE '123%' THEN 1 ELSE 0 END)
+SUM(CASE WHEN `attribute_id` = '19'
AND `stored_attribute_value` LIKE '123%' THEN 1 ELSE 0 END)
+SUM(CASE WHEN `attribute_id` = '20'
AND `stored_attribute_value` LIKE '123%' THEN 1 ELSE 0 END)) > 0
;
Upvotes: 1