A partir de un conjunto de valores, ¿cómo encuentro los valores no almacenados en la columna de una tabla?

12

Tengo una tabla que potencialmente almacenará cientos de miles de enteros.

desc id_key_table;

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

De un programa, tengo un gran conjunto de enteros. Me gustaría ver cuáles de estos enteros NO están en la columna id_key anterior.

Hasta ahora he llegado con los siguientes enfoques:

1) Iterar a través de cada número entero y realizar un:

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

Cuando el recuento es 0, falta la tecla id_ de la tabla.

Esto parece una forma horrible, horrible de hacerlo.


2) Cree una tabla temporal, inserte cada uno de los valores en la tabla temporal y realice una UNIÓN en las dos tablas.

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;

Este parece ser el mejor enfoque, sin embargo, estoy seguro de que hay un enfoque mucho mejor en el que aún no he pensado. Prefiero no tener que crear una tabla temporal y usar una consulta para determinar qué enteros faltan.

¿Hay una consulta adecuada para este tipo de búsqueda?

(MySQL)

Clinton
fuente
2
Sin embargo, me gusta cómo formuló su pregunta (Bienvenido a DBA), probablemente sea mucho más apropiado en stackoverflow ya que se trata de interactuar con un programa de algún tipo (no dba per se)
Derek Downey
Gracias por la bienvenida, pensé que un lugar como este podría tener más gurús que stackoverflow. Sin embargo, no me importa volver a preguntar allí.
Clinton
2
Como sugerí, volví a publicar en StackOverflow: stackoverflow.com/questions/5967822/…
Clinton
Se trató una situación similar para el servidor SQL en esta pregunta: Técnica para enviar muchos datos al proceso almacenado . Debería encontrar allí que el problema es similar en otros entornos db. De todos modos, voy por la solución no. 2 - envía la lista de identificadores, analiza, coloca en la tabla, únete a tu tabla principal Eso si no puedes usar otras soluciones, pero aquí tienes que cavar :-).
Marian

Respuestas:

7

Su segunda solución usando el LEFT JOIN es, con mucho, el mejor enfoque. No usaría una tabla temporal, usaría una tabla normal y la llenaría con nuevos valores cada vez que quisiera ejecutar la consulta.

Michael Riley - también conocido como Gunny
fuente
5

Parece que el "gran conjunto de enteros" es aún considerablemente más pequeño que la tabla con "cientos de miles de enteros". Con esa suposición y a menos que haya una manera en MySQL de usar una matriz de sus enteros como una tabla en su declaración SQL, su segunda opción es probablemente la mejor. Debe hacer un análisis completo de la tabla temporal y el índice en la tabla principal. El beneficio principal es que solo tiene que escanear el índice que contiene cientos de miles de enteros una vez y solo tiene que enviarle al cliente los resultados. Su consulta podría (pero no necesita ser) reescrita como sigue:

SELECT * FROM id_key_table_temp 
WHERE id_key NOT IN (select id_key FROM id_key_table);
Leigh Riffel
fuente
No estoy respaldando una tabla temporal sobre una tabla normal ya que no tengo conocimiento de las diferencias en la plataforma MySQL. En Oracle, una tabla temporal probablemente sería lo mejor, pero luego en Oracle simplemente usaría una matriz como tabla y se uniría directamente a ella.
Leigh Riffel
3

En lugar de una tabla temporal e insertar con insert into id_key_table_temp values (1),(2),(3),...,(500),(501);, puede construir una subconsulta con todos los valores que está intentando verificar:

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);
Jack dice que intente topanswers.xyz
fuente
2

Como se señaló en mi comentario, esto probablemente sea más adecuado para stackoverflow. Sin embargo, creo que ambas soluciones no son las mejores:

La solución 1 requiere múltiples llamadas selectas, muy ineficiente

La solución 2 es mejor, pero no estoy seguro de que el costo de insertar tantos valores sea la mejor solución.

Una posible solución 3 sería hacer una consulta:

SELECT DISTINCT id_key FROM id_key_table

y programáticamente obtenga la diferencia de su conjunto de enteros y de lo que hay en el DB En el peor de los casos, (ya que son muchos enteros) Esta ruta debería ser mejor que la Solución 1. La Solución 2 TAMBIÉN tiene el potencial de devolver muchos enteros (si la tabla tiene un montón que no está en su conjunto de datos), entonces depende ™!

Derek Downey
fuente
No soy fanático de esta solución ya que el conjunto de resultados sería muy grande.
Clinton
@Clinton es cierto, pero también podría ser muy grande en su segunda solución, si no proporciona suficientes enteros para filtrarlo.
Derek Downey
2

Más o menos abordé esto en StackOverflow , pero me gustaría dar más detalles sobre el uso de la tabla temporal permanente (PermTemp). ( temperatura permanente, ¿no es eso un oxímoron ?)

En StackOverflow , tuve la prueba de procedimiento almacenado. CreateSampleTable y test.GetMissingIntegers hacen una tabla de muestra y luego crean una tabla temporal dinámica para rellenar antes de hacer la gran JOIN para encontrar diferencias.

Esta vez, creemos la tabla de muestra junto con la tabla de tabla permanente.

Aquí está 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 ;

Después de ejecutar esto, aquí están las tablas y sus contenidos:

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)

Aquí están los disparadores para la tabla 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 ;

Ahora, importemos un nuevo lote de registros, tabla test.weekly_batch, algunas claves usadas antes, otras claves completamente nuevas:

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

Tomemos test.weekly_batch y combinémoslo con seguridad en test.id_key_table_keys y formemos la tabla 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 ;

Aquí está el resultado:

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

A partir de este punto, simplemente use la tabla new_keys_to_load como la lista de nuevas claves para importar. Como new_keys_to_load es más pequeño que la tabla PermTemp, siempre debe usar new_keys_to_load en el lado izquierdo de LEFT JOIN.

RolandoMySQLDBA
fuente
Respondí esta en SO ya
RolandoMySQLDBA