Dari sekumpulan nilai, bagaimana cara menemukan nilai yang tidak disimpan dalam kolom tabel?

12

Saya punya tabel yang berpotensi menyimpan ratusan ribu bilangan bulat

desc id_key_table;

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

Dari suatu program, saya memiliki satu set bilangan bulat besar. Saya ingin melihat bilangan bulat mana yang TIDAK dalam kolom id_key di atas.

Sejauh ini saya telah datang dengan pendekatan berikut:

1) Ulangi setiap integer dan lakukan:

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

Saat hitung 0, id_key tidak ada di tabel.

Ini sepertinya cara yang mengerikan dan mengerikan untuk melakukannya.


2) Buat tabel sementara, masukkan masing-masing nilai ke dalam tabel sementara, dan lakukan JOIN pada dua tabel.

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;

Ini sepertinya pendekatan terbaik, namun, saya yakin ada pendekatan yang jauh lebih baik yang belum saya pikirkan. Saya lebih suka tidak harus membuat tabel sementara dan menggunakan satu permintaan untuk menentukan bilangan bulat mana yang hilang.

Apakah ada permintaan yang tepat untuk jenis pencarian ini?

(MySQL)

Clinton
sumber
2
Saya suka bagaimana Anda mengajukan pertanyaan Anda (Selamat datang di DBA), namun, mungkin itu jauh lebih tepat untuk stackoverflow karena berkaitan dengan berinteraksi dengan suatu program (bukan dba per se)
Derek Downey
Terima kasih atas sambutannya, saya pikir tempat seperti ini mungkin memiliki lebih banyak guru daripada stackoverflow. Saya tidak keberatan bertanya ulang di sana.
Clinton
2
Seperti yang disarankan, saya mem-posting ulang ke StackOverflow: stackoverflow.com/questions/5967822/…
Clinton
Situasi serupa diperlakukan untuk sql server dalam pertanyaan ini: Teknik untuk mengirim banyak data ke proc yang disimpan . Anda harus menemukan di sana bahwa masalahnya serupa di lingkungan db lainnya. Bagaimanapun, saya mencari solusi no. 2 - kirim daftar id, parse, letakkan di meja, gabung ke meja utama Anda. Itu jika Anda tidak dapat menggunakan solusi lain, tetapi di sini Anda harus menggali :-).
Marian

Jawaban:

7

Solusi kedua Anda menggunakan LEFT JOIN sejauh ini merupakan pendekatan terbaik. Saya tidak akan menggunakan tabel sementara, saya akan menggunakan tabel biasa dan mengisinya dengan nilai-nilai baru setiap kali Anda ingin menjalankan kueri.

Michael Riley - AKA Gunny
sumber
5

Kedengarannya seperti "set besar bilangan bulat" masih jauh lebih kecil dari tabel dengan "ratusan ribu bilangan bulat". Dengan anggapan itu dan kecuali ada cara di MySQL untuk menggunakan array bilangan bulat Anda sebagai tabel dalam pernyataan SQL Anda, opsi kedua Anda mungkin yang terbaik. Ini harus melakukan pemindaian penuh dari tabel temp dan indeks pada tabel utama. Manfaat utama adalah hanya memindai indeks yang berisi ratusan ribu bilangan bulat satu kali dan hanya mengirim hasil kepada klien. Permintaan Anda dapat (tetapi tidak perlu) ditulis ulang sebagai berikut:

SELECT * FROM id_key_table_temp 
WHERE id_key NOT IN (select id_key FROM id_key_table);
Leigh Riffel
sumber
Saya tidak mendukung tabel sementara di atas tabel biasa karena saya tidak memiliki pengetahuan tentang perbedaan pada platform MySQL. Di Oracle, tabel sementara mungkin akan menjadi yang terbaik, tetapi kemudian di Oracle Anda hanya akan menggunakan array sebagai tabel dan bergabung langsung ke sana.
Leigh Riffel
3

Alih-alih tabel sementara dan menyisipkan dengan insert into id_key_table_temp values (1),(2),(3),...,(500),(501);, Anda bisa membuat subquery dengan semua nilai yang Anda coba periksa:

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 mengatakan coba topanswers.xyz
sumber
2

Seperti disebutkan dalam komentar saya, ini mungkin lebih cocok untuk stackoverflow. Namun, saya pikir kedua solusi itu bukan yang terbaik:

Solusi 1 membutuhkan beberapa panggilan pilih, sangat tidak efisien

Solusi 2 lebih baik, tetapi saya tidak yakin biaya memasukkan banyak nilai adalah solusi terbaik.

Kemungkinan solusi 3 adalah membuat satu permintaan:

SELECT DISTINCT id_key FROM id_key_table

dan secara terprogram mendapatkan perbedaan dari set integer Anda dan apa yang ada di DB. Paling buruk, (karena banyak bilangan bulat) Rute ini harus lebih baik daripada Solusi 1. Solusi 2 berpotensi juga mengembalikan banyak bilangan bulat (jika tabel memiliki banyak bilangan yang tidak ada dalam dataset Anda), sehingga tergantung ™!

Derek Downey
sumber
Saya bukan penggemar solusi ini karena resultset akan sangat besar.
Clinton
@Clinton benar, tetapi bisa juga sangat besar dalam solusi kedua Anda, jika Anda tidak menyediakan cukup bilangan bulat untuk menyaringnya.
Derek Downey
2

Saya cukup banyak membahas ini di StackOverflow , tapi saya ingin menguraikan lebih lanjut tentang penggunaan tabel temp permanen (PermTemp). ( temp permanen, bukankah itu oxymoron ?)

Di StackOverflow , saya memiliki tes prosedur tersimpan. BuatSampleTable dan test.GetMissingIntegers membuat tabel sampel dan kemudian membuat tabel temp dinamis untuk diisi sebelum melakukan JOIN besar untuk menemukan perbedaan.

Kali ini, mari kita buat tabel sampel bersama dengan tabel tabel permanen.

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

Setelah menjalankan ini, berikut adalah tabel dan isinya:

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)

Berikut adalah Pemicu untuk tabel 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 ;

Sekarang, mari kita mengimpor kumpulan catatan baru, table test.weekly_batch, beberapa kunci yang digunakan sebelumnya, kunci-kunci lain yang dipukul dengan merek baru:

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

Mari kita ikuti test.weekly_batch dan dengan aman menggabungkannya ke test.id_key_table_keys dan membentuk 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 ;

Inilah hasilnya:

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

Dari titik ini, cukup gunakan tabel new_keys_to_load sebagai daftar merek memukul kunci baru untuk diimpor. Karena new_keys_to_load lebih kecil dari tabel PermTemp, Anda harus selalu menggunakan new_keys_to_load di sebelah kiri LEFT JOIN.

RolandoMySQLDBA
sumber
Saya menjawab ini di SO sudah
RolandoMySQLDBA