Em um conjunto de valores, como encontro os valores não armazenados na coluna de uma tabela?


12

Eu tenho uma tabela que potencialmente armazena centenas de milhares de números inteiros

desc id_key_table;

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

Em um programa, eu tenho um grande conjunto de números inteiros. Gostaria de ver quais desses números inteiros NÃO estão na coluna id_key acima.

Até agora, desenvolvi as seguintes abordagens:

1) Itere através de cada número inteiro e execute:

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

Quando count é 0, a id_key está ausente na tabela.

Parece uma maneira horrível e horrível de fazer isso.


2) Crie uma tabela temporária, insira cada um dos valores na tabela temporária e execute JOIN nas duas tabelas.

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;

Essa parece ser a melhor abordagem, no entanto, tenho certeza de que existe uma abordagem muito melhor na qual ainda não pensei. Eu preferiria não precisar criar uma tabela temporária e usar uma consulta para determinar quais números inteiros estão ausentes.

Existe uma consulta adequada para esse tipo de pesquisa?

(MySQL)


2
Eu gosto de como você pediu a sua pergunta (Bem-vindo ao DBA), no entanto, é provavelmente muito mais apropriado em stackoverflow como ele lida com a interação com um programa de algum tipo (não dba per se)
— Derek Downey

Obrigado pela recepção, pensei que um lugar como esse poderia ter mais gurus do que stackoverflow. Eu não me importo de perguntar lá, no entanto.
— Clinton

2
Como sugerido, reenviei para StackOverflow: stackoverflow.com/questions/5967822/…
— Clinton

Situação semelhante foi tratada para o servidor sql nesta questão: Técnica para enviar muitos dados para o processo armazenado . Você deve descobrir que o problema é semelhante em outros ambientes de banco de dados. Enfim, eu vou para a solução não. 2 - envie a lista de ids, analise, coloque na tabela, entre na sua tabela principal. Que se você não pode usar outras soluções, mas aqui você tem que cavar :-).
— Marian

Respostas:


7

Sua segunda solução usando o LEFT JOIN é de longe a melhor abordagem. Eu não usaria uma tabela temporária, usaria uma tabela regular e a preencheria com novos valores sempre que você quisesse executar a consulta.


5

Parece que o "grande conjunto de números inteiros" ainda é consideravelmente menor que a tabela com "centenas de milhares de números inteiros". Com essa suposição e a menos que haja uma maneira no MySQL de usar uma matriz de seus números inteiros como uma tabela em sua instrução SQL, sua segunda opção é provavelmente a melhor. Ele deve fazer uma varredura completa da tabela temporária e do índice na tabela principal. O principal benefício é que ele só precisa varrer o índice que contém centenas de milhares de números inteiros uma vez e apenas enviar os resultados ao cliente. Sua consulta pode (mas não precisa ser) reescrita da seguinte maneira:

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

Não estou endossando uma tabela temporária sobre uma tabela regular, pois não tenho conhecimento das diferenças na plataforma MySQL. No Oracle, uma tabela temporária provavelmente seria a melhor, mas, no Oracle, você usaria uma matriz como tabela e se juntaria diretamente a ela.
— Leigh Riffel

3

Em vez de uma tabela temporária e inserção com insert into id_key_table_temp values (1),(2),(3),...,(500),(501);, você pode construir uma subconsulta com todos os valores que você está tentando verificar:

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

Como observado no meu comentário, isso provavelmente é mais adequado ao stackoverflow. No entanto, acho que essas duas soluções não são as melhores:

A solução 1 requer várias chamadas selecionadas, muito ineficientes

A solução 2 é melhor, mas não tenho certeza de que o custo de inserir tantos valores seja a melhor solução.

Uma possível solução 3 seria fazer uma consulta:

SELECT DISTINCT id_key FROM id_key_table

e programaticamente obtenha a diferença do seu conjunto inteiro e do conteúdo do banco de dados. Na pior das hipóteses, (como são muitos números inteiros). Essa rota deve ser melhor que a Solução 1. A solução 2 também pode retornar muitos números inteiros (se a tabela tiver muitos que não estão no seu conjunto de dados), portanto depende ™!


Não sou fã desta solução, pois o conjunto de resultados seria muito grande.
— Clinton

@Clinton é verdade, mas também pode ser muito grande na sua segunda solução, se você não fornecer números inteiros suficientes para filtrá-la.
— Derek Downey

2

Abordei isso bastante no StackOverflow , mas gostaria de elaborar mais sobre o uso da tabela permanente temp (PermTemp). ( temperatura permanente, isso não é um oxímoro ?)

No StackOverflow , eu tinha o procedimento armazenado test.CreateSampleTable e test.GetMissingIntegers fazem uma tabela de amostra e, em seguida, criam uma tabela temporária dinâmica para preencher antes de fazer o grande JOIN para encontrar diferenças.

Desta vez, vamos criar a tabela de amostra junto com a tabela de tabela permanente.

Aqui está 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 ;

Depois de executar isso, aqui estão as tabelas e seu conteúdo:

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)

Aqui estão os gatilhos da tabela 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 ;

Agora, vamos importar um novo lote de registros, tabela test.weekly_batch, algumas chaves usadas antes, outras chaves com a marca nova:

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

Vamos pegar test.weekly_batch e mesclar com segurança em test.id_key_table_keys e formar a tabela 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 ;

Aqui está o resultado:

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

A partir desse ponto, basta usar a tabela new_keys_to_load como a lista de novas chaves de marca que serão importadas. Como new_keys_to_load é menor que a tabela PermTemp, você sempre deve usar new_keys_to_load no lado esquerdo do LEFT JOIN.


Eu respondi a essa no SO já
— RolandoMySQLDBA
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.