Reputation: 6672
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?
Upvotes: 112
Views: 198975
Reputation: 8973
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(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(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(s.colors, ',', '\",\"'), '\"]'),
'$[*]' COLUMNS (colors VARCHAR(10) PATH '$')
) AS j
WHERE j.colors = 1
) sub on sub.id=sh.id;
Upvotes: 0
Reputation: 1120
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');
Upvotes: 1
Reputation: 644
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, ','))
Upvotes: 0
Reputation: 238086
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
Upvotes: 220
Reputation: 474
select * from shirts where find_in_set('1',colors) <> 0
Works for me
Upvotes: 4
Reputation: 13936
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
).
Upvotes: 7
Reputation: 7
All the answers are not really correct, try this:
select * from shirts where 1 IN (colors);
Upvotes: -11
Reputation: 44343
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 !!!
Upvotes: 12
Reputation: 86386
FIND_IN_SET is your friend in this case
select * from shirts where FIND_IN_SET(1,colors)
Upvotes: 40
Reputation: 35798
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
Upvotes: 3
Reputation: 135808
Take a look at the FIND_IN_SET function for MySQL.
SELECT *
FROM shirts
WHERE FIND_IN_SET('1',colors) > 0
Upvotes: 30
Reputation: 3989
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'
Upvotes: 3