检查值列表中是否有任何值属于范围表

时间:2010-04-27 20:08:34

标签: mysql perl

我正在查看是否有任何整数列表都在范围列表中。范围在定义的表中定义,如:

#   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非常有帮助。然而,在他们回应的时候已经写完这篇文章并且认为在更永久的媒介中修复答案会有所帮助,我想我无论如何都会发布这个问题。也许SO会提供不同的解决方案吗?

3 个答案:

答案 0 :(得分:2)

这听起来像一个有趣的问题。我创建了一个测试范围表,如下所示:

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 checked for each recordessentially表示它符合您的想法:将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行和50K行多少行。我的猜测是,创建一个像这样的临时表将是最佳的解决方案,如果你有超过一小部分你正在查找的值。

答案 1 :(得分:1)

说实话,如果要检查的列表是一位数的大小,我要么循环检查Perl中的逐个检查(检查是你的查询),或者如果你担心连接/查询开始开销,将它们填充到临时表中并在SQL循环中循环它,一次将1个cvalue拉入变量,从临时表中删除该值并再次运行 - 对该变量进行自己的一次检查查询,在循环内。

这是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

我觉得这可以更优雅地完成,但这至少可以在更好的解决方案的荒谬中发挥作用:)

答案 2 :(得分:1)

您可以在Perl中构建一个SQL查询,它将使用多个值,如下所示:

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) ...