Como 'inserir se não existir' no MySQL?


838

Comecei pesquisando no Google e encontrei este artigo que fala sobre tabelas mutex.

Eu tenho uma mesa com ~ 14 milhões de registros. Se eu quiser adicionar mais dados no mesmo formato, existe uma maneira de garantir que o registro que desejo inserir ainda não exista sem o uso de um par de consultas (por exemplo, uma consulta para verificar e outra para inserir é o conjunto de resultados) esvaziar)?

Uma uniquerestrição em um campo garante que a insertfalha falhe se já estiver lá?

Parece que, com apenas uma restrição, quando emito a inserção via php, o script resmunga.



Consulte stackoverflow.com/questions/44550788/… para discussão sobre a não gravação dos valores de auto_inc.
Rick James

@RickJames - que é um q interessante .. mas não tenho certeza que está diretamente relacionado a este q :)
Warren

1
Foi mencionado em um comentário e essa outra pergunta reivindicou que esta pergunta era uma "duplicata exata". Então, achei que era uma boa ideia vincular as perguntas para o benefício de outras pessoas.
Rick James

1
Oh, eu nunca penso em olhar para a barra lateral.
Rick James

Respostas:


807

usar INSERT IGNORE INTO table

consulte http://bogdan.org.ua/2007/10/18/mysql-insert-if-not-exists-syntax.html

também há INSERT … ON DUPLICATE KEY UPDATEsintaxe, você pode encontrar explicações em dev.mysql.com


Publique em bogdan.org.ua de acordo com o webcache do Google :

18 de outubro de 2007

Para começar: a partir do MySQL mais recente, a sintaxe apresentada no título não é possível. Mas existem várias maneiras fáceis de realizar o que é esperado usando a funcionalidade existente.

Existem 3 soluções possíveis: usando INSERIR IGNORAR, SUBSTITUIR ou INSERIR ... NA DUPLICAÇÃO DE CHAVE DE ATUALIZAÇÃO.

Imagine que temos uma mesa:

CREATE TABLE `transcripts` (
`ensembl_transcript_id` varchar(20) NOT NULL,
`transcript_chrom_start` int(10) unsigned NOT NULL,
`transcript_chrom_end` int(10) unsigned NOT NULL,
PRIMARY KEY (`ensembl_transcript_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Agora imagine que temos um pipeline automático importando metadados de transcrições do Ensembl e que, devido a várias razões, o pipeline pode ser quebrado em qualquer etapa da execução. Portanto, precisamos garantir duas coisas:

  1. execuções repetidas do pipeline não destruirão nosso banco de dados

  2. execuções repetidas não morrem devido a erros de 'chave primária duplicada'.

Método 1: usando REPLACE

É muito simples:

REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

Se o registro existir, ele será substituído; se ainda não existir, será criado. No entanto, o uso desse método não é eficiente para o nosso caso: não precisamos sobrescrever registros existentes; é bom apenas ignorá-los.

Método 2: usando INSERIR IGNORE Também é muito simples:

INSERT IGNORE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

Aqui, se o 'ensembl_transcript_id' já estiver presente no banco de dados, ele será ignorado silenciosamente. (Para ser mais preciso, aqui está uma citação do manual de referência do MySQL: “Se você usar a palavra-chave IGNORE, os erros que ocorrem durante a execução da instrução INSERT serão tratados como avisos. Por exemplo, sem IGNORE, uma linha que duplica um índice UNIQUE existente ou PRIMARY KEY na tabela causa um erro de chave duplicada e a instrução é interrompida. ”.) Se o registro ainda não existir, ele será criado.

Este segundo método possui várias fraquezas em potencial, incluindo o não aborto da consulta, caso ocorra outro problema (consulte o manual). Portanto, ele deve ser usado se testado anteriormente sem a palavra-chave IGNORE.

Método 3: usando INSERIR… NA ATUALIZAÇÃO DUPLICATIVA DE CHAVE:

Terceira opção é usar INSERT … ON DUPLICATE KEY UPDATE sintaxe, e na parte UPDATE simplesmente não faça nenhuma operação sem sentido (vazia), como calcular 0 + 0 (o Geoffray sugere fazer a atribuição id = id para o mecanismo de otimização do MySQL ignorar esta operação). A vantagem desse método é que ele ignora apenas eventos-chave duplicados e ainda aborta com outros erros.

Como aviso final: este post foi inspirado no Xaprb. Também aconselho consultar seu outro post sobre como escrever consultas SQL flexíveis.


3
e posso combinar isso com "atrasado" para acelerar o script?
Warren

3
Sim, a inserção atrasada pode acelerar as coisas para você. experimentá-lo
knittl


10
INSERT … ON DUPLICATE KEY UPDATEé melhor, pois não exclui a linha, preservando quaisquer auto_incrementcolunas e outros dados.
redolent

15
Só para informar a todos. O INSERT … ON DUPLICATE KEY UPDATEmétodo using incrementa qualquer coluna AUTO_INCREMENT com falha na inserção. Provavelmente porque realmente não falhou, mas atualizou.
not2qubit

216

Solução:

INSERT INTO `table` (`value1`, `value2`) 
SELECT 'stuff for value1', 'stuff for value2' FROM DUAL 
WHERE NOT EXISTS (SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1) 

Explicação:

A consulta mais interna

SELECT * FROM `table` 
      WHERE `value1`='stuff for value1' AND `value2`='stuff for value2' LIMIT 1

usado como a WHERE NOT EXISTScondição detecta se já existe uma linha com os dados a serem inseridos. Depois que uma linha desse tipo é encontrada, a consulta pode parar e, portanto, a LIMIT 1(micro otimização pode ser omitida).

A consulta intermediária

SELECT 'stuff for value1', 'stuff for value2' FROM DUAL

representa os valores a serem inseridos. DUALrefere-se a uma linha especial, uma tabela de coluna presente por padrão em todos os bancos de dados Oracle (consulte https://en.wikipedia.org/wiki/DUAL_table ). Em um servidor MySQL versão 5.7.26, recebi uma consulta válida ao omitir FROM DUAL, mas versões mais antigas (como 5.5.60) parecem exigir as FROMinformações. UsandoWHERE NOT EXISTS da consulta intermediária retorna um conjunto de resultados vazio se a consulta mais interna encontrar dados correspondentes.

A consulta externa

INSERT INTO `table` (`value1`, `value2`) 

insere os dados, se algum for retornado pela consulta intermediária.


4
você pode dar mais informações sobre como usar isso?
21812 Alex V

36
Esta variante é adequada, se nenhuma tecla única na tabela existe ( INSERT IGNOREe INSERT ON DUPLICATE KEYexigem restrições de chave únicas)
rabudde

2
Se você usar "from dual" na linha 2 em vez de "from table", não precisará da cláusula "limit 1".
Rich

6
E se stuff for value1e stuff for value2for idêntico? Isso daria umDuplicate column name
Robin

1
Eu também prefiro muito mais SELECT 1do que SELECT *nas subconsultas. Muito mais provável que isso possa ser satisfeito por um índice.
Arth

58

na atualização de chave duplicada ou inserir ignorar pode ser soluções viáveis ​​com o MySQL.


Exemplo de atualização de chave duplicada com base em mysql.com

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

UPDATE table SET c=c+1 WHERE a=1;

Exemplo de inserção ignorar com base em mysql.com

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    {VALUES | VALUE} ({expr | DEFAULT},...),(...),...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

Ou:

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    SET col_name={expr | DEFAULT}, ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

Ou:

INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    SELECT ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]

24

Qualquer restrição simples deve fazer o trabalho, se uma exceção for aceitável. Exemplos :

  • chave primária se não for substituta
  • restrição exclusiva em uma coluna
  • restrição exclusiva de várias colunas

Desculpe, isso parece enganosamente simples. Sei que parece ruim em relação ao link que você compartilha conosco. ;-(

Mas, mesmo assim, eu dou essa resposta, porque parece preencher sua necessidade. (Caso contrário, isso poderá desencadear a atualização de seus requisitos, o que seria "uma coisa boa" (TM) também).

Editado : se uma inserção quebrar a restrição exclusiva do banco de dados, uma exceção será lançada no nível do banco de dados, retransmitida pelo driver. Certamente interromperá o seu script, com uma falha. No PHP deve ser possível resolver esse caso ...


1
adicionei um esclarecimento à pergunta - sua resposta ainda se aplica?
Warren

2
Eu acredito que sim. Uma restrição exclusiva causará a falha de inserções incorretas. Nota: você precisa lidar com essa falha no seu código, mas isso é bastante padrão.
KLE

1
por agora eu vou ficar com a solução que eu aceito -, mas vai mais olhar para lidar com falhas ENVIE etc como o aplicativo cresce
Warren

3
INSERT IGNOREbasicamente altera todos os erros em avisos para que seu script não seja interrompido. Você pode, então, visualizar todos os avisos com o comando SHOW WARNINGS. E outra nota importante : restrições UNIQUE não funcionam com valores NULL, ie. linha1 (1, NULL) e linha2 (1, NULL) serão inseridas (a menos que outra restrição, como uma chave primária, seja quebrada). Infeliz.
Simon East

18

Aqui está uma função PHP que inserirá uma linha apenas se todos os valores de colunas especificados ainda não existirem na tabela.

  • Se uma das colunas diferir, a linha será adicionada.

  • Se a tabela estiver vazia, a linha será adicionada.

  • Se existir uma linha em que todas as colunas especificadas tenham os valores especificados, a linha não será adicionada.

    function insert_unique($table, $vars)
    {
      if (count($vars)) {
        $table = mysql_real_escape_string($table);
        $vars = array_map('mysql_real_escape_string', $vars);
    
        $req = "INSERT INTO `$table` (`". join('`, `', array_keys($vars)) ."`) ";
        $req .= "SELECT '". join("', '", $vars) ."' FROM DUAL ";
        $req .= "WHERE NOT EXISTS (SELECT 1 FROM `$table` WHERE ";
    
        foreach ($vars AS $col => $val)
          $req .= "`$col`='$val' AND ";
    
        $req = substr($req, 0, -5) . ") LIMIT 1";
    
        $res = mysql_query($req) OR die();
        return mysql_insert_id();
      }
    
      return False;
    }

Exemplo de uso:

<?php
insert_unique('mytable', array(
  'mycolumn1' => 'myvalue1',
  'mycolumn2' => 'myvalue2',
  'mycolumn3' => 'myvalue3'
  )
);
?>

5
Muito caro se você tiver uma enorme carga de inserções.
Эџad Дьdulяңмaи

verdade, mas eficiente se você precisa adicionar exames específicos
Charles Floresta

1
Aviso: a mysql_* extensão está obsoleta no PHP 5.5.0 e foi removida no PHP 7.0.0. Em vez disso, a extensão mysqli ou PDO_MySQL deve ser usada. Consulte também a Visão geral da API do MySQL para obter mais ajuda ao escolher uma API do MySQL.
Dharman 20/03

17
REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

Se o registro existir, ele será substituído; se ainda não existir, será criado.


10
REPLACEpode excluir a linha e depois inserir em vez de atualizar. O efeito colateral é que as restrições podem excluir outros objetos e os gatilhos de exclusão são acionados.
xmedeko

1
No manual do MySQL: "REPLACE só faz sentido se uma tabela tiver um índice PRIMARY KEY ou UNIQUE. Caso contrário, ela se tornará equivalente a INSERT, porque não há índice a ser usado para determinar se uma nova linha duplicará outra."
BurninLeo 31/12/19

16

Tente o seguinte:

IF (SELECT COUNT(*) FROM beta WHERE name = 'John' > 0)
  UPDATE alfa SET c1=(SELECT id FROM beta WHERE name = 'John')
ELSE
BEGIN
  INSERT INTO beta (name) VALUES ('John')
  INSERT INTO alfa (c1) VALUES (LAST_INSERT_ID())
END

5
Tente Essas respostas são de baixo valor no StackOverflow, porque fazem muito pouco para educar o OP e milhares de futuros pesquisadores. Edite esta resposta para incluir como a solução funciona e por que é uma boa ideia.
Mickmackusa # 1/18

1
Solução perfeita no caso de os campos a serem combinados não serem essenciais ..!
Leo

6

Existem várias respostas que abordam como resolver isso se você tiver um UNIQUEíndice que possa ser verificado com ON DUPLICATE KEYou INSERT IGNORE. Esse nem sempre é o caso e, como UNIQUEpossui uma restrição de comprimento (1000 bytes), talvez você não consiga alterar isso. Por exemplo, eu tive que trabalhar com metadados no WordPress (wp_postmeta ).

Finalmente resolvi-o com duas consultas:

UPDATE wp_postmeta SET meta_value = ? WHERE meta_key = ? AND post_id = ?;
INSERT INTO wp_postmeta (post_id, meta_key, meta_value) SELECT DISTINCT ?, ?, ? FROM wp_postmeta WHERE NOT EXISTS(SELECT * FROM wp_postmeta WHERE meta_key = ? AND post_id = ?);

A consulta 1 é uma UPDATEconsulta regular sem efeito quando o conjunto de dados em questão não está lá. A consulta 2 é uma INSERTque depende de a NOT EXISTS, ou seja, INSERTé executada apenas quando o conjunto de dados não existe.


2

Algo digno de nota é que o INSERT IGNORE ainda aumentará a chave primária, independentemente de a declaração ter sido bem-sucedida ou não, como um INSERT normal faria.

Isso causará lacunas nas chaves primárias que podem tornar um programador mentalmente instável. Ou se seu aplicativo for mal projetado e depender de chaves primárias incrementais perfeitas, poderá se tornar uma dor de cabeça.

Examine innodb_autoinc_lock_mode = 0(configuração do servidor e vem com um leve impacto no desempenho) ou use primeiro um SELECT para garantir que sua consulta não falhe (que também vem com um impacto no desempenho e código extra).


Por que "lacunas em suas chaves primárias" - mesmo potencialmente - "tornam um programador mentalmente instável"? As lacunas ocorrem o tempo todo nas chaves primárias - toda vez que você exclui um registro, por exemplo.
Warren

Começando com a SELECTderrota, todo o objetivo de apenas entregar um lote grande de se INSERTnão querer se preocupar com duplicatas.
Warren

2

Atualizar ou inserir sem chave primária conhecida

Se você já possui uma chave exclusiva ou primária, as outras respostas respondem com INSERT INTO ... ON DUPLICATE KEY UPDATE ...ouREPLACE INTO ... devem funcionar bem (observe que substituir em exclusões, se existir, e depois inserir - portanto, não atualiza parcialmente os valores existentes).

Mas se você tiver os valores para some_column_ide some_type, cuja combinação é conhecida por ser única. E você deseja atualizar, some_valuese existir, ou inserir, se não existir. E você deseja fazer isso em apenas uma consulta (para evitar o uso de uma transação). Esta pode ser uma solução:

INSERT INTO my_table (id, some_column_id, some_type, some_value)
SELECT t.id, t.some_column_id, t.some_type, t.some_value
FROM (
    SELECT id, some_column_id, some_type, some_value
    FROM my_table
    WHERE some_column_id = ? AND some_type = ?
    UNION ALL
    SELECT s.id, s.some_column_id, s.some_type, s.some_value
    FROM (SELECT NULL AS id, ? AS some_column_id, ? AS some_type, ? AS some_value) AS s
) AS t
LIMIT 1
ON DUPLICATE KEY UPDATE
some_value = ?

Basicamente, a consulta é executada desta maneira (menos complicada do que parece):

  • Selecione uma linha existente através da WHEREcorrespondência da cláusula.
  • União que resulta com uma nova linha (tabela s) em potencial , onde os valores da coluna são explicitamente fornecidos (s.id é NULL, portanto, ele gera um novo identificador de incremento automático).
  • Se uma linha existente for encontrada, a nova linha potencial da tabela sserá descartada (devido a LIMIT 1 na tabela t) e sempre acionará uma ON DUPLICATE KEYqueUPDATE a some_valuecoluna.
  • Se uma linha existente não for encontrada, a nova linha potencial será inserida (conforme fornecido pela tabela s ).

Nota: Toda tabela em um banco de dados relacional deve ter pelo menos uma idcoluna primária de incremento automático . Se você não tiver, adicione-o, mesmo quando não precisar dele à primeira vista. Definitivamente, é necessário para esse "truque".


Vários outros respondentes ofereceram um INSERT INTO ... SELECT FROMformato. Por que você também?
Warren

2
@warren Ou você não leu minha resposta, não a entendeu ou eu não a expliquei corretamente. De qualquer forma, deixe-me enfatizar o seguinte: essa não é apenas uma INSERT INTO... SELECT FROM...solução regular . Consulte-me um link para uma resposta que seja a mesma; se você puder encontrá-la, eu excluirei esta resposta, caso contrário, você receberá minha resposta (oferta?). Verifique se a resposta que você vinculará usa apenas 1 consulta (para atualização + inserção), nenhuma transação e é capaz de segmentar qualquer combinação de colunas que sejam conhecidas por serem únicas (portanto, separadamente, as colunas não precisa ser único).
Yeti
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.