113

I have a field COLORS (varchar(50)) in a my table SHIRTS that contains a comma delimited string such as 1,2,5,12,15,. Each number representing the available colors.

When running the query select * from shirts where colors like '%1%' to get all the red shirts (color=1), I also get the shirts whose color is grey (=12) and orange (=15).

How should I rewrite the query so that is selects ONLY the color 1 and not all colors containing the number 1?

3
  • 6
    You could do this via regex, I suppose, but the much better solution would be to break shirt colors into a separate table (colors) and use a join table (shirt_colors) using the ids of color/shirt to link them. Commented Feb 17, 2011 at 18:36
  • I can't believe with 6 answers none of them mentioned MySQL's SET data type.. Commented Dec 14, 2012 at 15:30
  • 1
    check this: stackoverflow.com/questions/12559876/… Commented Jun 28, 2014 at 6:55

12 Answers 12

220

The classic way would be to add commas to the left and right:

select * from shirts where CONCAT(',', colors, ',') like '%,1,%'

But find_in_set also works:

select * from shirts where find_in_set('1',colors) <> 0
Sign up to request clarification or add additional context in comments.

7 Comments

I tried find_in_set but it returns the same result no matter the color value I enter... Any suggestions?
@bikey77: Maybe this is the problem, the documentation says: This function does not work properly if the first argument contains a comma (“,”) character.
My wrong, it was a logical mistake due to the same dummy values. It works fine. Thanks!
@Andomar Before i found your answer i wast struggling with IN but yours is work like a charm... Thank you so much..
It has a performance impact as Find_in_set doesn't used index
|
40

FIND_IN_SET is your friend in this case

select * from shirts where FIND_IN_SET(1,colors) 

1 Comment

find_in_set is too slow for large tables
30

Take a look at the FIND_IN_SET function for MySQL.

SELECT * 
    FROM shirts 
    WHERE FIND_IN_SET('1',colors) > 0

1 Comment

Beware: find in set doesn't uses indexes on table.
12

This will work for sure, and I actually tried it out:

lwdba@localhost (DB test) :: DROP TABLE IF EXISTS shirts;
Query OK, 0 rows affected (0.08 sec)

lwdba@localhost (DB test) :: CREATE TABLE shirts
    -> (<BR>
    -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> ticketnumber INT,
    -> colors VARCHAR(30)
    -> );<BR>
Query OK, 0 rows affected (0.19 sec)

lwdba@localhost (DB test) :: INSERT INTO shirts (ticketnumber,colors) VALUES
    -> (32423,'1,2,5,12,15'),
    -> (32424,'1,5,12,15,30'),
    -> (32425,'2,5,11,15,28'),
    -> (32426,'1,2,7,12,15'),
    -> (32427,'2,4,8,12,15');
Query OK, 5 rows affected (0.06 sec)
Records: 5  Duplicates: 0  Warnings: 0

lwdba@localhost (DB test) :: SELECT * FROM shirts WHERE LOCATE(CONCAT(',', 1 ,','),CONCAT(',',colors,',')) > 0;
+----+--------------+--------------+
| id | ticketnumber | colors       |
+----+--------------+--------------+
|  1 |        32423 | 1,2,5,12,15  |
|  2 |        32424 | 1,5,12,15,30 |
|  4 |        32426 | 1,2,7,12,15  |
+----+--------------+--------------+
3 rows in set (0.00 sec)

Give it a Try !!!

1 Comment

Hey @rolandomysqldba, I test your query and it works ok but I need to make some changes in it. Let's say if I want to get all the shirts where color value is 1,2 in the column.
7

If the set of colors is more or less fixed, the most efficient and also most readable way would be to use string constants in your app and then use MySQL's SET type with FIND_IN_SET('red',colors) in your queries. When using the SET type with FIND_IN_SET, MySQL uses one integer to store all values and uses binary "and" operation to check for presence of values which is way more efficient than scanning a comma-separated string.

In SET('red','blue','green'), 'red' would be stored internally as 1, 'blue' would be stored internally as 2 and 'green' would be stored internally as 4. The value 'red,blue' would be stored as 3 (1|2) and 'red,green' as 5 (1|4).

Comments

4
select * from shirts where find_in_set('1',colors) <> 0

Works for me

Comments

3

If you're using MySQL, there is a method REGEXP that you can use...

http://dev.mysql.com/doc/refman/5.1/en/regexp.html#operator_regexp

So then you would use:

SELECT * FROM `shirts` WHERE `colors` REGEXP '\b1\b'

1 Comment

Couldn't figure this one out, although I'm pretty sure it's my fault. Thanks a mil mate.
3

You should actually fix your database schema so that you have three tables:

shirt: shirt_id, shirt_name
color: color_id, color_name
shirtcolor: shirt_id, color_id

Then if you want to find all of the shirts that are red, you'd do a query like:

SELECT *
FROM shirt, color
WHERE color.color_name = 'red'
  AND shirt.shirt_id = shirtcolor.shirt_id
  AND color.color_id = shirtcolor.color_id

4 Comments

@Blindy: That's only true if you assume the OP has edit rights on the database schema; has the time to redesign the database, migrate the data, and refactor all clients; and that the reduction of complexity for this query outweighs the increase in complexity in the rest of the application.
@Andomar, then again when he'll run into size restrictions for row retrievals and his "records" will get clipped, THAT's when the real fun will begin!
@Blindy: You're missing the point; I'm not arguing that he has the best solution, just that not everyone has the freedom to redesign his environment to his liking
I agree with @Andomar
1

1. For MySQL:

SELECT FIND_IN_SET(5, columnname) AS result 
FROM table

2.For Postgres SQL :

SELECT * 
FROM TABLENAME f
WHERE 'searchvalue' = ANY (string_to_array(COLUMNNAME, ','))

Example

select * 
from customer f
where '11' = ANY (string_to_array(customerids, ','))

Comments

1

You can achieve this by following function.

Run following query to create function.

DELIMITER ||
CREATE FUNCTION `TOTAL_OCCURANCE`(`commastring` TEXT, `findme`     VARCHAR(255)) RETURNS int(11)
NO SQL
-- SANI: First param is for comma separated string and 2nd for string to find.
return ROUND (   
    (
        LENGTH(commastring)
        - LENGTH( REPLACE ( commastring, findme, "") ) 
    ) / LENGTH(findme)        
);

And call this function like this

msyql> select TOTAL_OCCURANCE('A,B,C,A,D,X,B,AB', 'A');

1 Comment

if it is counting properly, you should never need to use ROUND... if it doesn't return a whole number then there's some other issue
0

The correct answer would be to fix the table design by splitting the comma separated string into rows.


If the schema can not be changed , you could try the following approach which requires MySQL 8+.

We can use JSON_TABLE to split comma-separated values dynamically.


Consider the following data example

CREATE TABLE shirts (
    id INT PRIMARY KEY,
    colors VARCHAR(50) NOT NULL
);

INSERT INTO shirts VALUES (1,'1,2,3'), (2,'2,3,4'), 
  (3,'3,4'), (4,'1,2'), (5,'12,14');

Split the colors

SELECT s.id,
       j.colors
FROM shirts s
JOIN JSON_TABLE(
    CONCAT('[\"', REPLACE(REPLACE(s.colors,' ',''), ',', '\",\"'), '\"]'),  
    '$[*]' COLUMNS (colors VARCHAR(10) PATH '$')  
) AS j

Retrieve all id that have colors "1" included

 SELECT s.id,
        j.colors
 FROM shirts s
 JOIN JSON_TABLE(
                CONCAT('[\"', REPLACE(REPLACE(s.colors,' ',''), ',', '\",\"'), '\"]'),  
                       '$[*]' COLUMNS (colors VARCHAR(10) PATH '$')  
                 ) AS j
 WHERE j.colors = 1;

Above query we could use as a JOIN to retrieve all columns from the table

SELECT sh.*
FROM shirts sh
INNER JOIN (SELECT s.id,
                   j.colors
            FROM shirts s
            JOIN JSON_TABLE(
                          CONCAT('[\"', REPLACE(REPLACE(s.colors,' ',''), ',', '\",\"'), '\"]'),  
                                 '$[*]' COLUMNS (colors VARCHAR(10) PATH '$')  
                          ) AS j
            WHERE j.colors = 1
           ) sub on sub.id=sh.id;

See example


Comments

-11

All the answers are not really correct, try this:

select * from shirts where 1 IN (colors);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.