Jak "wstawić, jeśli nie istnieje" w MySQL?

Zacząłem od googlowania i znalazłem ten Artykuł , który mówi o tablicach mutex.

Mam tabelę z ~14 milionami rekordów. Jeśli chcę dodać więcej danych w tym samym formacie, czy istnieje sposób, aby upewnić się, że rekord, który chcę wstawić, nie istnieje już bez użycia pary zapytań(tj. jedno zapytanie do sprawdzenia i jedno do wstawienia jest puste)?

Czy ograniczenie unique Na polu gwarantuje, że insert nie powiedzie się, jeśli jest już tam?

Wygląda na to, że z jedynie ograniczeniem, kiedy wystawiam insert przez php, skrypt wykrzywia się.

Author: warren, 2009-09-01

10 answers

Użyj INSERT IGNORE INTO table

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

Istnieje również INSERT … ON DUPLICATE KEY UPDATE składnia, można znaleźć wyjaśnienia na dev.mysql.com


Post z bogdan.org.ua według webcache Google:

18 października 2007

Na początek: od najnowszego MySQL, składnia przedstawiona w tytule NIE JEST możliwe. Ale istnieje kilka bardzo łatwych sposobów, aby osiągnąć to, co na oczekiwane przy użyciu istniejących funkcjonalności.

Istnieją 3 możliwe rozwiązania: użycie INSERT IGNORE, REPLACE lub WSTAW ... PRZY AKTUALIZACJI DUPLIKATU KLUCZA.

Wyobraź sobie, że mamy tabelę:

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;

Teraz wyobraź sobie, że mamy automatyczny rurociąg importujący transkrypty meta-danych z Ensembl, i że z różnych powodów rurociąg może zostać złamana na każdym etapie egzekucji. Dlatego musimy zapewnić dwa rzeczy:

  1. Powtarzające się egzekucje rurociąg nie zniszczy naszych baza danych

  2. Powtarzające się egzekucje nie zginą z powodu " duplikatu błędy klucza podstawowego.

Metoda 1: Użycie REPLACE

To bardzo proste:

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

Jeśli rekord istnieje, zostanie nadpisany; jeśli jeszcze nie istnieje, zostanie stworzony. Jednak stosowanie tej metody nie jest skuteczne w naszym przypadku: nie musimy nadpisywać istniejących rekordów, jest dobrze żeby je pominąć.

Metoda 2: użycie INSERT IGNORE również bardzo proste:

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

Tutaj, jeśli 'ensembl_transcript_id' jest już obecny w baza danych, zostanie po cichu pominięta (zignorowana). (Dokładniej, oto cytat z podręcznika MySQL reference: "jeśli używasz ignoruj słowo kluczowe, błędy, które występują podczas wykonywania instrukcji INSERT to traktowane jako ostrzeżenia. Na przykład, bez ignorowania, wiersz, który duplikuje istniejący unikalny indeks lub wartość klucza głównego w tabela powoduje błąd duplicate-key i polecenie jest przerywane.".) Jeśli rekord jeszcze nie istnieje, zostanie utworzony.

[[6]} ta druga metoda ma kilka potencjalnych słabości, w tym nie-aborcji zapytania w przypadku wystąpienia jakiegokolwiek innego problemu (patrz manual). Dlatego powinien być stosowany, jeśli wcześniej przetestowany bez Ignoruj słowo kluczowe.

Metoda 3: użycie INSERT ... przy aktualizacji duplikatu klucza:

Trzecią opcją jest użycie INSERT … ON DUPLICATE KEY UPDATE składni, a w część aktualizacji po prostu nie rób nic nie ma znaczenia (pusty) operacja, jak obliczanie 0+0 (Geoffray sugeruje wykonanie id = id przypisanie dla silnika optymalizacji MySQL zignorować to operacji). Zaletą tej metody jest to, że ignoruje ona tylko duplikaty kluczowe zdarzenia, i nadal przerywa na inne błędy.

Jako ostatnia uwaga: ten post został zainspirowany przez Xaprb. Radziłbym też zapoznaj się z innym postem na temat pisania elastycznych zapytań SQL.

 862
Author: knittl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-21 06:53:53

Rozwiązanie:

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) 

Explanation:

Najskrytsze zapytanie

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

Używany jako WHERE NOT EXISTS-warunek wykrywa, czy istnieje już wiersz z danymi do wstawienia. Po znalezieniu jednego wiersza tego rodzaju, zapytanie może zostać zatrzymane, stąd LIMIT 1 (Mikro-optymalizacja, może zostać pominięta).

Zapytanie pośrednie

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

Przedstawia wartości, które mają być wstawione. DUAL odnosi się do specjalnego jednego wiersza, jednej kolumny tabeli obecny przez domyślne we wszystkich bazach danych Oracle (zobacz https://en.wikipedia.org/wiki/DUAL_table ). na serwerze MySQL w wersji 5.7.26 otrzymałem poprawne zapytanie przy pominięciu FROM DUAL, ale starsze wersje (jak 5.5.60) wydają się wymagać informacji FROM. Za pomocą WHERE NOT EXISTS zapytanie pośrednie zwraca pusty zestaw wyników, jeśli najbardziej wewnętrzne zapytanie znalazło pasujące dane.

Zapytanie zewnętrzne

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

Wstawia dane, jeśli jakiekolwiek są zwracane przez zapytanie pośrednie.

 249
Author: Server,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-22 13:57:28

Przy duplicate key update , lub insert ignore mogą być realne rozwiązania z MySQL.


Przykład na duplikat aktualizacji klucza Aktualizacja na podstawie 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;

Przykład insert ignore na podstawie 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] ... ]

Lub:

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

Lub:

INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name [(col_name,...)]
    SELECT ...
    [ ON DUPLICATE KEY UPDATE
      col_name=expr
        [, col_name=expr] ... ]
 62
Author: Zed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-16 17:36:16

Każde proste ograniczenie powinno wykonać zadanie, jeśli wyjątek jest akceptowalny. Przykłady:

  • Klucz podstawowy, jeśli nie zastępczy
  • unikalne ograniczenie na kolumnie
  • multi-column unique constraint

Przepraszam, to wydaje się zwodniczo proste. Wiem, że źle to wygląda z linkiem, którym się z nami dzielisz. ;-(

Ale nigdy nie udzielam tej odpowiedzi, ponieważ wydaje się ona zaspokajać Twoje potrzeby. (Jeśli nie, może to spowodować aktualizację wymagań, które byłyby " a Good Thing " (TM) also).

Edited: jeśli insert złamie unikalne ograniczenie bazy danych, wyjątkiem jest throw na poziomie bazy danych, przekazywany przez sterownik. To z pewnością zatrzyma twój scenariusz, z porażką. W PHP musi być możliwe adresowanie tego przypadku ...

 24
Author: KLE,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-01 16:15:27

Tutaj znajduje się funkcja PHP, która wstawia wiersz tylko wtedy, gdy wszystkie podane wartości kolumn nie istnieją już w tabeli.

  • Jeśli jedna z kolumn jest inna, wiersz zostanie dodany.

  • Jeśli tabela jest pusta, wiersz zostanie dodany.

  • Jeśli istnieje wiersz, w którym wszystkie podane kolumny mają określone wartości, wiersz nie zostanie dodany.

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

Przykładowe użycie:

<?php
insert_unique('mytable', array(
  'mycolumn1' => 'myvalue1',
  'mycolumn2' => 'myvalue2',
  'mycolumn3' => 'myvalue3'
  )
);
?>
 18
Author: Jrm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-02 18:33:09
REPLACE INTO `transcripts`
SET `ensembl_transcript_id` = 'ENSORGT00000000001',
`transcript_chrom_start` = 12345,
`transcript_chrom_end` = 12678;

Jeśli rekord istnieje, zostanie nadpisany; jeśli jeszcze nie istnieje, zostanie utworzony.

 17
Author: Rocio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-06 14:35:08

Spróbuj:

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
 16
Author: Jeb's,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-10 10:18:28

Istnieje kilka odpowiedzi, które obejmują, jak rozwiązać ten problem, jeśli masz UNIQUE indeks, który możesz sprawdzić za pomocą ON DUPLICATE KEY lub INSERT IGNORE. Nie zawsze tak jest, a ponieważ UNIQUE ma ograniczenie długości (1000 bajtów), możesz nie być w stanie tego zmienić. Na przykład musiałem pracować z metadanymi w WordPress (wp_postmeta).

W końcu rozwiązałem to dwoma zapytaniami:

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

ZAPYTANIE 1 jest zwykłym zapytaniem UPDATE bez efektu, gdy dany zbiór danych nie istnieje. Query 2 is an INSERT która zależy od NOT EXISTS, tzn. {[7] } jest wykonywana tylko wtedy, gdy zbiór danych nie istnieje.

 6
Author: wortwart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-18 18:38:12

Warto zauważyć, że INSERT IGNORE nadal będzie zwiększał klucz podstawowy, niezależnie od tego, czy deklaracja zakończyła się sukcesem, czy nie, tak jak zwykła INSERT.

Spowoduje to luki w głównych kluczach, które mogą sprawić, że programista będzie niestabilny psychicznie. Lub jeśli aplikacja jest źle zaprojektowana i zależy od doskonałych przyrostowych kluczy podstawowych, może to być ból głowy.

Spójrz na innodb_autoinc_lock_mode = 0 (ustawienie serwera i ma niewielki wpływ na wydajność), lub użyj najpierw SELECT, aby upewnij się, że Twoje zapytanie nie zawiedzie(które również zawiera trafienie wydajności i dodatkowy kod).

 3
Author: Gilly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-07 21:21:07

Update or insert without known primary key

Jeśli masz już klucz unique lub primary, Pozostałe odpowiedzi z INSERT INTO ... ON DUPLICATE KEY UPDATE ... lub REPLACE INTO ... powinny działać poprawnie(zauważ, że zastąp na usuwa, jeśli istnieje, a następnie wstawia - w ten sposób nie aktualizuje częściowo istniejących wartości).

Ale jeśli masz wartości dla some_column_id i some_type, których kombinacja jest znana jako unikalna. I chcesz zaktualizować some_value, jeśli istnieje, lub wstawić, jeśli nie istnieje. I chcesz to zrobić w jednym zapytaniu (aby uniknąć wykorzystania transakcji). To może być rozwiązanie:

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

Zasadniczo zapytanie wykonuje się w ten sposób (mniej skomplikowane, niż może się wydawać):

  • wybierz istniejący wiersz poprzez dopasowanie klauzuli WHERE.
  • Unia, która daje wynik z potencjalnym nowym wierszem (Tabela s), gdzie wartości kolumn są jawnie podane (s.id jest NULL, więc wygeneruje nowy identyfikator auto-increment).
  • Jeśli znaleziono istniejący wiersz, to potencjalny nowy wiersz z tabeli s jest odrzucony (ze względu na ograniczenie 1 w tabeli t), A zawsze wywoła ON DUPLICATE KEY, która będzie UPDATE kolumną some_value.
  • Jeśli istniejący wiersz nie został znaleziony, to potencjalny nowy wiersz jest wstawiany(jak podano w tabeli s).

uwaga: każda tabela w relacyjnej bazie danych powinna mieć co najmniej podstawową kolumnę auto-increment id. Jeśli tego nie masz, dodaj go, nawet jeśli nie potrzebujesz go na pierwszy rzut oka. Jest to zdecydowanie potrzebne do tej "sztuczki".

 3
Author: Yeti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-06 08:26:22