Jak na podstawie zestawu wartości znaleźć wartości, które nie są przechowywane w kolumnie tabeli?

12

Mam tabelę, która potencjalnie pomieści setki tysięcy liczb całkowitych

desc id_key_table;

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

Z programu mam duży zestaw liczb całkowitych. Chciałbym zobaczyć, które z tych liczb całkowitych NIE znajdują się w powyższej kolumnie id_key.

Do tej pory wymyśliłem następujące podejścia:

1) Iteruj przez każdą liczbę całkowitą i wykonaj:

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

Gdy liczba wynosi 0, brak identyfikatora w tabeli.

To wydaje się okropnym, okropnym sposobem na zrobienie tego.


2) Utwórz tabelę tymczasową, wstaw każdą z wartości do tabeli tymczasowej i wykonaj JOIN na dwóch tabelach.

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;

Wydaje się to najlepszym podejściem, ale jestem pewien, że istnieje o wiele lepsze podejście, o którym jeszcze nie myślałem. Wolałbym nie tworzyć tabeli tymczasowej i używać jednego zapytania do ustalenia, które liczby całkowite brakuje.

Czy istnieje odpowiednie zapytanie dla tego typu wyszukiwania?

(MySQL)

Clinton
źródło
2
Podoba mi się, jak zadałeś swoje pytanie (Witamy w DBA), ale prawdopodobnie jest bardziej odpowiednie w przypadku przepełnienia stosu, ponieważ dotyczy interakcji z jakimś programem (nie dba per se)
Derek Downey
Dziękuję za powitanie. Pomyślałem, że takie miejsce może mieć więcej guru niż stackoverflow. Nie mam nic przeciwko, żeby ponownie o to zapytać.
Clinton
2
Zgodnie z sugestią, opublikowałem ponownie w StackOverflow: stackoverflow.com/questions/5967822/...
Clinton
Podobna sytuacja została potraktowana dla serwera SQL w tym pytaniu: Technika wysyłania dużej ilości danych do przechowywanego proc . Powinieneś znaleźć tam, że problem jest podobny w innych środowiskach db. W każdym razie wybieram rozwiązanie nie. 2 - wyślij listę identyfikatorów, przeanalizuj, umieść w tabeli, dołącz do głównego stołu. Że jeśli nie możesz użyć innych rozwiązań, ale tutaj musisz kopać :-).
Marian

Odpowiedzi:

7

Twoje drugie rozwiązanie z LEFT JOIN jest zdecydowanie najlepszym podejściem. Nie użyłbym tabeli tymczasowej, użyłbym zwykłej tabeli i zapełniłbym ją nowymi wartościami za każdym razem, gdy chcesz uruchomić zapytanie.

Michael Riley - AKA Gunny
źródło
5

Wygląda na to, że „duży zestaw liczb całkowitych” jest wciąż znacznie mniejszy niż tabela z „setkami tysięcy liczb całkowitych”. Przy takim założeniu i chyba że w MySQL istnieje sposób na użycie tablicy liczb całkowitych jako tabeli w instrukcji SQL, druga opcja jest prawdopodobnie najlepsza. Powinien wykonać pełne skanowanie tabeli tymczasowej i indeksu na głównej tabeli. Główną zaletą jest to, że musi tylko zeskanować indeks zawierający setki tysięcy liczb całkowitych za jednym razem i musi tylko wysłać klientowi wyniki. Twoje zapytanie może (ale nie musi) zostać przepisane w następujący sposób:

SELECT * FROM id_key_table_temp 
WHERE id_key NOT IN (select id_key FROM id_key_table);
Leigh Riffel
źródło
Nie polecam tabeli tymczasowej w stosunku do zwykłej tabeli, ponieważ nie mam wiedzy na temat różnic na platformie MySQL. W Oracle tabela tymczasowa byłaby prawdopodobnie najlepsza, ale w Oracle wystarczy użyć tablicy jako tabeli i dołączyć bezpośrednio do niej.
Leigh Riffel
3

Zamiast tabeli tymczasowej i wstawiania za pomocą insert into id_key_table_temp values (1),(2),(3),...,(500),(501);, możesz utworzyć podzapytanie ze wszystkimi wartościami, które próbujesz sprawdzić:

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 mówi, że spróbuj topanswers.xyz
źródło
2

Jak zauważono w moim komentarzu, jest to prawdopodobnie bardziej odpowiednie dla przepływu stosu. Myślę jednak, że oba te rozwiązania nie są najlepsze:

Rozwiązanie 1 wymaga wielu połączeń Select, bardzo nieefektywnych

Rozwiązanie 2 jest lepsze, ale nie jestem pewien, czy koszt wprowadzenia tak wielu wartości jest najlepszym rozwiązaniem.

Możliwym rozwiązaniem 3 byłoby utworzenie jednego zapytania:

SELECT DISTINCT id_key FROM id_key_table

i programowo uzyskaj różnicę z zestawu liczb całkowitych i zawartości DB. W najgorszym przypadku (ponieważ jest dużo liczb całkowitych) Ta trasa powinna być lepsza niż Rozwiązanie 1. Rozwiązanie 2 może również zwrócić wiele liczb całkowitych (jeśli tabela zawiera grupę, której nie ma w zestawie danych), więc zależy ™!

Derek Downey
źródło
Nie jestem fanem tego rozwiązania, ponieważ zestaw wyników byłby bardzo duży.
Clinton
@Clinton prawda, ale może być również bardzo duża w drugim rozwiązaniu, jeśli nie podasz wystarczającej liczby całkowitej, aby ją odfiltrować.
Derek Downey
2

Prawie to rozwiązałem w StackOverflow , ale chciałbym rozwinąć więcej informacji na temat korzystania z tabeli permanent temp (PermTemp). ( stała temperatura, czy to nie oksymoron ?)

W StackOverflow miałem procedurę składowaną test.CreateSampleTable i test.GetMissingIntegers utwórz tabelę próbek, a następnie utwórz dynamiczną tabelę temp do wypełnienia przed wykonaniem dużego JOIN w celu znalezienia różnic.

Tym razem stwórzmy przykładową tabelę wraz ze stałą tabelą.

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

Po uruchomieniu tego oto tabele i ich zawartość:

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)

Oto wyzwalacze dla tabeli 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 ;

Teraz zaimportujmy nową partię rekordów, test tabeli. Tydzień_batch, niektóre klucze używane wcześniej, inne klucze klapsy nowe:

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

Weźmy test.weekly_batch i bezpiecznie połączmy go w test.id_key_table_keys i utwórz tabelę 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 ;

Oto wynik:

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

Od tego momentu po prostu użyj tabeli new_keys_to_load jako listy marek z nowymi kluczami do zaimportowania. Ponieważ new_keys_to_load jest mniejszy niż tabela PermTemp, zawsze powinieneś używać new_keys_to_load po lewej stronie LEFT JOIN.

RolandoMySQLDBA
źródło
Odpowiedziałem już na SO
RolandoMySQLDBA