Jak zaktualizować, jeśli istnieje, wstawić, jeśli nie (AKA „upsert” lub „scal”) w MySQL?

152

Czy istnieje łatwy sposób na INSERTuzyskanie wiersza, gdy nie istnieje, lub UPDATEjeśli istnieje, za pomocą jednego zapytania MySQL?

blub
źródło

Odpowiedzi:

191

Użyj INSERT ... ON DUPLICATE KEY UPDATE. Na przykład:

INSERT INTO `usage`
(`thing_id`, `times_used`, `first_time_used`)
VALUES
(4815162342, 1, NOW())
ON DUPLICATE KEY UPDATE
`times_used` = `times_used` + 1
chaos
źródło
10
Tak, wierzę, odpowiednik SQL Server nazywa MERGE. Ogólnie pojęcie to jest często określane jako "UPSERT".
chaos
3
@blub: Jeśli utworzysz unikalny klucz na gebi topicbędzie działał ( ALTER TABLE table ADD UNIQUE geb_by_topic (geb, topic)).
chaos
1
@Brian: Odpowiednik Oracle jest również wywoływany, MERGEale nie jestem pewien, czy jego składnia jest identyczna z SQL Server.
Ken Keenan
1
@Brooks: Jeśli przekażesz 0, w rzeczywistości użyje 0 jako wartości. Więc nie rób tego. Nie przekazuj niczego ani nie przekazuj a NULL, aby umożliwić auto_incrementzachowanie działania (co w przeciwnym razie tak, działa tak, jak zakładasz ; zobacz dev.mysql.com/doc/refman/5.5/en/example-auto-increment. html ).
chaos
3
Dziękuję i możesz użyć, VALUES(col)aby uzyskać wartość z argumentu wstawiania w przypadku duplikatu. Np .:ON DUPLICATE UPDATE b = VALUES(b), c = VALUES(c)
gerrytan
5

Wiem, że to stare pytanie, ale Google ostatnio mnie tu przyprowadziło, więc wyobrażam sobie, że inni też tu przychodzą.

@chaos jest poprawne: istnieje INSERT ... ON DUPLICATE KEY UPDATEskładnia.

Jednak pierwotne pytanie dotyczyło MySQL, aw MySQL jest REPLACE INTO ...składnia. IMHO, to polecenie jest łatwiejsze i prostsze w użyciu do upserts. Z instrukcji:

REPLACE działa dokładnie tak samo, jak INSERT, z tą różnicą, że jeśli stary wiersz w tabeli ma taką samą wartość jak nowy wiersz dla indeksu PRIMARY KEY lub UNIQUE, stary wiersz jest usuwany przed wstawieniem nowego wiersza.

Zauważ, że to nie jest standardowy SQL. Przykład z instrukcji:

CREATE TABLE test (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT,
  data VARCHAR(64) DEFAULT NULL,
  ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id)
);

mysql> REPLACE INTO test VALUES (1, 'Old', '2014-08-20 18:47:00');
Query OK, 1 row affected (0.04 sec)

mysql> REPLACE INTO test VALUES (1, 'New', '2014-08-20 18:47:42');
Query OK, 2 rows affected (0.04 sec)

mysql> SELECT * FROM test;
+----+------+---------------------+
| id | data | ts                  |
+----+------+---------------------+
|  1 | New  | 2014-08-20 18:47:42 |
+----+------+---------------------+
1 row in set (0.00 sec)

Edycja: po prostu uczciwe ostrzeżenie, które REPLACE INTOnie jest jak UPDATE. Jak mówi instrukcja, REPLACEusuwa wiersz, jeśli istnieje, a następnie wstawia nowy. (Zwróć uwagę na zabawne „2 wiersze dotknięte” w powyższym przykładzie). Oznacza to, że zastąpi on wartości wszystkich kolumn istniejącego rekordu (a nie tylko zaktualizuje niektóre kolumny). Zachowanie MySQL REPLACE INTOjest bardzo podobne do Sqlite INSERT OR REPLACE INTO. Zobacz to pytanie , aby poznać niektóre obejścia, jeśli chcesz zaktualizować tylko kilka kolumn (a nie wszystkie), jeśli rekord już istnieje.

Armadillo Jim
źródło