This sounded like an interesting problem. I created a test range table like so:
CREATE TABLE `test_ranges` (
`rangeid` int(11) NOT NULL,
`max` int(11) NOT NULL,
`min` int(11) NOT NULL,
PRIMARY KEY (`rangeid`),
KEY `idx_minmax` (`min`,`max`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
I inserted 50,000 rows into that table, each a range with max-min=10, like so:
mysql> select * from test_ranges limit 2;
+---------+-----+-----+
| rangeid | max | min |
+---------+-----+-----+
| 1 | 15 | 5 |
| 2 | 20 | 10 |
+---------+-----+-----+
2 rows in set (0.00 sec)
My perl code to get the ranges that match a list of integers is to create a temporary table to hold the integers, and ask MySQL to do the matching for me:
$DB->do_sql("CREATE TEMPORARY TABLE test_vals ( val int NOT NULL ) ENGINE=InnoDB");
for (12, 345, 394, 1450, 999, 9999, 99999, 999999 ) {
$DB->do_sql("INSERT INTO test_vals VALUES (?)", $_);
}
$answer = $DB->do_sql("SELECT DISTINCT * from test_vals, test_ranges WHERE val BETWEEN min AND max");
That returns me the correct list. In the mysql client that would look like:
mysql> SELECT DISTINCT * from test_vals, test_ranges WHERE val BETWEEN min AND max;
+-------+---------+--------+-------+
| val | rangeid | max | min |
+-------+---------+--------+-------+
| 12 | 1 | 15 | 5 |
| 12 | 2 | 20 | 10 |
| 345 | 67 | 345 | 335 |
| 345 | 68 | 350 | 340 |
| 345 | 69 | 355 | 345 |
| 394 | 77 | 395 | 385 |
| 394 | 78 | 400 | 390 |
| 1450 | 288 | 1450 | 1440 |
| 1450 | 289 | 1455 | 1445 |
| 1450 | 290 | 1460 | 1450 |
| 999 | 198 | 1000 | 990 |
| 999 | 199 | 1005 | 995 |
| 9999 | 1998 | 10000 | 9990 |
| 9999 | 1999 | 10005 | 9995 |
| 99999 | 19998 | 100000 | 99990 |
| 99999 | 19999 | 100005 | 99995 |
+-------+---------+--------+-------+
16 rows in set (0.00 sec)
Or, for just the list of matching values:
mysql> SELECT DISTINCT val from test_vals, test_ranges WHERE val BETWEEN min AND max;
+-------+
| val |
+-------+
| 12 |
| 345 |
| 394 |
| 999 |
| 1450 |
| 9999 |
| 99999 |
+-------+
7 rows in set (0.00 sec)
MySQL (at least 5.0, which I'm on) claims via EXPLAIN that it does not use an index for the comparison in a normal way. However, it reports "Range checked for each record" which essentially means it does what you'd think: treats the values from the test_vals
table as constants and looks them up in the test_ranges
table using the index idx_minmax
.
mysql> explain SELECT DISTINCT * from test_vals, test_ranges WHERE val BETWEEN min AND max \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: test_vals
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 8
Extra: Using temporary
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: test_ranges
type: ALL
possible_keys: idx_minmax
key: NULL
key_len: NULL
ref: NULL
rows: 48519
Extra: Range checked for each record (index map: 0x2)
2 rows in set (0.00 sec)
It's quite fast, but I don't know how many more rows you'll have than the 8 and 50K that I tested with. My guess is that creating a temporary table like this would be the optimal solution if you have more than a small handful of values you're looking up.