从一组值中,如何找到未存储在表的列中的值?


12

我有一个表,它可能存储数十万个整数

desc id_key_table;

+----------------+--------------+------+-----+---------+-------+
| Field          | Type         | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+-------+
| id_key         | int(16)      | NO   | PRI | NULL    |       |
+----------------+--------------+------+-----+---------+-------+

从程序中,我有大量的整数。我想看看上面的id_key列中没有这些整数中的哪个。

到目前为止,我已经提出了以下方法:

1)遍历每个整数并执行:

select count(*) count from id_key_table where id_key = :id_key

当count为0时,表中缺少id_key。

这似乎是一种可怕的,可怕的方式。


2)创建一个临时表,将每个值插入临时表,然后在两个表上执行JOIN。

create temporary table id_key_table_temp (id_key int(16) primary key );

insert into id_key_table_temp values (1),(2),(3),...,(500),(501);

select temp.id_key
from id_key_table_temp temp left join id_key_table as main 
         on temp.id_key = main.id_key 
where main.killID is null;

drop table id_key_table_temp;

这似乎是最好的方法,但是,我敢肯定还有一种我尚未想到的更好的方法。我希望不必创建临时表并使用一个查询来确定缺少哪些整数。

是否有针对此类搜索的适当查询?

(MySQL)


2
我喜欢您如何问您的问题(欢迎使用DBA),但是,它在stackoverflow上可能更合适,因为它可以处理与某种程序(本身不是dba)进行交互
Derek Downey

谢谢您的欢迎,我认为像这样的地方可能比stackoverflow更具权威。我不介意在那儿再问。
克林顿

2
按照建议,我重新发布到StackOverflow:stackoverflow.com/questions/5967822/…–
克林顿(Clinton)

在以下问题中,针对sql server处理了类似情况:将大量数据发送到存储的proc的技术。您应该在那里发现该问题在其他数据库环境中也类似。无论如何,我寻求解决方案。2-发送ID列表,解析,放入表中,加入主表。那就是如果您不能使用其他解决方案,但是在这里您必须挖掘:-)。
玛丽安

Answers:


7

到目前为止,使用LEFT JOIN的第二个解决方案是最好的方法。我不会使用临时表,而是会使用常规表并在需要运行查询时随时用新值填充它。


5

听起来“大整数集”仍然比“数十万个整数”表小得多。有了这种假设,除非MySQL中没有一种方法可以将整数数组用作SQL语句中的表,否则第二种选择可能是最好的。它应该对临时表和主表上的索引进行全面扫描。主要好处是它只需扫描一次包含数十万个整数的索引,并且只需要将结果发送给客户端即可。您的查询可以(但不必)重写为以下内容:

SELECT * FROM id_key_table_temp 
WHERE id_key NOT IN (select id_key FROM id_key_table);

我不认可常规表上的临时表,因为我不了解MySQL平台上的差异。在Oracle中,最好使用一个临时表,但是在Oracle中,您只需将数组用作表并直接连接到该表即可。
雷·

3

除了使用临时表并插入之外insert into id_key_table_temp values (1),(2),(3),...,(500),(501);,您还可以使用要检查的所有值构造一个子查询:

select id_key
from ( select @row := @row + 1 as id_key 
       from (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s1,
            (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s2,
            (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s3,
            (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s4,
            (select @row:=0) s5 ) s
where id_key in(1, 2, 3, 500, 501)
      and id_key not in (select id_key from main);

2

如我的评论所述,这可能更适合stackoverflow。但是,我认为这两种解决方案都不是最好的:

解决方案1需要多个选择调用,效率很低

解决方案2更好,但是我不确定插入这么多值的成本是最好的解决方案。

可能的解决方案3是进行一个查询:

SELECT DISTINCT id_key FROM id_key_table

并以编程方式从整数集和数据库中获取差异。最糟糕的是,(因为它有很多整数)此路由应该比解决方案1更好。解决方案2有可能还返回很多整数(如果表中的数据集不在您的数据集中),因此它可以取决于™!


我不喜欢这种解决方案,因为结果集非常大。
克林顿

@Clinton是的,但是如果您没有提供足够的整数来过滤掉它,那么在第二个解决方案中它也可能很大。
德里克·唐尼

2

我在StackOverflow中解决了很多问题,但是我想详细说明如何使用永久性临时(PermTemp)表。(永久温度,那不是矛盾吗?)

StackOverflow中,我有存储过程test.CreateSampleTable和test.GetMissingIntegers创建一个示例表,然后创建一个动态临时表以进行填充,然后再进行大的JOIN查找差异。

这次,让我们与永久表一起创建样本表。

这是test.LoadSampleTables:

DELIMITER $$

DROP PROCEDURE IF EXISTS `LoadSampleTables` $$
CREATE DEFINER=`lwdba`@`127.0.0.1` PROCEDURE `LoadSampleTables`(maxinttoload INT)
BEGIN

  DECLARE X,OKTOUSE,MAXLOOP INT;

  DROP TABLE IF EXISTS test.id_key_table;
  DROP TABLE IF EXISTS test.id_key_table_keys;
  CREATE TABLE test.id_key_table (id_key INT(16)) ENGINE=MyISAM;
  CREATE TABLE test.id_key_table_keys (id_key INT(16)) ENGINE=MyISAM;

  SET X=1;
  WHILE X <= maxinttoload DO
    INSERT INTO test.id_key_table VALUES (X);
    SET X = X + 1;
  END WHILE;
  ALTER TABLE test.id_key_table ADD PRIMARY KEY (id_key);

  SET MAXLOOP = FLOOR(SQRT(maxinttoload));
  SET X = 2;
  WHILE X <= MAXLOOP DO
    DELETE FROM test.id_key_table WHERE MOD(id_key,X) = 0 AND id_key > X;
    SELECT MIN(id_key) INTO OKTOUSE FROM test.id_key_table WHERE id_key > X;
    SET X = OKTOUSE;
  END WHILE;
  OPTIMIZE TABLE test.id_key_table;

  INSERT INTO test.id_key_table_keys SELECT id_key FROM test.id_key_table;
  ALTER TABLE test.id_key_table_keys ADD PRIMARY KEY (id_key);
  OPTIMIZE TABLE test.id_key_table_keys;

END $$

DELIMITER ;

运行此命令后,这里是表及其内容:

mysql> call test.loadsampletables(25);
+-------------------+----------+----------+----------+
| Table             | Op       | Msg_type | Msg_text |
+-------------------+----------+----------+----------+
| test.id_key_table | optimize | status   | OK       |
+-------------------+----------+----------+----------+
1 row in set (0.20 sec)

+------------------------+----------+----------+----------+
| Table                  | Op       | Msg_type | Msg_text |
+------------------------+----------+----------+----------+
| test.id_key_table_keys | optimize | status   | OK       |
+------------------------+----------+----------+----------+
1 row in set (0.28 sec)

Query OK, 0 rows affected (0.29 sec)

mysql> select * from test.id_key_table;
+--------+
| id_key |
+--------+
|      1 |
|      2 |
|      3 |
|      5 |
|      7 |
|     11 |
|     13 |
|     17 |
|     19 |
|     23 |
+--------+
10 rows in set (0.00 sec)

mysql> select * from test.id_key_table_keys;
+--------+
| id_key |
+--------+
|      1 |
|      2 |
|      3 |
|      5 |
|      7 |
|     11 |
|     13 |
|     17 |
|     19 |
|     23 |
+--------+
10 rows in set (0.00 sec)

这是PermTemp表的触发器

mysql> DELIMITER $$
mysql>
mysql> CREATE TRIGGER test.AddPermTempKey AFTER INSERT ON test.id_key_table
    -> FOR EACH ROW
    -> BEGIN
    ->     INSERT IGNORE INTO test.id_key_table_keys VALUES (NEW.id_key);
    -> END $$
Query OK, 0 rows affected (0.09 sec)

mysql>
mysql> CREATE TRIGGER test.DeletePermTempKey AFTER DELETE ON test.id_key_table
    -> FOR EACH ROW
    -> BEGIN
    ->     DELETE FROM test.id_key_table_keys WHERE id_key = OLD.id_key;
    -> END $$
Query OK, 0 rows affected (0.08 sec)

mysql>
mysql> DELIMITER ;

现在,让我们导入新的记录记录表test.weekly_batch,之前使用过的某些键,以及其他全新的键:

mysql> CREATE TABLE test.weekly_batch (id_key INT(16)) ENGINE=MyISAM;
Query OK, 0 rows affected (0.04 sec)

mysql> INSERT INTO test.weekly_batch VALUES (17),(19),(23),(29),(31),(37),(41);
Query OK, 7 rows affected (0.00 sec)
Records: 7  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE test.weekly_batch ADD PRIMARY KEY (id_key);
Query OK, 7 rows affected (0.08 sec)
Records: 7  Duplicates: 0  Warnings: 0

让我们进行test.weekly_batch并将其安全地合并到test.id_key_table_keys中,并形成表test.new_keys_to_load:

DELIMITER $$

DROP PROCEDURE IF EXISTS `test`.`ImportWeeklyBatch` $$
CREATE PROCEDURE `test`.`ImportWeeklyBatch` ()
TheStoredProcedure:BEGIN

  DECLARE RCOUNT INT;

  SELECT COUNT(1) INTO RCOUNT FROM information_schema.tables
  WHERE table_schema='test' AND table_name='weekly_batch';
  IF RCOUNT = 0 THEN
    LEAVE TheStoredProcedure;
  END IF;
  SELECT COUNT(1) INTO RCOUNT FROM test.weekly_batch;
  IF RCOUNT = 0 THEN
    LEAVE TheStoredProcedure;
  END IF;
  DROP TABLE IF EXISTS test.new_keys_to_load;
  CREATE TABLE test.new_keys_to_load (id_key INT(16));
  INSERT INTO test.new_keys_to_load (id_key)
  SELECT id_key FROM test.weekly_batch A
  LEFT JOIN test.id_key_table_keys B USING (id_key)
  WHERE B.id_key IS NULL;

  SELECT * FROM test.new_keys_to_load;

END $$

DELIMITER ;

结果如下:

mysql> call test.importweeklybatch;
+--------+
| id_key |
+--------+
|     29 |
|     31 |
|     37 |
|     41 |
+--------+
4 rows in set (0.14 sec)

从这一点来看,只需将new_keys_to_load表用作要导入的新品牌密钥列表即可。由于new_keys_to_load小于PermTemp表,因此应始终在LEFT JOIN左侧使用new_keys_to_load。


我已经在SO上回答了这个问题
RolandoMySQLDBA,2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.