Проверка того, попадает ли какой-либо список значений в таблицу диапазонов - PullRequest
1 голос
/ 28 апреля 2010

Я проверяю, попадает ли какой-либо из списка целых чисел в список диапазонов. Диапазоны определены в таблице, определенной примерно так:

#   Extra   Type    Field       Default Null    Key 
0           int(11) rangeid     0       NO      PRI 
1           int(11) max         0       NO      MUL 
2           int(11) min         0       NO      MUL 

Использование MySQL 5.1 и Perl 5.10.

Я могу проверить, находится ли одно значение, скажем, 7, в каком-либо из диапазонов с помощью оператора типа

SELECT 1
  FROM range
  WHERE 7 BETWEEN min AND max

Если 7 находится в любом из этих диапазонов, я получаю одну строку назад. Если это не так, строки не возвращаются.

Теперь у меня есть список, скажем, 50 из этих значений, которые в настоящее время не хранятся в таблице. Я собираю их, используя map:

my $value_list = '('
  . ( join ', ', map { int $_ } @values )
  . ')'
  ;

Я хочу посмотреть, попадет ли какой-либо из элементов в список в какой-либо из диапазонов, но меня не особо интересует, какое число или какой диапазон. Я хотел бы использовать синтаксис, такой как:

SELECT 1
  FROM range
  WHERE (1, 2, 3, 4, 5, 6, 7, 42, 309, 10000) BETWEEN min AND max

MySQL любезно наказывает меня за такой синтаксис:

Operand should contain 1 column(s)

Я пинговал # mysql , которые были весьма полезны. Однако, уже написав это к тому времени, когда они ответили, и подумав, что было бы полезно зафиксировать ответ в более постоянной среде, я решил в любом случае опубликовать вопрос. Может быть, ТАК предоставит другое решение?

Ответы [ 3 ]

2 голосов
/ 14 августа 2010

Это звучало как интересная проблема. Я создал таблицу тестового диапазона примерно так:

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

Я вставил 50 000 строк в эту таблицу, каждая из которых имеет диапазон max-min = 10, например:

mysql> select * from test_ranges limit 2;
+---------+-----+-----+
| rangeid | max | min |
+---------+-----+-----+
|       1 |  15 |   5 | 
|       2 |  20 |  10 | 
+---------+-----+-----+
2 rows in set (0.00 sec)

Мой Perl-код для получения диапазонов, которые соответствуют списку целых чисел, - это создать временную таблицу для хранения целых чисел и попросить MySQL выполнить сопоставление для меня:

$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");

Это возвращает мне правильный список. В клиенте mysql это будет выглядеть так:

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)

Или, просто для списка совпадающих значений:

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 (по крайней мере 5.0, на котором я сейчас) заявляет через EXPLAIN, что он не использует индекс для сравнения обычным способом. Тем не менее, он сообщает « Range проверен для каждой записи », что по существу означает, что он делает то, что вы думаете: обрабатывает значения из таблицы test_vals как константы и ищет их таблица test_ranges с индексом 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)

Это довольно быстро, но я не знаю, сколько у вас будет больше строк, чем 8 и 50 КБ, с которыми я тестировал. Я полагаю, что создание такой временной таблицы было бы оптимальным решением, если у вас есть несколько небольших значений, которые вы ищете.

1 голос
/ 28 апреля 2010

Вы можете создать SQL-запрос в Perl, который будет работать с несколькими значениями, следующим образом:

sub check_range {
    'SELECT 1 FROM range WHERE ' .
        join ' OR ' =>
        map "($_ BETWEEN min AND max)" => @_
}

print check_range( 1, 2, 3, 4, 5, 6, 7, 42, 309, 10000 ), "\n";

> SELECT 1 FROM range WHERE (1 BETWEEN min AND max) OR (2 BETWEEN min AND max)
> OR (3 BETWEEN min AND max) OR (4 BETWEEN min AND max) ...
1 голос
/ 28 апреля 2010

Честно говоря, если проверяемый список имеет одноразрядный размер, я бы либо проходил циклическую проверку в Perl (проверка была вашим запросом), либо если вас беспокоит соединение / Запросы запускаются, заполняют их во временную таблицу и зацикливают ее в цикле SQL, вытаскивая 1 значение за раз в переменную, удаляя это значение из временной таблицы и снова выполняя свой собственный запрос на одну проверку для этой переменной внутри петли.

Вот код Sybase - надеюсь, он легко переводится на MySQL

-- previously, CREATE TABLE #your_temp_table (num int)
CREATE TABLE #in_range (num int)
DECLARE @seven int -- This is a JOKE! NEVER use a variable name like that!!!
WHILE (exists (select 1 from #your_temp_table)) 
BEGIN
    SELECT @seven = min(num) from #your_temp_table
    DELETE #your_temp_table WHERE num = @seven
    INSERT #in_range
        SELECT @seven
        FROM range
        WHERE @seven BETWEEN min AND max
END
SELECT num from #in_range
DROP TABLE #in_range

У меня такое ощущение, что это можно сделать намного элегантнее, но это, по крайней мере, работает в отсутствие лучшего решения:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...