Reputation: 143
I'm trying to extract specific rows from a mySQL table that contains lists of numbers.
I have a single table with 2 columns- id and data. Each row has a sorted, comma separated record of numbers ranging from 1 to 1000. I want to only select records with a partial or full set of specific numbers in it. I've tried using LIKE and IN and also looked at FIND_IN_SET.
t1.id t1.data
1 2,9,569
2 2,9,991,979
3 9,569,763
4 52,57,569,763,892,897
5 763
6 2,9,10,15,151,569,771,801,888,973
If I'm looking for rows with one or more of the values (2,9,569,763), I don't want to have to write:
SELECT t1.id from t1
WHERE t1.data NOT IN (1,3,4,5,6,7,8,10,11,...........,1000);
to return 3 rows, t1.id = 1,3 and 5.
Is there a simpler way? Something like (in mySQL):
SELECT t1.id from t1
WHERE t1.data "only includes one or more of" (2,9,569,763);
Upvotes: 1
Views: 157
Reputation: 562991
Paul Spiegel's answer gives the correct result, but it can't be optimized with indexes, because of the use of FIND_IN_SET(). It will always do a table-scan, which will get more and more expensive the more rows you have.
You should take this as a clue that storing lists of numbers as a comma-separated list in a string column is a bad idea when you actually want to do some searches for discrete members of that list.
What you should do instead is store the list as a child table, with one member per row.
CREATE TABLE mydata (
t1id INT NOT NULL,
member INT NOT NULL,
PRIMARY KEY (t1id, member),
FOREIGN KEY (t1id) REFERENCES t1(id)
);
INSERT INTO mydata VALUES
(1,2),(1,9),(1,569),
(2,2),(2,9),(2,991),(2,979),
(3,9),(3,569),(3,763),
(4,52,(4,57,(4,569,(4,763,(4,892),(4,897),
(5,763),
(6,2),(6,9),(6,10),(6,15),(6,151),(6,569),(6,771),(6,801),(6,888),(6,973);
Now you would join your original table t1
to mydata
but exclude the matches to values in your desired list.
mysql> select * from t1 left join mydata on t1.id=mydata.t1id
and mydata.member not in (2,9,569,763);
+----+------+--------+
| id | t1id | member |
+----+------+--------+
| 1 | NULL | NULL |
| 2 | 2 | 979 |
| 2 | 2 | 991 |
| 3 | NULL | NULL |
| 4 | 4 | 52 |
| 4 | 4 | 57 |
| 4 | 4 | 892 |
| 4 | 4 | 897 |
| 5 | NULL | NULL |
| 6 | 6 | 10 |
| 6 | 6 | 15 |
| 6 | 6 | 151 |
| 6 | 6 | 771 |
| 6 | 6 | 801 |
| 6 | 6 | 888 |
| 6 | 6 | 973 |
+----+------+--------+
You see there are NULLs of id 1, 3, 5 because there are no values that are NOT in your specified list. Those are the id's that you want to return.
mysql> select t1.id from t1 left join mydata on t1.id=mydata.t1id
and mydata.member not in (2,9,569,763)
where mydata.member is null;
+----+
| id |
+----+
| 1 |
| 3 |
| 5 |
+----+
Upvotes: 1
Reputation: 31832
Not simple but..
Count single hits and compare it with the number of all values in the data
column. They must be equal.
select id, data
from t1
where (find_in_set(2, data) > 0)
+ (find_in_set(9, data) > 0)
+ (find_in_set(569, data) > 0)
+ (find_in_set(763, data) > 0)
= char_length(data) - char_length(replace(data, ',', '')) + 1
Demo: https://www.db-fiddle.com/f/oLcrz4vmXRWXqYQhCnZA5z/0
Upvotes: 1
Reputation: 7880
Try to use the REGEXP function:
SELECT t1.id from t1
WHERE t1.data REGEXP '(2,9,569,763)';
Upvotes: 0