27

I have a column in one of my table where I store multiple ids seperated by comma's. Is there a way in which I can use this column's value in the "IN" clause of a query.

The column(city) has values like 6,7,8,16,21,2

I need to use as

select * from table where e_ID in (Select city from locations where e_Id=?)

I am satisfied with Crozin's answer, but I am open to suggestions, views and options.

Feel free to share your views.

Sashi Kant
  • 13,277
  • 9
  • 44
  • 71

11 Answers11

32

Building on the FIND_IN_SET() example from @Jeremy Smith, you can do it with a join so you don't have to run a subquery.

SELECT * FROM table t
JOIN locations l ON FIND_IN_SET(t.e_ID, l.city) > 0
WHERE l.e_ID = ?

This is known to perform very poorly, since it has to do table-scans, evaluating the FIND_IN_SET() function for every combination of rows in table and locations. It cannot make use of an index, and there's no way to improve it.

I know you said you are trying to make the best of a bad database design, but you must understand just how drastically bad this is.

Explanation: Suppose I were to ask you to look up everyone in a telephone book whose first, middle, or last initial is "J." There's no way the sorted order of the book helps in this case, since you have to scan every single page anyway.

The LIKE solution given by @fthiella has a similar problem with regards to performance. It cannot be indexed.

Also see my answer to Is storing a delimited list in a database column really that bad? for other pitfalls of this way of storing denormalized data.

If you can create a supplementary table to store an index, you can map the locations to each entry in the city list:

CREATE TABLE location2city (
 location INT,
 city INT,
 PRIMARY KEY (location, city)
); 

Assuming you have a lookup table for all possible cities (not just those mentioned in the table) you can bear the inefficiency one time to produce the mapping:

INSERT INTO location2city (location, city)
  SELECT l.e_ID, c.e_ID FROM cities c JOIN locations l
  ON FIND_IN_SET(c.e_ID, l.city) > 0;

Now you can run a much more efficient query to find entries in your table:

SELECT * FROM location2city l
JOIN table t ON t.e_ID = l.city
WHERE l.e_ID = ?;

This can make use of an index. Now you just need to take care that any INSERT/UPDATE/DELETE of rows in locations also inserts the corresponding mapping rows in location2city.

Community
  • 1
  • 1
Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
  • I've used the FIND_IN_SET option. This is the best answers of the available here. Instead of joins and sub queries this suits best. But unless it's very important dont try to store comma separated values into a column. That doesn't fit the RDBMS. I've used that because the table and data were created much before and there were lot of data which I dont want to risk by Data Transfer. – Vijay Nov 23 '12 at 11:36
  • Solved exactly what I needed. Thanks. – SudhirR Nov 23 '19 at 08:22
25

From MySQL's point of view you're not storing multiple ids separated by comma - you're storing a text value, which has the exact same meaing as "Hello World" or "I like cakes!" - i.e. it doesn't have any meaing.

What you have to do is to create a separated table that will link two objects from the database together. Read more about many-to-many or one-to-many (depending on your requirements) relationships in SQL-based databases.

Crozin
  • 43,890
  • 13
  • 88
  • 135
  • Thanks for your suggestion and I accept that the design is poor, but I have some constraints which I cannot change. Is there a way to implement this.... – Sashi Kant May 07 '12 at 11:23
24

Rather than use IN on your query, use FIND_IN_SET (docs):

SELECT * FROM table 
WHERE 0 < FIND_IN_SET(e_ID, (
             SELECT city FROM locations WHERE e_ID=?))

The usual caveats about first form normalization apply (the database shouldn't store multiple values in a single column), but if you're stuck with it, then the above statement should help.

Jeremy Smyth
  • 23,270
  • 2
  • 52
  • 65
  • Thanks for your answer. This is very helpfull. Any light on performance? – Sashi Kant Nov 16 '12 at 18:01
  • 2
    Performance is going to be relatively poor, because `FIND_IN_SET` cannot use any indexes. If you're concerned about performance (and have the authority to do so), you really will need to split your `locations`.`city` column into another table using 1nf, and you can then use indexes to help your performance. – Jeremy Smyth Nov 17 '12 at 18:03
  • 1
    As @JeremySmyth says, theory is storing a comma-separated list of numbers in a column violates a basic rule of database design, the atomicity rule (one cell, one value). So, it would be better to redesign that. BUT, if the constraints don't allow that, go with `FIND_IN_SET`. – inigomedina Nov 20 '12 at 08:33
22

This does not use IN clause, but it should do what you need:

Select *
from table
where
  CONCAT(',', (Select city from locations where e_Id=?), ',')
  LIKE
  CONCAT('%,', e_ID, ',%')

but you have to make sure that e_ID does not contain any commas or any jolly character.

e.g.

CONCAT(',', '6,7,8,16,21,2', ',') returns ',6,7,8,16,21,2,'

e_ID=1  --> ',6,7,8,16,21,2,' LIKE '%,1,%'  ? FALSE
e_ID=6  --> ',6,7,8,16,21,2,' LIKE '%,6,%'  ? TRUE
e_ID=21 --> ',6,7,8,16,21,2,' LIKE '%,21,%' ? TRUE
e_ID=2  --> ',6,7,8,16,21,2,' LIKE '%,2,%'  ? TRUE
e_ID=3  --> ',6,7,8,16,21,2,' LIKE '%,3,%'  ? FALSE
etc.
fthiella
  • 48,073
  • 15
  • 90
  • 106
  • yes ... convert the comparison field from an int to a string, and use like. This works. I know the problem is caused by bad DB design, but unfortunately sometimes we have to work with this bad DB design sometimes without enough time to reengineer it. – Aaron Wallentine Aug 18 '17 at 18:05
8

Don't know if this is what you want to accomplish. With MySQL there is feature to concatenate values from a group GROUP_CONCAT

You can try something like this:

select * from table where e_ID in (Select GROUP_CONCAT(city SEPARATOR ',') from locations where e_Id=?)
SubRed
  • 3,169
  • 1
  • 18
  • 17
7

this one in for oracle ..here string concatenation is done by wm_concat

select * from table where e_ID in (Select wm_concat(city) from locations where e_Id=?)

yes i agree with raheel shan .. in order put this "in" clause we need to make that column into row below code one do that job.

select * from table  where to_char(e_ID) 
in (
  select substr(city,instr(city,',',1,rownum)+1,instr(city,',',1,rownum+1)-instr(city,',',1,rownum)-1) from 
  (
  select ','||WM_CONCAT(city)||',' city,length(WM_CONCAT(city))-length(replace(WM_CONCAT(city),','))+1 CNT from locations where e_Id=? ) TST 
  ,ALL_OBJECTS OBJ where TST.CNT>=rownum
    ) ;
GKV
  • 501
  • 4
  • 8
6

you should use

FIND_IN_SET Returns position of value in string of comma-separated values

mysql> SELECT FIND_IN_SET('b','a,b,c,d');
    -> 2
Rinzler
  • 2,139
  • 1
  • 27
  • 44
6

You need to "SPLIT" the city column values. It will be like:

SELECT *
  FROM table
 WHERE e_ID IN (SELECT TO_NUMBER(
                                 SPLIT_STR(city /*string*/
                                           , ',' /*delimiter*/
                                           , 1 /*start_position*/
                                           )
                                 )
                  FROM locations);

You can read more about the MySQL split_str function here: http://blog.fedecarg.com/2009/02/22/mysql-split-string-function/

Also, I have used the TO_NUMBER function of Oracle here. Please replace it with a proper MySQL function.

Rachcha
  • 8,486
  • 8
  • 48
  • 70
5

IN takes rows so taking comma seperated column for search will not do what you want but if you provide data like this ('1','2','3') this will work but you can not save data like this in your field whatever you insert in the column it will take the whole thing as a string.

Muhammad Raheel
  • 19,823
  • 7
  • 67
  • 103
2

You can create a prepared statement dynamically like this

set @sql = concat('select * from city where city_id in (',
                  (select cities from location where location_id = 3),
                  ')');
prepare in_stmt from @sql;
execute in_stmt;
deallocate prepare in_stmt;
Andreas Wederbrand
  • 38,065
  • 11
  • 68
  • 78
0

Ref: Use a comma-separated string in an IN () in MySQL

Recently I faced the same problem and this is how I resolved it.

It worked for me, hope this is what you were looking for.

select * from table_name t where (select (CONCAT(',',(Select city from locations l where l.e_Id=?),',')) as city_string) LIKE CONCAT('%,',t.e_ID,',%');

Example: It will look like this

select * from table_name t where ',6,7,8,16,21,2,' LIKE '%,2,%';
Community
  • 1
  • 1
Vi8L
  • 958
  • 10
  • 12