Reputation: 197
products table (mySQL)
record_id categories (comma-delimited list)
--------- --------------------------------
1 960|1,957|1,958|1
I have the following dynamic query (simplified for the purposes of this question). The query is passed specified categories, each in the format xxxx|yyyy, and I need to return products having the passed category in its comma-delimited list of categories.
The current query looks like:
select p.* from products p
where (p.categories like '%27|0%' or p.categories like '%972|1%' or p.categories like '%969|1%')
But, the LIKE clause sometimes permits anomalies. I would like to write the query more like:
select p.* from products p
where (p.categories contains '27|0' or p.categories contains'972|1' or p.categories contains '969|1')
How would I do this?
Upvotes: 3
Views: 1205
Reputation: 3167
You can use LOCATE function:
select p.* from products p
where (LOCATE('27|0', p.categories) > 0 or LOCATE('972|1', p.categories) > 0 or LOCATE('969|1', p.categories) > 0))
EDIT: If you want to avoid false positives, you could double the amount of conditions, or triple if second number can be first number of anouther category:
select p.* from products p
where (
LOCATE('27|0', p.categories) = 1 or LOCATE(',27|0', p.categories) > 0 or
LOCATE('972|1', p.categories) = 1 or LOCATE(',972|1', p.categories) > 0 or
LOCATE('969|1', p.categories) = 1 or LOCATE(',969|1', p.categories) > 0
)
EDIT2: Final solution (long, after knowing that commas are separators):
select p.* from products p
where (
p.categories = '27|0' OR LOCATE('27|0,', p.categories) = 1 or LOCATE(',27|0,', p.categories) > 0 or LOCATE(',27|0', p.categories) = CHAR_LENGTH(p.categories) - CHAR_LENGTH(',27|0') + 1 or
p.categories = '972|1' OR LOCATE('972|1,', p.categories) = 1 or LOCATE(',972|1,', p.categories) > 0 or LOCATE(',972|1', p.categories) = CHAR_LENGTH(p.categories) - CHAR_LENGTH(',972|1') + 1 or
p.categories = '969|1' OR LOCATE('969|1,', p.categories) = 1 or LOCATE(',969|1,', p.categories) > 0 or LOCATE(',969|1', p.categories) = CHAR_LENGTH(p.categories) - CHAR_LENGTH(',969|1') + 1
)
Alternative (shorter, using REGEX, possibly slower)
select p.* from products p WHERE
p.categories REGEXP '(,|^)27\|0(,|$)'
or p.categories REGEXP '(,|^)972\|1(,|$)'
or p.categories REGEXP '(,|^)969\|1(,|$)'
You will need to escape | characters for REGEXP as this is a special character in regular expressions.
EDIT3:
REGEXP solution could be improved by using just one regular expression, such expressions are compiled during querying so single compilation is faster than 3 separate compilations even if matching expression is more complicated:
select p.* from products p WHERE p.categories REGEXP '(,|^)(27\|0|972\|1|969\|1)(,|$)'
Upvotes: 1