Come "inserire se non esiste" in MySQL?


838

Ho iniziato a cercare su google e ho trovato questo articolo che parla delle tabelle mutex.

Ho una tabella con ~ 14 milioni di dischi. Se voglio aggiungere più dati nello stesso formato, c'è un modo per garantire che il record che voglio inserire non esista già senza usare una coppia di query (cioè, una query da controllare e una da inserire è il set di risultati è vuoto)?

Un uniquevincolo su un campo garantisce il insertfallimento se è già lì?

Sembra che con un semplice vincolo, quando invio l'inserto tramite php, lo script grida.



Consultare stackoverflow.com/questions/44550788/… per la discussione sulla mancata masterizzazione dei valori auto_inc.
Rick James,

@RickJames - questo è un q interessante ... ma non sono sicuro che sia direttamente correlato a questo q :)
warren,

1
È stato menzionato in un commento e quell'altra domanda ha affermato che questa domanda era un "duplicato esatto". Quindi, ho pensato che fosse una buona idea collegare le domande insieme a beneficio degli altri.
Rick James,

1
Oh, non penso mai di guardare la barra laterale.
Rick James,

Risposte:


808

uso INSERT IGNORE INTO table

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

c'è anche la INSERT … ON DUPLICATE KEY UPDATEsintassi, puoi trovare spiegazioni su dev.mysql.com


Pubblica da bogdan.org.ua secondo la webcache di Google :

18 ottobre 2007

Per iniziare: a partire dall'ultimo MySQL, la sintassi presentata nel titolo non è possibile. Ma ci sono molti modi molto semplici per realizzare ciò che ci si aspetta usando le funzionalità esistenti.

Esistono 3 possibili soluzioni: utilizzare INSERT IGNORE, REPLACE o INSERT ... AL DUPLICATE KEY UPDATE.

Immagina di avere un tavolo:

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;

Ora immagina di avere una pipeline automatica che importa metadati di trascrizioni da Ensembl e che per vari motivi la pipeline potrebbe essere rotta in qualsiasi fase dell'esecuzione. Pertanto, dobbiamo garantire due cose:

  1. ripetute esecuzioni della pipeline non distruggeranno il nostro database

  2. esecuzioni ripetute non moriranno a causa di errori di "chiave primaria duplicata".

Metodo 1: usare REPLACE

È molto semplice:

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

Se il record esiste, verrà sovrascritto; se non esiste ancora, verrà creato. Tuttavia, l'utilizzo di questo metodo non è efficace nel nostro caso: non è necessario sovrascrivere i record esistenti, va bene solo saltarli.

Metodo 2: usare INSERT IGNORE Anche molto semplice:

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

Qui, se "ensembl_transcript_id" è già presente nel database, verrà silenziosamente ignorato (ignorato). (Per essere più precisi, ecco una citazione dal manuale di riferimento di MySQL: "Se si utilizza la parola chiave IGNORE, gli errori che si verificano durante l'esecuzione dell'istruzione INSERT vengono trattati come avvisi. Ad esempio, senza IGNORE, una riga che duplica un indice UNIQUE esistente o il valore PRIMARY KEY nella tabella provoca un errore di chiave duplicata e l'istruzione viene interrotta. ”. Se il record non esiste ancora, verrà creato.

Questo secondo metodo presenta diversi potenziali punti deboli, incluso il mancato aborto della query nel caso in cui si verifichino altri problemi (consultare il manuale). Quindi dovrebbe essere usato se precedentemente testato senza la parola chiave IGNORE.

Metodo 3: utilizzo di INSERT… ON DUPLICATE KEY UPDATE:

La terza opzione è quella di utilizzare la INSERT … ON DUPLICATE KEY UPDATE sintassi e nella parte UPDATE semplicemente non fare alcuna operazione insignificante (vuota), come il calcolo di 0 + 0 (Geoffray suggerisce di fare l'assegnazione id = id per il motore di ottimizzazione di MySQL per ignorare questa operazione). Il vantaggio di questo metodo è che ignora solo eventi chiave duplicati e si interrompe ancora su altri errori.

Come ultimo avviso: questo post è stato ispirato da Xaprb. Consiglierei anche di consultare l'altro suo post sulla scrittura di query SQL flessibili.


3
e posso combinarlo con "ritardato" per velocizzare lo script?
Warren,

3
sì, l'inserimento ritardato potrebbe velocizzare le cose per te. provalo
knittl


10
INSERT … ON DUPLICATE KEY UPDATEè migliore poiché non elimina la riga, preservando auto_incrementcolonne e altri dati.
redolent l'

15
Solo per informare tutti. L'uso del INSERT … ON DUPLICATE KEY UPDATEmetodo incrementa qualsiasi colonna AUTO_INCREMENT con inserimento non riuscito. Probabilmente perché non è proprio fallito, ma AGGIORNATO.
not2qubit,

216

Soluzione:

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) 

Spiegazione:

La query più intima

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

usato come WHERE NOT EXISTS-condition rileva se esiste già una riga con i dati da inserire. Dopo aver trovato una riga di questo tipo, la query potrebbe interrompersi, quindi la LIMIT 1(micro-ottimizzazione potrebbe essere omessa).

La query intermedia

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

rappresenta i valori da inserire. DUALfa riferimento a una riga speciale, una tabella di colonne presente per impostazione predefinita in tutti i database Oracle (vedere https://en.wikipedia.org/wiki/DUAL_table ). Su una versione di MySQL Server 5.7.26 ho ottenuto una query valida quando ho omesso FROM DUAL, ma le versioni precedenti (come 5.5.60) sembrano richiedere le FROMinformazioni. Utilizzando WHERE NOT EXISTSla query intermedia viene restituito un set di risultati vuoto se la query più interna ha trovato dati corrispondenti.

La query esterna

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

inserisce i dati, se presenti, vengono restituiti dalla query intermedia.


4
puoi darci qualche informazione in più su come usarlo?
Alex V,

36
Questa variante è adatta se non esiste una chiave univoca sulla tabella ( INSERT IGNOREe INSERT ON DUPLICATE KEYrichiede vincoli di chiave univoci)
rabudde

2
Se si utilizza "da doppio" sulla riga 2 anziché "da tabella", non è necessaria la clausola "limite 1".
Ricco

6
Cosa succede se stuff for value1e stuff for value2sono identici? Ciò porterebbe aDuplicate column name
Robin il

1
Preferisco di gran lunga SELECT 1piuttosto che SELECT *nelle sottoquery. Molto più probabile che questo possa essere soddisfatto da un indice.
Arth,

58

in caso di aggiornamento duplicato della chiave , oppure inserire ignore possono essere soluzioni valide con MySQL.


Esempio di aggiornamento dell'aggiornamento della chiave duplicato basato su 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;

Esempio di inserimento ignore basato su 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] ... ]

O:

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] ... ]

O:

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

24

Qualsiasi semplice vincolo dovrebbe fare il lavoro, se un'eccezione è accettabile. Esempi:

  • chiave primaria se non surrogata
  • vincolo univoco su una colonna
  • vincolo univoco a più colonne

Mi dispiace è questo sembra ingannevolmente semplice. So che sembra male di fronte al link che condividi con noi. ;-(

Ma io non ho mai dato questa risposta, perché sembra soddisfare il tuo bisogno. (In caso contrario, potrebbe innescare l'aggiornamento dei requisiti, che sarebbe anche "una buona cosa" (TM)).

Modificato : se un inserimento rompe il vincolo univoco del database, viene generata un'eccezione a livello di database, trasmessa dal driver. Certamente fermerà la tua sceneggiatura, con un fallimento. Deve essere possibile in PHP affrontare quel caso ...


1
ho aggiunto un chiarimento alla domanda: la tua risposta è ancora valida?
Warren,

2
Credo di si. Un vincolo univoco provocherà il fallimento di inserimenti errati. Nota: devi affrontare questo errore nel tuo codice, ma questo è abbastanza standard.
KLE,

1
per ora seguirò la soluzione che ho accettato - ma esaminerò ulteriormente la gestione degli errori INSERT ecc. man mano che l'app cresce
warren

3
INSERT IGNOREfondamentalmente modifica tutti gli errori in avvisi in modo che lo script non venga interrotto. È quindi possibile visualizzare eventuali avvisi con il comando SHOW WARNINGS. E un'altra nota importante : i vincoli UNIQUE non funzionano con valori NULL, ad es. row1 (1, NULL) e row2 (1, NULL) verranno entrambi inseriti (a meno che non venga rotto un altro vincolo come una chiave primaria). Sfortunato.
Simon East,

18

Ecco una funzione PHP che inserirà una riga solo se tutti i valori delle colonne specificati non esistono già nella tabella.

  • Se una delle colonne differisce, verrà aggiunta la riga.

  • Se la tabella è vuota, verrà aggiunta la riga.

  • Se esiste una riga in cui tutte le colonne specificate hanno i valori specificati, la riga non verrà aggiunta.

    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;
    }

Esempio di utilizzo:

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

5
Abbastanza costoso se hai un carico enorme di inserzioni.
Эџad Дьdulяңмaи

vero, ma efficace se è necessario aggiungere controlli specifici
Charles Forest,

1
Attenzione: l' mysql_* estensione è obsoleta a partire da PHP 5.5.0 ed è stata rimossa a partire da PHP 7.0.0. Invece, dovrebbe essere utilizzata l'estensione mysqli o PDO_MySQL . Vedi anche la panoramica API MySQL per ulteriore aiuto nella scelta di un'API MySQL.
Dharman,

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

Se il record esiste, verrà sovrascritto; se non esiste ancora, verrà creato.


10
REPLACEpuò eliminare la riga e quindi inserire invece di aggiornare. L'effetto collaterale è che i vincoli possono eliminare altri oggetti e vengono attivati ​​i trigger di eliminazione.
xmedeko,

1
Dal manuale di MySQL: "SOSTITUISCI ha senso solo se una tabella ha un PRIMARY KEY o un indice UNIQUE. Altrimenti, diventa equivalente a INSERT, perché non esiste un indice da utilizzare per determinare se una nuova riga ne duplica un'altra."
BurninLeo,

16

Prova quanto segue:

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
Prova Queste risposte sono di scarso valore su StackOverflow perché fanno molto poco per educare l'OP e migliaia di futuri ricercatori. Modifica questa risposta per includere come funziona la soluzione e perché è una buona idea.
Mickmackusa,

1
Soluzione perfetta nel caso in cui i campi da abbinare non siano chiavi ..!
Leo

6

Esistono diverse risposte che spiegano come risolverlo se si dispone di un UNIQUEindice che è possibile verificare con ON DUPLICATE KEYo INSERT IGNORE. Questo non è sempre il caso, e come UNIQUEha un vincolo di lunghezza (1000 byte) potresti non essere in grado di cambiarlo. Ad esempio, ho dovuto lavorare con i metadati in WordPress ( wp_postmeta).

Alla fine l'ho risolto con due domande:

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 = ?);

La query 1 è una UPDATEquery normale senza effetti quando il set di dati in questione non è presente. La query 2 è una INSERTche dipende da a NOT EXISTS, ovvero INSERTviene eseguita solo quando il set di dati non esiste.


2

Una cosa degna di nota è che INSERT IGNORE incrementerà comunque la chiave primaria indipendentemente dal fatto che l'istruzione abbia avuto successo o meno, come farebbe un normale INSERT.

Ciò causerà lacune nelle chiavi primarie che potrebbero rendere mentalmente instabile un programmatore. Oppure, se la tua applicazione è mal progettata e dipende da chiavi primarie incrementali perfette, potrebbe diventare un mal di testa.

Guarda innodb_autoinc_lock_mode = 0(impostazione del server e viene fornito con un leggero hit delle prestazioni) o usa prima un SELEZIONA per assicurarti che la tua query non fallisca (che include anche un hit delle prestazioni e un codice extra).


Perché "lacune nelle tue chiavi primarie" - anche potenzialmente - "renderebbero un programmatore mentalmente instabile"? Le lacune si verificano continuamente nelle chiavi primarie, ad esempio ogni volta che si elimina un record.
Warren,

A partire da una SELECTsconfitta, lo scopo è semplicemente quello di distribuire un grosso lotto di se INSERTnon voler preoccuparsi dei duplicati.
Warren,

2

Aggiorna o inserisci senza chiave primaria nota

Se disponi già di una chiave univoca o primaria, l'altra risposta con una INSERT INTO ... ON DUPLICATE KEY UPDATE ...o REPLACE INTO ...dovrebbe funzionare correttamente (nota che sostituisci in cancella se esiste e quindi inserisce, quindi non aggiorna parzialmente i valori esistenti).

Ma se hai i valori per some_column_ide some_type, la cui combinazione è nota per essere unica. E vuoi aggiornare some_valuese esiste o inserire se non esiste. E vuoi farlo in una sola query (per evitare di usare una transazione). Questa potrebbe essere una soluzione:

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 = ?

Fondamentalmente, la query viene eseguita in questo modo (meno complicata di quanto possa sembrare):

  • Seleziona una riga esistente tramite la WHEREcorrispondenza della clausola.
  • Unione che risulta con una potenziale nuova riga (tabella s), in cui i valori di colonna vengono esplicitamente indicati (s.id è NULL, quindi genererà un nuovo identificatore di incremento automatico).
  • Se viene trovata una riga esistente, la potenziale nuova riga dalla tabella sviene scartata (a causa di LIMIT 1 sulla tabella t) e si attiverà sempre un ON DUPLICATE KEYquale sarà UPDATEla some_valuecolonna.
  • Se non viene trovata una riga esistente, viene inserita la potenziale nuova riga (come indicato dalla tabella s).

Nota: ogni tabella in un database relazionale dovrebbe avere almeno una idcolonna di incremento automatico primario . Se non lo hai, aggiungilo anche quando non ti serve a prima vista. È sicuramente necessario per questo "trucco".


Diversi altri risponditori hanno offerto un INSERT INTO ... SELECT FROMformato. Perché anche tu?
Warren,

2
@warren O non hai letto la mia risposta, non la capisci o non l'ho spiegata correttamente. In ogni caso, vorrei sottolineare quanto segue: questa non è solo una INSERT INTO... SELECT FROM...soluzione regolare . Per favore, riferiscimi un link a una risposta uguale, se riesci a trovarla eliminerò questa risposta, altrimenti voterai la mia risposta (affare?). Assicurati di verificare che la risposta che intendi collegare utilizza solo 1 query (per aggiornamento + inserimento), nessuna transazione ed è in grado di scegliere come target qualsiasi combinazione di colonne nota per essere unica (quindi separatamente le colonne non deve essere unico).
Yeti l'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.