À partir d'un ensemble de valeurs, comment trouver les valeurs non stockées dans la colonne d'une table?

12

J'ai une table qui peut potentiellement stocker des centaines de milliers d'entiers

desc id_key_table;

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

À partir d'un programme, j'ai un grand nombre d'entiers. J'aimerais voir lesquels de ces nombres entiers NE SONT PAS dans la colonne id_key ci-dessus.

Jusqu'à présent, j'ai trouvé les approches suivantes:

1) Itérer sur chaque entier et effectuer:

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

Lorsque count est 0, id_key est absent de la table.

Cela semble être une façon horrible et horrible de le faire.


2) Créez une table temporaire, insérez chacune des valeurs dans la table temporaire et effectuez un JOIN sur les deux tables.

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;

Cela semble être la meilleure approche, cependant, je suis sûr qu'il y a une bien meilleure approche à laquelle je n'ai pas encore pensé. Je préférerais ne pas avoir à créer une table temporaire et utiliser une requête pour déterminer les entiers manquants.

Existe-t-il une requête appropriée pour ce type de recherche?

(MySQL)

Clinton
la source
2
J'aime la façon dont vous avez posé votre question (Bienvenue dans DBA) cependant, elle est probablement beaucoup plus appropriée sur stackoverflow car elle traite de l'interaction avec un programme quelconque (pas dba en soi)
Derek Downey
Merci pour l'accueil, je pensais qu'un endroit comme celui-ci pourrait avoir plus de gourous que stackoverflow. Cela ne me dérange pas de demander à nouveau là-bas.
Clinton
2
Comme suggéré, j'ai republié sur StackOverflow: stackoverflow.com/questions/5967822/…
Clinton
Une situation similaire a été traitée pour le serveur sql dans cette question: Technique pour envoyer beaucoup de données dans le proc stocké . Vous devriez y trouver que le problème est similaire dans d'autres environnements db. Quoi qu'il en soit, je choisis la solution non. 2 - envoyer la liste des identifiants, analyser, mettre dans la table, joindre à votre table principale. Que si vous ne pouvez pas utiliser d'autres solutions, mais ici vous devez creuser :-).
Marian

Réponses:

7

Votre deuxième solution en utilisant LEFT JOIN est de loin la meilleure approche. Je n'utiliserais pas de table temporaire, j'utiliserais une table régulière et la remplirais avec de nouvelles valeurs chaque fois que vous voudriez exécuter la requête.

Michael Riley - AKA Gunny
la source
5

Il semble que le "grand ensemble d'entiers" soit encore beaucoup plus petit que le tableau avec "des centaines de milliers d'entiers". Avec cette supposition et à moins qu'il n'y ait un moyen dans MySQL d'utiliser un tableau de vos entiers comme table dans votre instruction SQL, votre deuxième option est probablement la meilleure. Il devrait faire un scan complet de la table temporaire et de l'index sur la table principale. Le principal avantage est qu'il n'a qu'à scanner l'index contenant des centaines de milliers d'entiers une seule fois et n'a qu'à envoyer les résultats au client. Votre requête pourrait (mais ne doit pas être) réécrite comme suit:

SELECT * FROM id_key_table_temp 
WHERE id_key NOT IN (select id_key FROM id_key_table);
Leigh Riffel
la source
Je n'approuve pas une table temporaire sur une table régulière car je n'ai aucune connaissance des différences sur la plate-forme MySQL. Dans Oracle, une table temporaire serait probablement la meilleure, mais dans Oracle, vous utiliseriez simplement un tableau comme table et vous y joindriez directement.
Leigh Riffel
3

Au lieu d'une table temporaire et de l'insertion avec insert into id_key_table_temp values (1),(2),(3),...,(500),(501);, vous pouvez créer une sous-requête avec toutes les valeurs que vous essayez de vérifier:

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 dit d'essayer topanswers.xyz
la source
2

Comme indiqué dans mon commentaire, cela est probablement plus adapté au stackoverflow. Cependant, je pense que ces deux solutions ne sont pas les meilleures:

La solution 1 nécessite plusieurs appels sélectionnés, très inefficace

La solution 2 est meilleure, mais je ne suis pas sûr que le coût d'insérer autant de valeurs soit la meilleure solution.

Une solution possible 3 serait de faire une seule requête:

SELECT DISTINCT id_key FROM id_key_table

et obtenir par programme la différence de votre ensemble d'entiers et de ce qui se trouve dans la base de données. Au pire, (car il y a beaucoup d'entiers) Cette route devrait être meilleure que la solution 1. La solution 2 a le potentiel de retourner également beaucoup d'entiers (si la table a un groupe qui ne se trouve pas dans votre ensemble de données), donc elle dépend ™!

Derek Downey
la source
Je ne suis pas fan de cette solution car le jeu de résultats serait très volumineux.
Clinton
@Clinton true, mais il pourrait également être très important dans votre deuxième solution, si vous ne fournissez pas suffisamment d'entiers pour le filtrer.
Derek Downey
2

J'ai à peu près abordé cela dans StackOverflow , mais je voudrais en savoir plus sur l'utilisation de la table temporaire permanente (PermTemp). ( température permanente, n'est-ce pas un oxymore ?)

Dans StackOverflow , j'avais la procédure stockée test.CreateSampleTable et test.GetMissingIntegers créer un exemple de table, puis créer une table temporaire dynamique à remplir avant de faire le grand JOIN pour trouver des différences.

Cette fois, créons la table d'exemple avec la table de table permanente.

Voici 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 ;

Après avoir exécuté ceci, voici les tableaux et leur contenu:

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)

Voici les déclencheurs de la table 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 ;

Maintenant, permet d'importer un nouveau lot d'enregistrements, la table test.weekly_batch, certaines clés utilisées auparavant, d'autres clés flambant neuves:

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

Prenons test.weekly_batch et fusionnons-le en toute sécurité dans test.id_key_table_keys et formons la table 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 ;

Voici le résultat:

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

À partir de ce moment, utilisez simplement la table new_keys_to_load comme liste des nouvelles clés fessées à importer. Étant donné que new_keys_to_load est plus petit que la table PermTemp, vous devez toujours utiliser new_keys_to_load sur le côté gauche de LEFT JOIN.

RolandoMySQLDBA
la source
Je lui ai répondu ce sur le SO déjà
RolandoMySQLDBA