Wie finde ich aus einer Reihe von Werten die Werte, die nicht in der Spalte einer Tabelle gespeichert sind?

12

Ich habe eine Tabelle, in der möglicherweise Hunderttausende von Ganzzahlen gespeichert werden

desc id_key_table;

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

Aus einem Programm habe ich eine große Menge von ganzen Zahlen. Ich würde gerne sehen, welche dieser Ganzzahlen NICHT in der obigen Spalte id_key stehen.

Bisher habe ich mir folgende Ansätze ausgedacht:

1) Durchlaufen Sie jede Ganzzahl und führen Sie Folgendes aus:

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

Wenn count 0 ist, fehlt der id_key in der Tabelle.

Dies scheint ein schrecklicher, schrecklicher Weg zu sein.


2) Erstellen Sie eine temporäre Tabelle, fügen Sie jeden der Werte in die temporäre Tabelle ein und führen Sie eine VERBINDUNG für die beiden Tabellen durch.

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;

Dies scheint der beste Ansatz zu sein, aber ich bin sicher, dass es einen weitaus besseren Ansatz gibt, an den ich noch nicht gedacht habe. Ich würde es vorziehen, keine temporäre Tabelle erstellen und eine Abfrage verwenden zu müssen, um festzustellen, welche Ganzzahlen fehlen.

Gibt es eine richtige Abfrage für diese Art der Suche?

(MySQL)

Clinton
quelle
2
Mir gefällt, wie Sie Ihre Frage gestellt haben (Willkommen bei DBA), aber es ist wahrscheinlich weitaus besser für Stackoverflow geeignet, da es sich um die Interaktion mit einem Programm handelt (nicht mit dba per se)
Derek Downey,
Vielen Dank für die Begrüßung, ich dachte, ein Ort wie dieser könnte mehr Gurus als Stackoverflow haben. Es macht mir nichts aus, dort noch einmal zu fragen.
Clinton
2
Wie vorgeschlagen, habe ich erneut zu StackOverflow
Clinton
Eine ähnliche Situation wurde für SQL Server in dieser Frage behandelt: Technik zum Senden vieler Daten in gespeicherte Prozesse . Dort sollten Sie feststellen, dass das Problem in anderen Datenbankumgebungen ähnlich ist. Wie auch immer, ich gehe für Lösung Nr. 2 - Liste der IDs senden, analysieren, in die Tabelle einfügen, mit Ihrer Haupttabelle verbinden. Das, wenn Sie keine anderen Lösungen verwenden können, aber hier müssen Sie graben :-).
Marian

Antworten:

7

Ihre zweite Lösung mit LEFT JOIN ist bei weitem der beste Ansatz. Ich würde keine temporäre Tabelle verwenden, sondern eine reguläre Tabelle verwenden und sie jedes Mal mit neuen Werten füllen, wenn Sie die Abfrage ausführen möchten.

Michael Riley - AKA Gunny
quelle
5

Es hört sich so an, als ob die "große Menge von ganzen Zahlen" immer noch erheblich kleiner ist als die Tabelle mit "Hunderttausenden von ganzen Zahlen". Mit dieser Annahme und sofern es in MySQL keine Möglichkeit gibt, ein Array Ihrer Ganzzahlen als Tabelle in Ihrer SQL-Anweisung zu verwenden, ist Ihre zweite Option wahrscheinlich die beste. Es sollte ein vollständiger Scan der temporären Tabelle und des Index für die Haupttabelle durchgeführt werden. Der Hauptvorteil besteht darin, dass nur der Index mit Hunderttausenden von Ganzzahlen einmal gescannt und dem Client nur die Ergebnisse gesendet werden müssen. Ihre Anfrage könnte (muss aber nicht) wie folgt umgeschrieben werden:

SELECT * FROM id_key_table_temp 
WHERE id_key NOT IN (select id_key FROM id_key_table);
Leigh Riffel
quelle
Ich unterstütze keine temporäre Tabelle gegenüber einer regulären Tabelle, da ich die Unterschiede auf der MySQL-Plattform nicht kenne. In Oracle wäre eine temporäre Tabelle wahrscheinlich am besten, aber in Oracle würden Sie einfach ein Array als Tabelle verwenden und direkt damit verbinden.
Leigh Riffel
3

Anstelle einer temporären Tabelle und des Einfügens mit insert into id_key_table_temp values (1),(2),(3),...,(500),(501);können Sie eine Unterabfrage mit allen Werten erstellen, die Sie überprüfen möchten :

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 sagt, versuchen Sie es mit topanswers.xyz
quelle
2

Wie in meinem Kommentar erwähnt, ist dies wahrscheinlich besser für den Stapelüberlauf geeignet. Ich denke jedoch, dass diese beiden Lösungen nicht die besten sind:

Lösung 1 erfordert mehrere ausgewählte Anrufe, die sehr ineffizient sind

Lösung 2 ist besser, aber ich bin mir nicht sicher, ob die Kosten für das Einfügen so vieler Werte die beste Lösung sind.

Eine mögliche Lösung 3 wäre eine Abfrage:

SELECT DISTINCT id_key FROM id_key_table

und programmgesteuert den Unterschied zwischen Ihrer Ganzzahl und dem Inhalt der Datenbank ermitteln. Im schlimmsten Fall (da es sich um viele Ganzzahlen handelt) sollte diese Route besser sein als Lösung 1. Lösung 2 kann AUCH viele Ganzzahlen zurückgeben (wenn die Tabelle eine Reihe enthält, die nicht in Ihrem Datensatz enthalten sind) hängt davon ab ™!

Derek Downey
quelle
Ich bin kein Fan dieser Lösung, da die Ergebnismenge sehr groß wäre.
Clinton
@Clinton stimmt, aber es könnte auch in Ihrer zweiten Lösung sehr groß sein, wenn Sie nicht genügend Ganzzahlen angeben, um es herauszufiltern.
Derek Downey
2

Ich habe dies in StackOverflow ziemlich genau angesprochen , möchte aber näher auf die Verwendung der permanenten temporären Tabelle (PermTemp) eingehen. ( permanente Temperatur, ist das nicht ein Oxymoron? ?)

In StackOverflow hatte ich die gespeicherte Prozedur test.CreateSampleTable und test.GetMissingIntegers erstellen eine Beispieltabelle und erstellen dann eine dynamische temporäre Tabelle zum Auffüllen, bevor Sie den großen JOIN ausführen, um Unterschiede zu finden.

Dieses Mal erstellen wir die Beispieltabelle zusammen mit der permanenten Tabellentabelle.

Hier ist 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 ;

Nachdem Sie dies ausgeführt haben, sind hier die Tabellen und deren Inhalt:

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)

Hier sind die Trigger für die PermTemp-Tabelle

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 ;

Jetzt können Sie einen neuen Datensatzstapel importieren, Tabelle test.weekly_batch, einige zuvor verwendete Schlüssel, andere brandneue Schlüssel:

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

Nehmen wir test.weekly_batch und führen es sicher in test.id_key_table_keys ein und bilden die Tabelle 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 ;

Hier ist das Ergebnis:

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

Verwenden Sie ab diesem Zeitpunkt einfach die Tabelle new_keys_to_load als Liste der brandneuen Schlüssel, die importiert werden sollen. Da new_keys_to_load kleiner als die PermTemp-Tabelle ist, sollten Sie immer new_keys_to_load auf der linken Seite von LEFT JOIN verwenden.

RolandoMySQLDBA
quelle
Ich antwortete , dies auf SO bereits
RolandoMySQLDBA