Wie kann ich in MySQL einfügen, wenn es nicht vorhanden ist?


838

Ich begann mit googeln und fand diesen Artikel, der sich mit Mutex-Tabellen befasst.

Ich habe einen Tisch mit ~ 14 Millionen Datensätzen. Wenn ich weitere Daten im selben Format hinzufügen möchte, gibt es eine Möglichkeit, um sicherzustellen, dass der Datensatz, den ich einfügen möchte, nicht bereits vorhanden ist, ohne zwei Abfragen zu verwenden (dh eine zu überprüfende Abfrage und eine einzufügende Abfrage ist die Ergebnismenge leeren)?

uniqueGarantiert eine Einschränkung für ein Feld, dass der insertWille fehlschlägt, wenn er bereits vorhanden ist?

Es scheint, dass mit nur einer Einschränkung, wenn ich die Einfügung über PHP ausstelle, das Skript krächzt.




@ RickJames - das ist ein interessantes q .. aber nicht sicher, ob es direkt mit diesem q zusammenhängt :)
Warren

1
Es wurde in einem Kommentar erwähnt, und diese andere Frage behauptete, diese Frage sei ein "genaues Duplikat". Daher hielt ich es für eine gute Idee, die Fragen zum Nutzen anderer miteinander zu verknüpfen.
Rick James

1
Oh, ich denke nie daran, auf die Seitenleiste zu schauen.
Rick James

Antworten:


806

verwenden INSERT IGNORE INTO table

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

Es gibt auch INSERT … ON DUPLICATE KEY UPDATESyntax, Erklärungen finden Sie auf dev.mysql.com


Post von bogdan.org.ua gemäß Googles Webcache :

18. Oktober 2007

Zu Beginn: Ab dem neuesten MySQL ist die im Titel angegebene Syntax nicht möglich. Es gibt jedoch mehrere sehr einfache Möglichkeiten, mit den vorhandenen Funktionen das zu erreichen, was erwartet wird.

Es gibt drei mögliche Lösungen: Verwenden von INSERT IGNORE, REPLACE oder INSERT… ON DUPLICATE KEY UPDATE.

Stellen Sie sich vor, wir haben einen Tisch:

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;

Stellen Sie sich nun vor, wir haben eine automatische Pipeline, die Transkript-Metadaten aus Ensembl importiert, und dass die Pipeline aus verschiedenen Gründen bei jedem Ausführungsschritt unterbrochen werden kann. Daher müssen wir zwei Dinge sicherstellen:

  1. Wiederholte Ausführungen der Pipeline zerstören unsere Datenbank nicht

  2. Wiederholte Ausführungen sterben nicht aufgrund von Fehlern beim Duplizieren des Primärschlüssels.

Methode 1: Verwenden von REPLACE

Es ist sehr einfach:

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

Wenn der Datensatz vorhanden ist, wird er überschrieben. Wenn es noch nicht existiert, wird es erstellt. Die Verwendung dieser Methode ist in unserem Fall jedoch nicht effizient: Wir müssen vorhandene Datensätze nicht überschreiben, es ist in Ordnung, sie nur zu überspringen.

Methode 2: INSERT IGNORE verwenden Auch sehr einfach:

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

Wenn die 'ensembl_transcript_id' bereits in der Datenbank vorhanden ist, wird sie hier stillschweigend übersprungen (ignoriert). (Genauer gesagt, hier ein Zitat aus dem MySQL-Referenzhandbuch: „Wenn Sie das Schlüsselwort IGNORE verwenden, werden Fehler, die beim Ausführen der INSERT-Anweisung auftreten, stattdessen als Warnungen behandelt. Ohne IGNORE beispielsweise eine Zeile, die einen vorhandenen UNIQUE-Index dupliziert oder PRIMARY KEY-Wert in der Tabelle verursacht einen Duplikatschlüsselfehler und die Anweisung wird abgebrochen. ”.) Wenn der Datensatz noch nicht vorhanden ist, wird er erstellt.

Diese zweite Methode weist mehrere potenzielle Schwachstellen auf, einschließlich des Nichtabbruchs der Abfrage, falls ein anderes Problem auftritt (siehe Handbuch). Daher sollte es verwendet werden, wenn es zuvor ohne das Schlüsselwort IGNORE getestet wurde.

Methode 3: Verwenden von INSERT… ON DUPLICATE KEY UPDATE:

Die dritte Option ist zu verwenden INSERT … ON DUPLICATE KEY UPDATE Syntax und im UPDATE-Teil nichts zu tun, um eine bedeutungslose (leere) Operation durchzuführen, wie z. B. die Berechnung von 0 + 0 (Geoffray schlägt vor, die Zuweisung id = id für die MySQL-Optimierungs-Engine vorzunehmen, um diese Operation zu ignorieren). Der Vorteil dieser Methode besteht darin, dass nur doppelte Schlüsselereignisse ignoriert werden und andere Fehler weiterhin abgebrochen werden.

Als letzter Hinweis: Dieser Beitrag wurde von Xaprb inspiriert. Ich würde auch empfehlen, seinen anderen Beitrag zum Schreiben flexibler SQL-Abfragen zu konsultieren.


3
und kann ich das mit "verzögert" kombinieren, um das Skript zu beschleunigen?
Warren

3
Ja, verzögertes Einfügen kann die Dinge für Sie beschleunigen. probieren Sie es aus
stricken Sie den


10
INSERT … ON DUPLICATE KEY UPDATEist besser, da die Zeile nicht gelöscht wird und auto_incrementSpalten und andere Daten erhalten bleiben.
duftend

15
Nur um alle zu informieren. Mit der INSERT … ON DUPLICATE KEY UPDATEMethode wird jede AUTO_INCREMENT-Spalte mit fehlgeschlagener Einfügung erhöht. Wahrscheinlich, weil es nicht wirklich gescheitert ist, aber UPDATE'd.
not2qubit

216

Lösung:

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) 

Erläuterung:

Die innerste Abfrage

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

WHERE NOT EXISTSWird als Bedingung verwendet, wird erkannt, ob bereits eine Zeile mit den einzufügenden Daten vorhanden ist. Nachdem eine Zeile dieser Art gefunden wurde, wird die Abfrage möglicherweise gestoppt, daher kann die LIMIT 1(Mikrooptimierung, weggelassen werden).

Die Zwischenabfrage

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

repräsentiert die einzufügenden Werte. DUALbezieht sich auf eine spezielle Tabelle mit einer Zeile und einer Spalte, die standardmäßig in allen Oracle-Datenbanken vorhanden ist (siehe https://en.wikipedia.org/wiki/DUAL_table ). Auf einem MySQL-Server Version 5.7.26 habe ich beim Auslassen eine gültige Abfrage erhaltenFROM DUAL , aber ältere Versionen (wie 5.5.60) scheinen die FROMInformationen zu benötigen . Bei Verwendung WHERE NOT EXISTSder Zwischenabfrage wird eine leere Ergebnismenge zurückgegeben, wenn die innerste Abfrage übereinstimmende Daten gefunden hat.

Die äußere Abfrage

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

Fügt die Daten ein, falls diese von der Zwischenabfrage zurückgegeben werden.


4
Können Sie weitere Informationen zur Verwendung geben?
Alex V

36
Diese Variante ist geeignet, wenn kein eindeutiger Schlüssel in der Tabelle vorhanden ist ( INSERT IGNOREund INSERT ON DUPLICATE KEYeindeutige Schlüsseleinschränkungen erforderlich sind)
Rabudde

2
Wenn Sie in Zeile 2 "from dual" anstelle von "from table" verwenden, benötigen Sie die Klausel "limit 1" nicht.
Rich

6
Was ist, wenn stuff for value1und stuff for value2identisch sind? Dies würde einDuplicate column name
Robin

1
Ich bevorzuge auch viel SELECT 1anstatt SELECT *in den Unterabfragen. Viel wahrscheinlicher, dass dies durch einen Index erfüllt werden kann.
Arth

58

Bei doppelter Schlüsselaktualisierung oder Einfügen ignorieren können mit MySQL praktikable Lösungen sein.


Beispiel für ein Update eines doppelten Schlüsselupdates basierend auf 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;

Beispiel für das Ignorieren von Einfügungen basierend auf 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] ... ]

Oder:

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

Oder:

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

24

Jede einfache Einschränkung sollte den Job erledigen, wenn eine Ausnahme akzeptabel ist. Beispiele:

  • Primärschlüssel, wenn nicht Ersatz
  • eindeutige Einschränkung für eine Spalte
  • mehrspaltige eindeutige Einschränkung

Entschuldigung, das scheint täuschend einfach zu sein. Ich weiß, dass es angesichts des Links, den Sie mit uns teilen, schlecht aussieht. ;-(

Aber ich gebe diese Antwort trotzdem, weil sie Ihr Bedürfnis zu befriedigen scheint. (Wenn nicht, kann dies dazu führen, dass Sie Ihre Anforderungen aktualisieren. Dies wäre auch "eine gute Sache" (TM).)

Bearbeitet : Wenn eine Einfügung die Datenbankeinschränkung aufheben würde, wird auf Datenbankebene eine Ausnahme ausgelöst, die vom Treiber weitergeleitet wird. Es wird sicherlich Ihr Skript mit einem Fehler stoppen. In PHP muss es möglich sein, diesen Fall anzusprechen ...


1
Ich habe der Frage eine Klarstellung hinzugefügt - trifft Ihre Antwort immer noch zu?
Warren

2
Ich glaube schon. Eine eindeutige Einschränkung führt zum Ausfall falscher Einfügungen. Hinweis: Sie müssen sich mit diesem Fehler in Ihrem Code befassen, dies ist jedoch Standard.
KLE

1
Im Moment werde ich mich an die Lösung halten, die ich akzeptiert habe - aber ich werde mich weiter mit der Behandlung von INSERT-Fehlern usw. befassen, wenn die App wächst
warren

3
INSERT IGNOREGrundsätzlich werden alle Fehler in Warnungen umgewandelt, damit Ihr Skript nicht unterbrochen wird. Sie können dann alle Warnungen mit dem Befehl anzeigen SHOW WARNINGS. Und noch ein wichtiger Hinweis : UNIQUE-Einschränkungen funktionieren nicht mit NULL-Werten, d. H. row1 (1, NULL) und row2 (1, NULL) werden beide eingefügt (es sei denn, eine andere Einschränkung wie ein Primärschlüssel ist fehlerhaft). Unglücklich.
Simon East

18

Hier ist eine PHP-Funktion, die nur dann eine Zeile einfügt, wenn nicht alle angegebenen Spaltenwerte in der Tabelle vorhanden sind.

  • Wenn sich eine der Spalten unterscheidet, wird die Zeile hinzugefügt.

  • Wenn die Tabelle leer ist, wird die Zeile hinzugefügt.

  • Wenn eine Zeile vorhanden ist, in der alle angegebenen Spalten die angegebenen Werte haben, wird die Zeile nicht hinzugefügt.

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

Anwendungsbeispiel:

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

5
Ziemlich teuer, wenn Sie eine große Menge an Einfügungen haben.
Эџad Дьdulяңмaи

wahr, aber effizient, wenn Sie bestimmte Untersuchungen hinzufügen müssen
Charles Forest

1
Warnung: Die mysql_* Erweiterung ist ab PHP 5.5.0 veraltet und wurde ab PHP 7.0.0 entfernt. Stattdessen sollte entweder die Erweiterung mysqli oder PDO_MySQL verwendet werden. Weitere Hilfe bei der Auswahl einer MySQL-API finden Sie in der MySQL-API-Übersicht .
Dharman

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

Wenn der Datensatz vorhanden ist, wird er überschrieben. Wenn es noch nicht existiert, wird es erstellt.


10
REPLACEkann die Zeile löschen und dann anstelle der Aktualisierung einfügen. Der Nebeneffekt ist, dass Einschränkungen andere Objekte löschen und Löschauslöser ausgelöst werden können.
Xmedeko

1
Aus dem MySQL-Handbuch: "REPLACE ist nur dann sinnvoll, wenn eine Tabelle einen PRIMARY KEY- oder UNIQUE-Index hat. Andernfalls entspricht sie INSERT, da kein Index verwendet werden kann, um zu bestimmen, ob eine neue Zeile eine andere dupliziert."
BurninLeo

16

Versuche Folgendes:

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
Versuchen Sie, diese Antworten sind bei StackOverflow von geringem Wert, da sie nur sehr wenig zur Aufklärung des OP und Tausender zukünftiger Forscher beitragen. Bitte bearbeiten Sie diese Antwort, um anzugeben, wie die Lösung funktioniert und warum sie eine gute Idee ist.
Mickmackusa

1
Perfekte Lösung für den Fall, dass die zu passenden Felder keine Schlüssel sind ..!
Leo

6

Es gibt verschiedene Antworten, die beschreiben, wie Sie dieses Problem lösen können, wenn Sie einen UNIQUEIndex haben, mit dem Sie ON DUPLICATE KEYoder vergleichen können INSERT IGNORE. Dies ist nicht immer der Fall, und aufgrund UNIQUEeiner Längenbeschränkung (1000 Byte) können Sie dies möglicherweise nicht ändern. Zum Beispiel musste ich mit Metadaten in WordPress ( wp_postmeta) arbeiten.

Ich habe es endlich mit zwei Fragen gelöst:

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

Abfrage 1 ist eine reguläre UPDATEAbfrage ohne Auswirkung, wenn das betreffende Dataset nicht vorhanden ist. Abfrage 2 ist eine, INSERTdie von a abhängt NOT EXISTS, dh die INSERTwird nur ausgeführt, wenn der Datensatz nicht vorhanden ist.


2

Bemerkenswert ist, dass INSERT IGNORE den Primärschlüssel immer noch erhöht, unabhängig davon, ob die Anweisung erfolgreich war oder nicht, wie dies bei einem normalen INSERT der Fall wäre.

Dies führt zu Lücken in Ihren Primärschlüsseln, die einen Programmierer psychisch instabil machen können. Wenn Ihre Anwendung schlecht gestaltet ist und von perfekten inkrementellen Primärschlüsseln abhängt, kann dies zu Kopfschmerzen führen.

Schauen Sie nach innodb_autoinc_lock_mode = 0(Servereinstellung und mit einem leichten Leistungseinbruch) oder verwenden Sie zuerst ein SELECT, um sicherzustellen, dass Ihre Abfrage nicht fehlschlägt (was auch einen Leistungseinbruch und zusätzlichen Code beinhaltet).


Warum sollten "Lücken in Ihren Primärschlüsseln" - sogar potenziell - "einen Programmierer psychisch instabil machen"? In Primärschlüsseln treten ständig Lücken auf - beispielsweise jedes Mal, wenn Sie einen Datensatz löschen.
Warren

Beginnend mit einer SELECTNiederlage der ganze Zweck, nur eine große Menge von INSERTs abzugeben und sich nicht um Duplikate sorgen zu wollen.
Warren

2

Aktualisieren oder einfügen ohne bekannten Primärschlüssel

Wenn Sie bereits einen eindeutigen oder Primärschlüssel haben, funktionieren die anderen Antworten entweder INSERT INTO ... ON DUPLICATE KEY UPDATE ...oder REPLACE INTO ...sollten einwandfrei funktionieren (beachten Sie, dass das Ersetzen in Löschungen, falls vorhanden, und das Einfügen - vorhandene Werte werden daher nicht teilweise aktualisiert).

Aber wenn Sie die Werte für some_column_idund haben some_type, deren Kombination als eindeutig bekannt ist. Und Sie möchten aktualisieren, some_valuewenn vorhanden, oder einfügen, wenn nicht vorhanden. Und Sie möchten dies in nur einer Abfrage tun (um die Verwendung einer Transaktion zu vermeiden). Dies könnte eine Lösung sein:

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

Grundsätzlich wird die Abfrage folgendermaßen ausgeführt (weniger kompliziert als es aussehen mag):

  • Wählen Sie eine vorhandene Zeile über die WHEREKlauselübereinstimmung aus.
  • Vereinigen Sie das Ergebnis mit einer potenziellen neuen Zeile (Tabelle s), in der die Spaltenwerte explizit angegeben werden (s.id ist NULL, sodass eine neue automatische Inkrement-ID generiert wird).
  • Wenn eine vorhandene Zeile gefunden wird, wird die potenzielle neue Zeile aus der Tabelle sverworfen (aufgrund von LIMIT 1 in der Tabelle t) und es wird immer eine ausgelöst, ON DUPLICATE KEYdie UPDATEdie some_valueSpalte enthält.
  • Wenn eine vorhandene Zeile nicht gefunden wird, wird die potenzielle neue Zeile eingefügt (wie in der Tabelle angegeben s).

Hinweis: Jede Tabelle in einer relationalen Datenbank sollte mindestens eine primäre idSpalte für die automatische Inkrementierung enthalten . Wenn Sie dies nicht haben, fügen Sie es hinzu, auch wenn Sie es auf den ersten Blick nicht benötigen. Es wird definitiv für diesen "Trick" benötigt.


Mehrere andere Antwortende haben ein INSERT INTO ... SELECT FROMFormat angeboten. Warum hast du auch?
Warren

2
@warren Entweder hast du meine Antwort nicht gelesen, du verstehst sie nicht oder ich habe sie nicht richtig erklärt. Lassen Sie mich auf jeden Fall Folgendes hervorheben: Dies ist nicht nur eine reguläre INSERT INTO... SELECT FROM...Lösung. Bitte verweisen Sie auf einen Link zu einer Antwort, die dieselbe ist. Wenn Sie sie finden, werde ich diese Antwort löschen. Andernfalls stimmen Sie meiner Antwort zu (Deal?). Stellen Sie sicher, dass die Antwort, die Sie verknüpfen möchten, nur 1 Abfrage (für Aktualisierung + Einfügen) und keine Transaktion verwendet und auf eine beliebige Kombination von Spalten abzielen kann, von denen bekannt ist, dass sie eindeutig sind (daher sind die Spalten separat nicht vorhanden) müssen einzigartig sein).
Yeti
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.