Da un set di valori, come posso trovare i valori non memorizzati nella colonna di una tabella?


12

Ho una tabella che potenzialmente memorizzerà centinaia di migliaia di numeri interi

desc id_key_table;

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

Da un programma, ho una grande serie di numeri interi. Vorrei vedere quali di questi numeri interi NON si trovano nella colonna id_key sopra.

Finora ho escogitato i seguenti approcci:

1) Scorrere tutti i numeri interi ed eseguire un:

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

Quando count è 0, l'id_key manca dalla tabella.

Sembra un modo orribile e orribile per farlo.


2) Creare una tabella temporanea, inserire ciascuno dei valori nella tabella temporanea ed eseguire un JOIN sulle due tabelle.

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;

Questo sembra l'approccio migliore, tuttavia, sono sicuro che esiste un approccio molto migliore a cui non ho ancora pensato. Preferirei non dover creare una tabella temporanea e utilizzare una query per determinare quali numeri interi mancano.

Esiste una query corretta per questo tipo di ricerca?

(MySQL)


2
Mi piace come hai fatto la tua domanda (Benvenuto in DBA), tuttavia è probabilmente molto più appropriato su StackOverflow in quanto si tratta di interagire con un programma di qualche tipo (non dba in sé)
Derek Downey

Grazie per il benvenuto, ho pensato che un posto come questo potesse avere più guru di StackOverflow. Non mi dispiace chiedere nuovamente lì.
Clinton,

2
Come suggerito, ho ripubblicato su StackOverflow: stackoverflow.com/questions/5967822/…
Clinton,

Una situazione simile è stata trattata per il server sql in questa domanda: tecnica per l'invio di molti dati nel proc memorizzato . Dovresti trovare lì che il problema è simile in altri ambienti db. Comunque, vado per la soluzione no. 2 - invia un elenco di ID, analizza, metti in tabella, unisciti alla tua tabella principale. Che se non puoi usare altre soluzioni, ma qui devi scavare :-).
Marian,

Risposte:


7

La tua seconda soluzione che utilizza LEFT JOIN è di gran lunga l'approccio migliore. Non userei una tabella temporanea, userei una tabella regolare e la popolerei con nuovi valori ogni volta che vuoi eseguire la query.


5

Sembra che il "grande insieme di numeri interi" sia ancora considerevolmente più piccolo della tabella con "centinaia di migliaia di numeri interi". Con questa supposizione e, a meno che non esista un modo in MySQL per utilizzare un array di numeri interi come tabella nell'istruzione SQL, la seconda opzione è probabilmente la migliore. Dovrebbe eseguire una scansione completa della tabella temporanea e dell'indice sulla tabella principale. Il vantaggio principale è che deve solo scansionare l'indice contenente centinaia di migliaia di numeri interi una volta e deve solo inviare i risultati al client. La tua query potrebbe (ma non è necessario) riscritta come segue:

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

Non sto approvando una tabella temporanea su una tabella normale in quanto non sono a conoscenza delle differenze sulla piattaforma MySQL. In Oracle una tabella temporanea sarebbe probabilmente la migliore, ma in Oracle useresti semplicemente un array come tabella e ti uniresti direttamente ad esso.
Leigh Riffel,

3

Invece di una tabella temporanea e inserimento con insert into id_key_table_temp values (1),(2),(3),...,(500),(501);, è possibile costruire una sottoquery con tutti i valori che si sta tentando di verificare:

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

Come notato nel mio commento, questo è probabilmente più adatto allo stackoverflow. Tuttavia, penso che entrambe queste soluzioni non siano le migliori:

La soluzione 1 richiede chiamate multiple selezionate, molto inefficienti

La soluzione 2 è migliore, ma non sono sicuro che il costo dell'inserimento di molti valori sia la soluzione migliore.

Una possibile soluzione 3 sarebbe quella di fare una query:

SELECT DISTINCT id_key FROM id_key_table

e a livello di codice ottenere la differenza dal set di numeri interi e da ciò che è presente nel DB. Nel peggiore dei casi (poiché sono molti numeri interi), questa route dovrebbe essere migliore della Soluzione 1. La soluzione 2 ha il potenziale per restituire ANCHE molti numeri interi (se la tabella ha un gruppo che non è nel tuo set di dati), quindi dipende ™!


Non sono un fan di questa soluzione poiché il set di risultati sarebbe molto ampio.
Clinton,

@Clinton vero, ma potrebbe essere molto grande anche nella tua seconda soluzione, se non fornisci abbastanza numeri interi per filtrarlo.
Derek Downey,

2

Ho praticamente affrontato questo problema in StackOverflow , ma vorrei approfondire l'uso della tabella permanente temp (PermTemp). ( temperatura permanente, non è un ossimoro ?)

In StackOverflow , ho avuto la procedura memorizzata test.CreateSampleTable e test.GetMissingIntegers crea una tabella di esempio e quindi crea una tabella di temperatura dinamica per popolare prima di fare il grande JOIN per trovare le differenze.

Questa volta, creiamo la tabella di esempio insieme alla tabella della tabella permanente.

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

Dopo aver eseguito questo, ecco le tabelle e il loro contenuto:

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)

Ecco i trigger per la tabella 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 ;

Ora, consente di importare un nuovo batch di record, tabella test.weekly_batch, alcune chiavi utilizzate in precedenza, altre chiavi di marca sculacciate nuove:

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

Prendiamo test.weekly_batch e uniamolo in modo sicuro in test.id_key_table_keys e formiamo la tabella 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 ;

Ecco il risultato:

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

Da questo punto, basta usare la tabella new_keys_to_load come elenco di chiavi di marca che sculacciano nuove chiavi da importare. Poiché new_keys_to_load è più piccolo della tabella PermTemp, dovresti sempre usare new_keys_to_load sul lato sinistro di LEFT JOIN.


Ho risposto questo su SO già
RolandoMySQLDBA
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.