MySQL: Wstaw rekord, jeśli nie istnieje w tabeli

384

Próbuję wykonać następujące zapytanie:

INSERT INTO table_listnames (name, address, tele)
VALUES ('Rupert', 'Somewhere', '022')
WHERE NOT EXISTS (
    SELECT name FROM table_listnames WHERE name='value'
);

Ale to zwraca błąd. Zasadniczo nie chcę wstawiać rekordu, jeśli pole „nazwa” rekordu już istnieje w innym rekordzie - jak sprawdzić, czy nowa nazwa jest unikalna?

Rupert
źródło
12
Wszystkie bieżące odpowiedzi na to lub duplikaty zakładają, że możesz dodać unikalny indeks. Czasami decyzja opiera się na logice biznesowej, której nie można narzucić na cały stół. Na przykład zezwalasz na wiele wierszy o określonej wartości w kolumnie, ale inna wartość w kolumnie będzie wyświetlana tylko w jednym wierszu. Jak to osiągamy?
Oscar
patrz także: stackoverflow.com/q/1361340/4418
warren

Odpowiedzi:

502

W rzeczywistości nie sugeruję, abyś to zrobił, ponieważ UNIQUEindeks sugerowany przez Piskvor i innych jest znacznie lepszym sposobem na zrobienie tego, ale możesz zrobić to, co próbujesz:

CREATE TABLE `table_listnames` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL,
  `address` varchar(255) NOT NULL,
  `tele` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB;

Wstaw rekord:

INSERT INTO table_listnames (name, address, tele)
SELECT * FROM (SELECT 'Rupert', 'Somewhere', '022') AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_listnames WHERE name = 'Rupert'
) LIMIT 1;

Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

SELECT * FROM `table_listnames`;

+----+--------+-----------+------+
| id | name   | address   | tele |
+----+--------+-----------+------+
|  1 | Rupert | Somewhere | 022  |
+----+--------+-----------+------+

Spróbuj ponownie wstawić ten sam rekord:

INSERT INTO table_listnames (name, address, tele)
SELECT * FROM (SELECT 'Rupert', 'Somewhere', '022') AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_listnames WHERE name = 'Rupert'
) LIMIT 1;

Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

+----+--------+-----------+------+
| id | name   | address   | tele |
+----+--------+-----------+------+
|  1 | Rupert | Somewhere | 022  |
+----+--------+-----------+------+

Wstaw inny rekord:

INSERT INTO table_listnames (name, address, tele)
SELECT * FROM (SELECT 'John', 'Doe', '022') AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_listnames WHERE name = 'John'
) LIMIT 1;

Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

SELECT * FROM `table_listnames`;

+----+--------+-----------+------+
| id | name   | address   | tele |
+----+--------+-----------+------+
|  1 | Rupert | Somewhere | 022  |
|  2 | John   | Doe       | 022  |
+----+--------+-----------+------+

I tak dalej...


Aktualizacja:

Aby zapobiec #1060 - Duplicate column namebłędom w przypadku, gdy dwie wartości mogą być równe, musisz nazwać kolumny wewnętrznego SELECT:

INSERT INTO table_listnames (name, address, tele)
SELECT * FROM (SELECT 'Unknown' AS name, 'Unknown' AS address, '022' AS tele) AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_listnames WHERE name = 'Rupert'
) LIMIT 1;

Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

SELECT * FROM `table_listnames`;

+----+---------+-----------+------+
| id | name    | address   | tele |
+----+---------+-----------+------+
|  1 | Rupert  | Somewhere | 022  |
|  2 | John    | Doe       | 022  |
|  3 | Unknown | Unknown   | 022  |
+----+---------+-----------+------+
Mikrofon
źródło
18
Dzięki, że pomogłem. Mój rzeczywisty problem jest znacznie bardziej złożony, a kolumna po prostu nie może być unikalna i nie mogę polegać na kluczu podstawowym. Ale tego właśnie szukałem.
Rupert
2
@Piskovar: Zgoda. @Rupert: należy indeksować kolumnę, o której mowa w wewnętrznej instrukcji select ( namew tym przypadku), jeśli to w ogóle możliwe. Zauważ też, że możesz to zrobić SELECT 'John', 'Doe', '022' FROM table_listnameszamiast SELECT * FROM (SELECT 'John', 'Doe', '022') AS tmp- ale to zadziała tylko wtedy, gdy table_listnameszawiera już jeden lub więcej wierszy. Wątpię, żeby prędkość była inna, więc prawdopodobnie nie stanowi to problemu.
Mike
3
@VijayRamamurthy: Działa, ponieważ wstawiasz wynik instrukcji Select. Przeczytaj uważnie zapytanie -> WHEREInstrukcja należy do SELECTzapytania. Zapytanie Select zwraca pojedynczy wiersz danych (dane są wstawiane) lub w ogóle nie ma danych (nic nie jest wstawiane)
Philipp
13
Nie wydaje się to działać, jeśli chcesz wstawić dwukrotnie tę samą wartość w różnych polach (np. SELECT 'Humbert', 'Humbert', '1'W wewnętrznej selekcji). OtrzymujęERROR 1060 (42S21): Duplicate column name
cburgmer
28
@cburgmer Mam ten sam problem #1060 - Duplicate column name. Znalazłeś rozwiązanie? Edycja: Znaleziono. Dodaj za każdą wartość AS:SELECT * FROM (SELECT 'Rupert' as name, 'Rupert' as friend, '022' as number) AS tmp
Kai Noack
292

INSERT nie zezwala WHEREna składnię .

Co możesz zrobić: utwórz UNIQUE INDEXpole, które powinno być unikalne ( name), a następnie użyj:

  • normalny INSERT(i obsłużyć błąd, jeśli nazwa już istnieje)
  • INSERT IGNORE(które nie powiedzie się po cichu spowoduje ostrzeżenie (zamiast błędu), jeśli nazwa już istnieje)
  • INSERT ... ON DUPLICATE KEY UPDATE(który wykona UPDATEna końcu, jeśli nazwa już istnieje, zobacz dokumentację )
Piskvor opuścił budynek
źródło
1
jeśli potrzebujesz ostrzeżenia, możesz to zrobić późniejmysql> show warnings\G
fiorentinoing
2
ime, komunikat ostrzegawczy, w przypadku INSERT IGNORE, odpowiada wyrażeniu regularnemu^Duplicate entry '.+' for key '.+'$
user2426679
1
jeśli użyjesz opcji insert ignore, czy zignoruje ona inne błędy, które mogą wystąpić, które nie są spowodowane duplikowaniem?
gepex
1
@gepex Tak, zrobi to. To duży problem z używaniem INSERT IGNOREIMO.
shmosel
1
Uwaga: ta odpowiedź może nie być najlepszym wyborem, jeśli jedna z kolumn unikatowego ograniczenia kluczowego ma wartość null! Ograniczenie działa, ale możesz spodziewać się innych wyników :) Wstawienie („John Doe”, NULL), („John Doe”, NULL) powoduje powstanie dwóch wierszy, chociaż oba pola są razem unikatowym kluczowym ograniczeniem. Zobacz także stackoverflow.com/questions/4081783/unique-key-with-nulls
Piemol
57

Pracował:

INSERT INTO users (full_name, login, password) 
  SELECT 'Mahbub Tito','tito',SHA1('12345') FROM DUAL
WHERE NOT EXISTS 
  (SELECT login FROM users WHERE login='tito');
Mahbub Tito
źródło
Działa to tylko w przypadku baz danych Oracle, prawda? en.wikipedia.org/wiki/DUAL_table To pytanie dotyczy w szczególności MySQL!
Wysoce nieregularny
1
Nie testuję w bazie danych Oracle. Działa dobrze w MySQL i został przetestowany.
Mahbub Tito
9
Odkąd nauczyłem się, że „dual” jest dostępny w MySQL, ale nie w MariaDB, gałęzi MySQL o otwartym kodzie źródłowym
wysoce nieregularna
3
Z linku wikipedii @ HighlyIrregular „Kilka innych baz danych (w tym Microsoft SQL Server, MySQL, PostgreSQL, SQLite i Teradata) pozwala całkowicie pominąć klauzulę FROM, jeśli nie jest potrzebna żadna tabela. Pozwala to uniknąć konieczności używania dowolnej tabeli zastępczej”. To samo zapytanie wydaje się działać w MySql (5.7, jeśli to ma znaczenie) bez FROM DUAL.
stannius
1
Testowane i działa dobrze w MariaDB! Dziękuję @MahbubTito
Jeff Monteiro,
27

MySQL zapewnia bardzo urocze rozwiązanie:

REPLACE INTO `table` VALUES (5, 'John', 'Doe', SHA1('password'));

Bardzo łatwy w użyciu, ponieważ zadeklarowałeś unikalny klucz podstawowy (tutaj o wartości 5).

sdesvergez
źródło
aby temu zapobiec, musisz stworzyć unikalny indeks, więcej informacji tutaj: link
Azmeer,
Bardzo niebezpieczne. Nie próbuj, dopóki nie wiesz, co robisz. W rzeczywistości nie próbuj wcale. Sprawdź inne odpowiedzi.
Jhourlad Estrella
3
Zauważ, że USUWA wszystkie pasujące wiersze, a następnie wstawia je ponownie. Bezpieczniejszą wersją tego samego zachowania jest użycie NA DUPLICTE KLUCZOWEJ AKTUALIZACJI, która aktualizuje wiersze matematyczne zamiast ich usuwania i ponownego wstawiania: dev.mysql.com/doc/ refman / 5.7 / pl /
replace.html
22
INSERT IGNORE INTO `mytable`
SET `field0` = '2',
`field1` = 12345,
`field2` = 12678;

Tutaj zapytanie mysql, które wstawia rekordy, jeśli nie istnieje, i zignoruje istniejące podobne rekordy.

----Untested----
Montaser El-sawy
źródło
4
dla mnie to nie zadziałało, WSTAW IGNORUJ DO emAssignedTagsForEvent SET eventId = '2', defaultTagId = '1';
pitu
8
Wygląda na mieszankę składni i aktualizacji. Czy masz na myśli INSERT IGNORE INTO `mytable` (`field0`, `field1`, `field2`) values ('2', 12345, 12678);?
Hobo
@Hobo z podręcznika MySQL dev.mysql.com/doc/refman/5.7/en/insert.html Składnia INSERT: Może to być INSERT [...] IGNORE INTO mytable VALUES ...lubINSERT [...] IGNORE INTO mytable SET col_name={expr | DEFAULT},...
Shirkam
„Jeśli użyjesz modyfikatora IGNORE, błędy występujące podczas wykonywania instrukcji INSERT są ignorowane.” Więc w zasadzie nic nie rozwiązałeś.
Marcelo Agimóvel,
18

Możesz łatwo użyć następującego sposobu:

INSERT INTO ... ON DUPLICATE KEY UPDATE ...

W ten sposób możesz wstawić dowolne nowe surowe dane, a jeśli masz zduplikowane dane, zamień konkretną kolumnę (najlepsze kolumny to znaczniki czasu).
Na przykład :

CREATE TABLE IF NOT EXISTS Devices (
  id         INT(6)       NOT NULL AUTO_INCREMENT,
  unique_id  VARCHAR(100) NOT NULL PRIMARY KEY,
  created_at VARCHAR(100) NOT NULL,
  UNIQUE KEY unique_id (unique_id),
  UNIQUE KEY id (id)
)
  CHARACTER SET utf8
  COLLATE utf8_unicode_ci;

INSERT INTO Devices(unique_id, time) 
VALUES('$device_id', '$current_time') 
ON DUPLICATE KEY UPDATE time = '$current_time';
Arash Hatami
źródło
13

Jeśli naprawdę nie możesz uzyskać unikalnego indeksu na stole, możesz spróbować ...

INSERT INTO table_listnames (name, address, tele)
    SELECT 'Rupert', 'Somewhere', '022'
        FROM some_other_table
        WHERE NOT EXISTS (SELECT name
                              FROM table_listnames
                              WHERE name='Rupert')
        LIMIT 1;
Brian Hooper
źródło
12

Aby rozwiązać podobny problem, zmodyfikowałem tabelę, aby miała unikalną kolumnę. Korzystając z twojego przykładu, przy tworzeniu chciałbym mieć coś takiego:

name VARCHAR(20),
UNIQUE (name)

a następnie użyj następującego zapytania podczas wstawiania do niego:

INSERT IGNORE INTO train
set table_listnames='Rupert'
obsessiveCookie
źródło
Wolę to rozwiązanie.
Ilyas karim
9

To zapytanie działa dobrze:

INSERT INTO `user` ( `username` , `password` )
    SELECT * FROM (SELECT 'ersks', md5( 'Nepal' )) AS tmp
    WHERE NOT EXISTS (SELECT `username` FROM `user` WHERE `username` = 'ersks' 
    AND `password` = md5( 'Nepal' )) LIMIT 1

I możesz utworzyć tabelę za pomocą następującego zapytania:

CREATE TABLE IF NOT EXISTS `user` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `username` varchar(30) NOT NULL,
    `password` varchar(32) NOT NULL,
    `status` tinyint(1) DEFAULT '0',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

Uwaga: Utwórz tabelę przy użyciu drugiego zapytania, zanim spróbujesz użyć pierwszego zapytania.

dakab
źródło
hash hasła md5 .... yikes!
Chad Grant,
4

Brian Hooper: Prawie osiągnąłeś punkt, ale masz błąd w synatxie. Twoja wkładka nigdy nie będzie działać. Przetestowałem na mojej bazie danych i oto poprawna odpowiedź:

INSERT INTO podatki (datum,ura,meritev1,meritev1_sunki,impulzi1,meritev2,meritev2_sunki,impulzi2)
            SELECT '$datum', '$ura', '$meritve1','$sunki1','$impulzi1','$meritve2','$sunki2','$impulzi2'
                FROM dual
                WHERE NOT EXISTS (SELECT datum,ura
                                      FROM podatki
                                      WHERE datum='$datum' and ura='$ura'

Dam ci mój przykład stołu. Wstaw jest prawie taki sam, jak napisał Bian Hooper, z tą różnicą, że wybrałem FROM DUAL ont z innej tabeli. Z poważaniem, Ivan

Ivan Laharnar
źródło
2
Co to za „dual”? Wbudowane słowo kluczowe? Po prostu nie widzę różnicy od tego, co powiedział Brian ... EDYCJA: Dalsze dochodzenie stało się mieszane i sprzeczne (?) Z wynikami. Podczas gdy strona tabeli SQL DUAL mówi, że MySQL nie obsługuje tabel DUAL, instrukcja MySQL mówi, że tak. Mój własny test pokazuje, że tak nie jest, ponieważ daje unknown table status: TABLE_TYPEkomunikaty, chociaż zapytanie dało wynik. Prawdopodobnie dlatego, że MySQL nie wymaga FROM DUALklauzuli?
not2qubit
4
insert into customer_keyskill(customerID, keySkillID)
select  2,1 from dual
where not exists ( 
    select  customerID  from customer_keyskill 
    where customerID = 2 
    and keySkillID = 1 )
Oms G.
źródło
dual jest tabelą Oracle, a nie MySQL
wysoce nieregularna
3

Wstawiasz nie aktualizuje wyniku. Możesz zdefiniować kolumnę nazwy w kolumnie głównej lub ustawić, aby była unikalna.

Użytkownik 1034
źródło
3

Miałem problem, a metoda zalecana przez Mike'a działała częściowo, miałem błąd Podpisz nazwę kolumny = „0” i zmieniłem składnię twojego zapytania w ten sposób`

     $tQ = "INSERT  INTO names (name_id, surname_id, sum, sum2, sum3,sum4,sum5) 
                SELECT '$name', '$surname', '$sum', '$sum2', '$sum3','$sum4','$sum5' 
FROM DUAL
                WHERE NOT EXISTS (
                SELECT sum FROM names WHERE name_id = '$name' 
AND surname_id = '$surname') LIMIT 1;";

Problem dotyczył nazw kolumn. sum3 było równe sum4, a mysql wyrzucił zdublowane nazwy kolumn, a ja napisałem kod w tej składni i działał idealnie,

Hovhannes Babayan
źródło
1
Właścicielem DUAL jest SYS (SYS jest właścicielem słownika danych, dlatego też DUAL jest częścią słownika danych.), Ale dostęp do DUAL może uzyskać każdy użytkownik. Tabela zawiera pojedynczą kolumnę VARCHAR2 (1) o nazwie DUMMY, która ma wartość „X”. MySQL pozwala na określenie DUAL jako tabeli w zapytaniach, które nie wymagają danych z żadnych tabel. Tabela DUAL jest specjalną tabelą z jednym wierszem i kolumną, domyślnie obecną w Oracle i innych instalacjach baz danych. W Oracle tabela zawiera pojedynczą kolumnę VARCHAR2 (1) o nazwie DUMMY, która ma wartość „X”. Jest odpowiedni do użycia przy wybieraniu pseudokolumny, takiej jak SYSDATE lub USER.
Adnan haider
3

Miałem podobny problem i musiałem wstawić wiele, jeśli nie istnieje. Tak więc z powyższych przykładów doszedłem do tej kombinacji ... jest tutaj na wypadek, gdyby ktoś jej potrzebował.

Uwaga: musiałem zdefiniować nazwę wszędzie, ponieważ wymagała tego MSSQL ... MySQL działa również z *.

INSERT INTO names (name)
SELECT name
FROM
(
  SELECT name
  FROM
  (
     SELECT 'Test 4' as name
  ) AS tmp_single
  WHERE NOT EXISTS
  (
     SELECT name FROM names WHERE name = 'Test 4'
  )
  UNION ALL
  SELECT name
  FROM
  (
     SELECT 'Test 5' as name
  ) AS tmp_single
  WHERE NOT EXISTS
  (
     SELECT name FROM names WHERE name = 'Test 5'
  )
) tmp_all;

MySQL: CREATE TABLE names( OIDint (11) NOT NULL AUTO_INCREMENT, namevarchar (32) COLLATE utf8_unicode_ci NOT NULL, PRIMARY KEY ( OID), UNIQUE KEY name_UNIQUE( name)) ENGINE = InnoDB AUTO_INCREMENT = 1;

lub

MSSQL: CREATE TABLE [nazwy] ([OID] INT IDENTITY (1, 1) NOT NULL, [name] NVARCHAR (32) NOT NULL, PRIMARY KEY CLUSTERED ([OID] ASC)); UTWÓRZ WYJĄTKOWY INDEKS NIEKLUSTEROWANY [nazwa_indeksu] NA [nazwy] ([nazwa] ASC);

Waldemar
źródło
2

Tego zapytania można użyć w kodzie PHP.

Mam kolumnę identyfikatora w tej tabeli, więc muszę sprawdzić, czy są duplikaty dla wszystkich kolumn oprócz tej kolumny identyfikatora:

#need to change values
SET @goodsType = 1, @sybType=5, @deviceId = asdf12345SDFasdf2345;    


INSERT INTO `devices` (`goodsTypeId`, `goodsId`, `deviceId`) #need to change tablename and columnsnames
SELECT * FROM (SELECT @goodsType, @sybType, @deviceId) AS tmp
WHERE NOT EXISTS (
    SELECT 'goodsTypeId' FROM `devices` #need to change tablename and columns names
    WHERE `goodsTypeId` = @goodsType
        AND `goodsId` = @sybType
        AND `deviceId` = @deviceId
) LIMIT 1;

a teraz nowy element zostanie dodany tylko w przypadku braku wiersza z wartościami skonfigurowanymi w SETciągu

Andrzej
źródło