Reputation: 1064
So, I have an array like this in my programming language:
$animalsToSearch = ['fox', 'cat', 'dog'];
And I have a MySQL database table like this:
id | value
----+----------------
1 | dog,elephant
2 | cat,dog
3 | spider,fox,cat
4 | cat
5 | dog,rabit
Unfortunately, I cannot change the table to be more relational (yet).
I want to pull out how many times an item in my array is present in the database value
column. Now, the obvious way to do this would be something like:
<?php
$count = [];
foreach (['fox', 'cat', 'dog'] as $animal) {
$count[$animal] = some_function_that_gets_the_data($animal);
}
But there might be dozens of animals to search for, and I don't want to send a db request per animal. I have thought of the way below, and then using PHP to look at the values to see if the animal is present within... but it's a bit hacky. Might there be a better way?
select
`value`
from
`animal`
where
find_in_set('fox', value)
or find_in_set('cat', value)
or find_in_set('dog', value);
ysth's solution explain
'd
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 PRIMARY <derived2> NULL ALL NULL NULL NULL NULL 2 100.00 Using temporary; Using filesort
1 PRIMARY animal NULL ALL NULL NULL NULL NULL 1,879,296 100.00 Using where; Using join buffer (Block Nested Loop)
2 DERIVED NULL NULL NULL NULL NULL NULL NULL NULL NULL No tables used
3 UNION NULL NULL NULL NULL NULL NULL NULL NULL NULL No tables used
Upvotes: 0
Views: 337
Reputation: 98398
Join against your list:
select animallist.animal, count(*) as times
from (
select 'fox' as animal union all select 'cat' union all select 'dog'
) animallist
join animal on find_in_set(animallist.animal, animal.value)
group by animallist.animal
You can try forcing it to only read through animal once, I don't know if that will make it faster or slower:
select straight_join animallist.animal, count(*) as times
from animal on find_in_set(animallist.animal, animal.value)
join (
select 'fox' as animal union all select 'cat' union all select 'dog'
) animallist
group by animallist.animal
Upvotes: 1
Reputation: 562731
A conventional index cannot help improve the performance of FIND_IN_SET()
just like an index cannot help improve SUBSTRING()
or LIKE
with wildcards. The performance will never be good, and will get worse the larger your table grows.
But a fulltext index can help.
ALTER TABLE animals ADD FULLTEXT INDEX (value);
mysql> select * from animals where match(value) against ('rabbit' in boolean mode);
+----+--------------+
| id | value |
+----+--------------+
| 5 | doggy,rabbit |
+----+--------------+
1 row in set (0.00 sec)
mysql> select * from animals where match(value) against ('rabbit doggy' in boolean mode);
+----+----------------+
| id | value |
+----+----------------+
| 5 | doggy,rabbit |
| 1 | doggy,elephant |
| 2 | kittycat,doggy |
+----+----------------+
For my test I made all the animal names at least 4 characters, because the default ft_min_word_len
is 4 characters. Words shorter than that aren't indexed by a FULLTEXT index.
Read the sections on https://dev.mysql.com/doc/refman/8.0/en/fulltext-search.html for more information.
Upvotes: 0