Как найти значения, не сохраненные в столбце таблицы, из набора значений?

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) Создайте временную таблицу, вставьте каждое из значений во временную таблицу и выполните JOIN для двух таблиц.

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;

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

Есть ли правильный запрос для этого типа поиска?

(Баз данных)

Клинтон
источник
2
Мне нравится, как вы задали свой вопрос (Добро пожаловать в DBA), однако, он, вероятно, гораздо более уместен в работе со стековым потоком, так как он имеет дело с взаимодействием с какой-то программой (а не с dba как таковой)
Дерек Дауни
Спасибо за приветствие, я подумал, что в таком месте может быть больше гуру, чем stackoverflow. Я не против переспросить там, хотя.
Клинтон
2
Как и предполагалось, я разместил репозиторий в StackOverflow: stackoverflow.com/questions/5967822/…
Клинтон
Подобная ситуация была рассмотрена для сервера sql в этом вопросе: Техника для отправки большого количества данных в хранимый процесс . Вы должны найти там, что проблема аналогична в других средах БД. Во всяком случае, я иду к решению нет. 2 - отправить список идентификаторов, разобрать, положить в таблицу, присоединиться к основной таблице. Это если вы не можете использовать другие решения, но здесь вы должны копать :-).
Marian

Ответы:

7

Ваше второе решение с использованием LEFT JOIN является наилучшим подходом. Я не буду использовать временную таблицу, я буду использовать обычную таблицу и заполнять ее новыми значениями в любое время, когда вы захотите выполнить запрос.

Майкл Райли - AKA Gunny
источник
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);
Джек говорит, попробуйте topanswers.xyz
источник
2

Как отмечено в моем комментарии, это, вероятно, больше подходит для stackoverflow. Тем не менее, я думаю, что оба эти решения не являются лучшими:

Решение 1 требует многократных вызовов, очень неэффективно

Решение 2 лучше, но я не уверен, что затраты на вставку такого количества значений - лучшее решение.

Возможным решением 3 было бы сделать один запрос:

SELECT DISTINCT id_key FROM id_key_table

и программно получить разницу от вашего набора целых и того, что находится в БД. В худшем случае (так как в нем много целых чисел), этот маршрут должен быть лучше, чем в решении 1. Решение 2 может также ТАКЖЕ возвращать много целых чисел (если в таблице есть набор, которого нет в вашем наборе данных), поэтому зависит ™!

Дерек Дауни
источник
Я не фанат этого решения, так как набор результатов будет очень большим.
Клинтон
@Clinton true, но оно может быть очень большим и в вашем втором решении, если вы не предоставите достаточно целых чисел для его фильтрации.
Дерек Дауни
2

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

В StackOverflow у меня была хранимая процедура test.CreateSampleTable и test.GetMissingIntegers создают примерную таблицу, а затем создают динамическую временную таблицу для заполнения перед выполнением большого JOIN для поиска различий.

На этот раз давайте создадим образец таблицы вместе с таблицей постоянных таблиц.

Вот 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 ;

После запуска вот таблицы и их содержимое:

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
источник
Я ответил на это на SO уже
RolandoMySQLDBA