Як з набору значень я можу знайти значення, які не зберігаються у стовпці таблиці?


12

У мене є таблиця, яка потенційно може зберігати сотні тисяч цілих чисел

desc id_key_table;

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

З програми у мене є великий набір цілих чисел. Я хотів би побачити, яке з цих цілих чисел НЕ у наведеному вище стовпці id_key.

Поки я придумав такі підходи:

1) Ітерація через ціле число і виконайте:

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 per se)
Дерек Дауні

Дякую за прийом, я подумав, що в такому місці може бути більше гуру, ніж stackoverflow. Я не проти перепросити туди, хоча.
Клінтон

2
Як було запропоновано, я повторно відправив на StackOverflow: stackoverflow.com/questions/5967822 / ...
Клінтон

Аналогічна ситуація була розглянута для сервера sql у цьому запитанні: Техніка передачі великої кількості даних у збережену процедуру . Ви повинні там виявити, що проблема схожа в інших середовищах db. У всякому разі, я йду на рішення ні. 2 - надішліть список ідентифікаторів, проаналізуйте, помістіть у таблицю, приєднайтесь до вашої основної таблиці. Це якщо ви не можете використовувати інші рішення, але тут вам доведеться копати :-).
Мар'ян

Відповіді:


7

Ваше друге рішення з використанням лівого приєднання - це на сьогоднішній день найкращий підхід. Я б не використовував тимчасову таблицю, я використовував би звичайну таблицю і заповнював її новими значеннями будь-коли, коли захочеться запустити запит.


5

Здається, що "великий набір цілих чисел" все ще значно менший, ніж таблиця з "сотнями тисяч цілих чисел". З цим припущенням, і якщо в MySQL не існує способу використовувати масив цілих чисел як таблицю у вашому операторі SQL, ваш другий варіант, мабуть, найкращий. Слід виконати повне сканування темп-таблиці та індексу на головній таблиці. Основна перевага полягає в тому, що він повинен одночасно сканувати індекс, що містить сотні тисяч цілих чисел, і лише повинен надсилати клієнту результати. Ваш запит можна (але не потрібно) переписувати таким чином:

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

Я не схвалюю тимчасову таблицю над звичайною таблицею, оскільки не знаю відмінностей на платформі MySQL. В Oracle тимчасова таблиця, мабуть, найкраща, але тоді в Oracle ви просто використовуєте масив як таблицю та приєднуєтесь безпосередньо до неї.
Лі Ріффер

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

Як зазначається в моєму коментарі, це, ймовірно, більше підходить для стаціонарного потоку. Однак я думаю, що обидва ці рішення не найкращі:

Рішення 1 вимагає декількох викликів, дуже неефективних

Рішення 2 краще, але я не впевнений, що вартість вставки, що багато значень, є найкращим рішенням.

Можливим рішенням 3 буде зробити один запит:

SELECT DISTINCT id_key FROM id_key_table

і програмно отримати різницю від вашого цілого набору та того, що знаходиться в БД. У гіршому випадку (оскільки це багато цілих чисел) Цей маршрут повинен бути кращим, ніж рішення 1. Рішення 2 має потенціал ТАКОЖ повернути безліч цілих чисел (якщо таблиця має купу, яких немає у вашому наборі даних), тож залежить ™!


Я не прихильник цього рішення, оскільки набір результатів був би дуже великий.
Клінтон

@Clinton правда, але це може бути дуже великим і у вашому другому рішенні, якщо ви не надасте достатньо цілих чисел для його фільтрації.
Дерек Дауні

2

Я в значній мірі вирішив це в StackOverflow , але хотів би детальніше розповісти про використання таблиці постійних темп (PermTemp). ( постійна температура, чи не це оксиморон ?)

У StackOverflow у мене був тест збереженої процедури. СтворітьSampleTable та test.GetMissingIntegers роблять зразкову таблицю, а потім створюють динамічну таблицю темп для заповнення, перш ніж робити велику ПРИЄДНАЙТЕ, щоб знайти відмінності.

Цього разу давайте створимо зразок таблиці разом із таблицею постійної таблиці.

Ось тест.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 з лівого боку лівого приєднання.


Я відповів на це на SO вже
RolandoMySQLDBA
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.