จากชุดของค่าฉันจะค้นหาค่าที่ไม่ได้เก็บไว้ในคอลัมน์ของตารางได้อย่างไร


12

ฉันมีตารางที่จะเก็บจำนวนเต็มนับแสน

desc id_key_table;

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

จากโปรแกรมฉันมีจำนวนเต็มชุดใหญ่ ฉันต้องการดูว่าเลขจำนวนเต็มใดที่ไม่อยู่ในคอลัมน์ id_key ด้านบน

จนถึงตอนนี้ฉันได้วิธีต่อไปนี้แล้ว:

1) วนซ้ำแต่ละจำนวนเต็มและทำ a:

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

เมื่อการนับเป็น 0 id_key จะหายไปจากตาราง

ดูเหมือนว่าจะเป็นวิธีที่น่ากลัวและน่ากลัว


2) สร้างตารางชั่วคราวแทรกค่าแต่ละค่าลงในตารางชั่วคราวและดำเนินการเข้าร่วมในสองตาราง

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;

ดูเหมือนว่าวิธีการที่ดีที่สุด แต่ฉันแน่ใจว่ามีวิธีที่ดีกว่าที่ฉันยังไม่ได้คิด ฉันต้องการที่จะไม่ต้องสร้างตารางชั่วคราวและใช้หนึ่งแบบสอบถามเพื่อตรวจสอบว่าจำนวนเต็มใดหายไป

มีคำค้นหาที่เหมาะสมสำหรับการค้นหาประเภทนี้หรือไม่?

(MySQL)


2
ฉันชอบวิธีที่คุณถามคำถามของคุณ (ยินดีต้อนรับสู่ DBA) แต่มันอาจจะเหมาะสมกว่าสำหรับ stackoverflow เนื่องจากเกี่ยวข้องกับการโต้ตอบกับโปรแกรมบางประเภท (ไม่ใช่ dba ต่อ se)
Derek Downey

ขอบคุณสำหรับการต้อนรับฉันคิดว่าสถานที่แบบนี้อาจมีปรมาจารย์มากกว่ากองซ้อน ฉันไม่รังเกียจที่จะถามว่า
Clinton

2
ตามที่แนะนำฉันโพสต์ใหม่ไปที่ StackOverflow: stackoverflow.com/questions/5967822/…
Clinton

สถานการณ์ที่คล้ายกันได้รับการรักษาสำหรับเซิร์ฟเวอร์ SQL ในคำถามนี้: เทคนิคสำหรับการส่งข้อมูลจำนวนมากเข้าไปใน proc คุณควรพบว่ามีปัญหาคล้ายกันในสภาพแวดล้อม db อื่น ๆ ต่อไปฉันจะไปเพื่อแก้ปัญหาไม่ 2 - ส่งรายการรหัสแยกวิเคราะห์วางในตารางเข้าร่วมกับตารางหลักของคุณ ถ้าคุณไม่สามารถใช้โซลูชันอื่น ๆ ได้ แต่ที่นี่คุณต้องขุด :-)
แมเรียน

คำตอบ:


7

ทางออกที่สองของคุณที่ใช้ LEFT JOIN คือแนวทางที่ดีที่สุด ฉันจะไม่ใช้ตารางชั่วคราวฉันจะใช้ตารางปกติและเติมข้อมูลด้วยค่าใหม่ทุกครั้งที่คุณต้องการเรียกใช้แบบสอบถาม


5

ดูเหมือนว่า "จำนวนเต็มชุดใหญ่" ยังคงมีขนาดเล็กกว่าโต๊ะอย่างมากด้วย "จำนวนเต็มนับแสน" ด้วยข้อสันนิษฐานนั้นและหากไม่มีวิธีใน MySQL ที่จะใช้อาร์เรย์จำนวนเต็มของคุณเป็นตารางในคำสั่ง SQL ของคุณตัวเลือกที่สองของคุณน่าจะดีที่สุด ควรทำการสแกนแบบเต็มของตารางอุณหภูมิและดัชนีบนตารางหลัก ประโยชน์หลักคือการสแกนดัชนีที่มีจำนวนเต็มหลายร้อยหลายพันครั้งเดียวเท่านั้นและจะต้องส่งผลลัพธ์ให้ลูกค้า ข้อความค้นหาของคุณสามารถเขียนใหม่ได้ (แต่ไม่จำเป็น) ดังนี้:

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

ฉันไม่รับรองตารางชั่วคราวเหนือตารางปกติเนื่องจากฉันไม่มีความรู้เกี่ยวกับความแตกต่างในแพลตฟอร์ม MySQL ใน Oracle ตารางชั่วคราวอาจจะดีที่สุด แต่ใน Oracle คุณเพียงแค่ใช้อาร์เรย์เป็นตารางและเข้าร่วมโดยตรง
Leigh Riffel

3

แทนที่จะเป็นตารางชั่วคราวและแทรกด้วยinsert into id_key_table_temp values (1),(2),(3),...,(500),(501);คุณสามารถสร้างแบบสอบถามย่อยด้วยค่าทั้งหมดที่คุณพยายามตรวจสอบ:

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

ตามที่ระบุไว้ในความคิดเห็นของฉันนี้น่าจะเหมาะกับ stackoverflow อย่างไรก็ตามฉันคิดว่าโซลูชันทั้งสองนั้นไม่ใช่วิธีที่ดีที่สุด:

โซลูชันที่ 1 ต้องใช้การโทรแบบเลือกหลายรายการที่ไม่มีประสิทธิภาพมาก

โซลูชันที่ 2 ดีกว่า แต่ฉันไม่แน่ใจว่าค่าใช้จ่ายในการแทรกค่าจำนวนมากเป็นทางออกที่ดีที่สุด

วิธีแก้ปัญหาที่เป็นไปได้ 3 คือสร้างข้อความค้นหาหนึ่งคำ:

SELECT DISTINCT id_key FROM id_key_table

และโดยทางโปรแกรมจะได้รับความแตกต่างจากชุดจำนวนเต็มของคุณและสิ่งที่อยู่ในฐานข้อมูล ที่แย่ที่สุด (เนื่องจากเป็นจำนวนเต็มจำนวนมาก) เส้นทางนี้ควรจะดีกว่าโซลูชันที่ 1 โซลูชันที่ 2 มีศักยภาพที่จะส่งคืนจำนวนเต็มจำนวนมาก (หากตารางมีพวงที่ไม่ได้อยู่ในชุดข้อมูลของคุณ) ดังนั้นมันจึง ขึ้นอยู่กับ™!


ฉันไม่ใช่แฟนของโซลูชันนี้เนื่องจากชุดผลลัพธ์จะมีขนาดใหญ่มาก
Clinton

@ Clinton จริง แต่อาจมีขนาดใหญ่มากในโซลูชันที่สองของคุณเช่นกันหากคุณไม่มีจำนวนเต็มเพียงพอที่จะกรองออก
Derek Downey

2

ฉันพูดถึงเรื่องนี้มากในStackOverflowแต่ฉันต้องการอธิบายเพิ่มเติมเกี่ยวกับการใช้ตาราง temp (PermTemp) แบบถาวร ( อุณหภูมิถาวรนั่นไม่ได้เป็นปฏิปักษ์ )

ในStackOverflowฉันมีการทดสอบโพรซีเดอร์ที่เก็บสร้าง CreateSampleTable และ test.GetMissingIntegers สร้างตารางตัวอย่างแล้วสร้างตารางชั่วคราวแบบไดนามิกเพื่อเติมก่อนทำการ JOIN ขนาดใหญ่เพื่อค้นหาความแตกต่าง

เวลานี้เรามาสร้างตารางตัวอย่างพร้อมกับตารางตารางถาวร

นี่คือการทดสอบ 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 ;

หลังจากเรียกใช้งานนี่คือตารางและเนื้อหา:

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)

นี่คือทริกเกอร์สำหรับตาราง 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 ;

ตอนนี้ให้นำเข้าชุดระเบียนใหม่ตาราง test.weekly_batch บางคีย์ที่ใช้ก่อนหน้านี้คีย์อื่น ๆ ที่ตีใหม่:

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

มาทดสอบ test.weekly_batch และรวมเข้าไว้ใน test.id_key_table_keys อย่างปลอดภัยแล้วสร้างตาราง 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 ;

นี่คือผลลัพธ์:

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

จากจุดนี้เพียงใช้ตาราง new_keys_to_load เป็นรายการของคีย์ใหม่ที่จะนำเข้า เนื่องจาก new_keys_to_load มีขนาดเล็กกว่าตาราง PermTemp คุณควรใช้ new_keys_to_load ทางด้านซ้ายของ LEFT JOIN เสมอ


ฉันตอบเรื่องนี้แล้วดังนั้น
RolandoMySQLDBA
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.