MySQL: Вставте запис, якщо його немає в таблиці


384

Я намагаюся виконати такий запит:

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

Але це повертає помилку. В основному я не хочу вставляти запис, якщо поле "ім'я" запису вже існує в іншій записи - як перевірити, чи нове ім'я унікальне?



12
Усі нинішні відповіді на це або дури передбачають, що ви можете додати унікальний індекс. Іноді рішення базується на бізнес-логіці, яку неможливо нав'язати всій таблиці. Наприклад, ви дозволяєте кілька рядків із певним значенням у стовпці, але інше значення стовпця буде дозволено відображатись лише в одному рядку. Як ми це зробимо?
Оскар

дивіться також: stackoverflow.com/q/1361340/4418
warren

Відповіді:


502

Я насправді не пропоную вам це зробити, оскільки UNIQUEіндекс, запропонований Пісквором та іншими, - це набагато кращий спосіб зробити це, але ви можете насправді робити те, що намагалися:

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;

Вставити запис:

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  |
+----+--------+-----------+------+

Спробуйте знову вставити той самий запис:

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  |
+----+--------+-----------+------+

Вставте інший запис:

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  |
+----+--------+-----------+------+

І так далі...


Оновлення:

Щоб уникнути #1060 - Duplicate column nameпомилок у випадку, якщо два значення можуть дорівнювати, потрібно назвати стовпці внутрішнього 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  |
+----+---------+-----------+------+

18
Дякую, що допомогло. Моя актуальна проблема набагато складніша, і стовпець просто не може бути унікальним, і я не можу залежати від первинного ключа. Але це саме те, що я шукав.
Руперт

2
@Piskovar: Погоджено. @Rupert: слід індексувати стовпчик, про який йдеться у внутрішньому операторі select ( nameу цьому випадку), якщо це взагалі можливо. Зауважте також, що ви можете робити це SELECT 'John', 'Doe', '022' FROM table_listnamesзамість SELECT * FROM (SELECT 'John', 'Doe', '022') AS tmp- але це буде працювати лише тоді, коли воно table_listnamesмістить один або кілька рядків. Я сумніваюся, що швидкість відрізняється, тому, мабуть, це не турбує.
Майк

3
@VijayRamamurthy: Це працює, тому що ви вставляєте результат оператора Select. Прочитайте запит уважно -> WHEREЗаява належить до SELECTзапиту. Запит Select або повертає один рядок даних (дані вставлені), або взагалі відсутні дані (нічого не вставлено)
Philipp

13
Здається, це не спрацює, якщо ви хочете двічі вставити одне і те ж значення в різні поля (наприклад, SELECT 'Humbert', 'Humbert', '1'у внутрішньому виділенні). Я отримуюERROR 1060 (42S21): Duplicate column name
cburgmer

28
@cburgmer У мене така ж проблема #1060 - Duplicate column name. Ви знайшли рішення? Редагувати: Знайдено. Додайте за кожним значенням AS:SELECT * FROM (SELECT 'Rupert' as name, 'Rupert' as friend, '022' as number) AS tmp
Kai Noack

292

INSERT не дозволяє WHEREв синтаксисі .

Що ви можете зробити: створити UNIQUE INDEXполе на полі, яке повинно бути унікальним ( name), а потім скористайтеся будь-яким:

  • нормально INSERT(і вирішити помилку, якщо назва вже існує)
  • INSERT IGNORE(що не вдасться мовчки викликати попередження (замість помилки), якщо ім'я вже існує)
  • INSERT ... ON DUPLICATE KEY UPDATE(який буде виконано UPDATEв кінці, якщо ім'я вже існує, дивіться документацію )

1
якщо вам потрібно отримати попереджувальне повідомлення, яке ви зможете зробити після цьогоmysql> show warnings\G
fiorentinoing

2
ime, попереджувальне повідомлення, у випадку з INSERT IGNORE, відповідає ^Duplicate entry '.+' for key '.+'$
регексу

1
якщо ви використовуєте вставну ігнору, чи буде вона ігнорувати й інші помилки, які можуть статися не тому, що копії?
gepex

1
@gepex Так, так і буде. Це велика проблема використання INSERT IGNOREІМО.
шмосель

1
Примітка: ця відповідь може бути не найкращим вибором, якщо один із стовпців унікального обмеження ключа є нульовим! Обмеження працює, але ви можете очікувати інших результатів :) Вставлення ("John Doe", NULL), ("John Doe", NULL) призводить до двох рядків, хоча обидва поля разом є унікальним обмеженням ключа. Також см stackoverflow.com/questions/4081783/unique-key-with-nulls
Piemol

57

Працювали:

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');

Це працює лише для баз даних Oracle, так? en.wikipedia.org/wiki/DUAL_table Це питання спеціально стосується MySQL!
Вкрай нерегулярний

1
Я не тестую в базі даних Oracle. Це добре працює в MySQL і тестується.
Махбуб Тіто

9
З тих пір я дізнався, що "подвійний" доступний в MySQL, але не в MariaDB, відкритий відділ MySQL
High Irregular

3
З посилання вікіпедії @ HighlyIrregular "Кілька інших баз даних (включаючи Microsoft SQL Server, MySQL, PostgreSQL, SQLite і Teradata) дозволяють повністю опустити пункт FROM, якщо не потрібна таблиця. Це дозволяє уникнути потреби в будь-якій макетній таблиці". Цей же запит, здається, працює в MySql (5.7, якщо це має значення) без ДУАЛЬНОГО.
stannius

1
Випробуваний і працює добре в MariaDB! Дякую @MahbubTito
Джефф Монтейро,

27

MySQL пропонує дуже миле рішення:

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

Дуже простий у використанні, оскільки ви оголосили унікальний первинний ключ (тут зі значенням 5).


Щоб запобігти цьому, вам потрібно створити унікальний індекс, більше інформації тут: посилання
Azmeer

Дуже небезпечно. Не робіть спроб, якщо ви не знаєте, що робите. Насправді, не намагайтеся взагалі. Перевірте інші відповіді.
Jhourlad Estrella

3
Зауважте, що ЦЕ ЗАБЕЗПЕЧУЄ будь-які відповідні рядки, а потім повторно вставляє їх, більш безпечною версією такої ж поведінки є використання ON DUPLICTE KEY UPDATE, яке оновлює матч-рядки, а не видаляє та повторно вставляє їх: dev.mysql.com/doc/ refman / 5.7 / en /
substitu.html

22
INSERT IGNORE INTO `mytable`
SET `field0` = '2',
`field1` = 12345,
`field2` = 12678;

Тут запит mysql, який вставляє записи, якщо їх немає, і ігнорує існуючі подібні записи.

----Untested----

4
це не працює для мене, ВСТАВИТИ IGNORE INTO emAssignedTagsForEvent SET eventId = '2', defaultTagId = '1';
піту

8
Схоже, це суміш вставлених та оновлених синтаксисів. Ви маєте на увазі INSERT IGNORE INTO `mytable` (`field0`, `field1`, `field2`) values ('2', 12345, 12678);?
Хобо

@Hobo Із посібника з MySQL dev.mysql.com/doc/refman/5.7/uk/insert.html ВСТАВИТИ синтаксис: Це може бути INSERT [...] IGNORE INTO mytable VALUES ...абоINSERT [...] IGNORE INTO mytable SET col_name={expr | DEFAULT},...
Ширкам

"Якщо ви використовуєте модифікатор IGNORE, помилки, що виникають під час виконання оператора INSERT, ігноруються." Отже, ви нічого не вирішили.
Марсело Агімовель

18

Ви можете легко використовувати наступний спосіб:

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

Таким чином ви можете вставити будь-які нові вихідні дані, і якщо у вас є дублікати даних, замініть конкретний стовпець (найкращий стовпчик - це часові позначки).
Наприклад :

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

13

Якщо ви справді не можете отримати унікальний індекс на столі, ви можете спробувати ...

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;

12

Щоб подолати подібну проблему, я змінив таблицю, щоб вона мала унікальний стовпчик. Використовуючи ваш приклад, у створенні я мав би щось на кшталт:

name VARCHAR(20),
UNIQUE (name)

а потім, використовуючи наступний запит, вставляючи його:

INSERT IGNORE INTO train
set table_listnames='Rupert'

Я вважаю за краще це рішення.
Ільяс Карім

9

Цей запит працює добре:

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

Таблицю можна створити за допомогою наступного запиту:

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 ;

Примітка: Створіть таблицю за допомогою другого запиту, перш ніж намагатися використовувати перший запит.


хеш паролю md5 .... вдається!
Чад Грант

4

Брайан Хупер: Ви майже потрапили в точку, але у вашому синатоксі виникла помилка. Ваша вставка ніколи не працюватиме. Я перевірив свою базу даних і ось правильна відповідь:

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'

Я наводжу вам свій приклад y таблиці. Вставка майже подібна до того, як писав Біан Хупер, за винятком того, що я поставив виділення ВІД ДУАЛЬНОГО ont з іншої таблиці. З повагою, Іване


2
Що це за «подвійний»? Вбудоване ключове слово? Я просто не бачу різниці від того, що сказав Брайан ... EDIT: Подальше розслідування отримало неоднозначні та суперечливі (?) Результати. Хоча на сторінці таблиці SQL DUAL йдеться про те, що MySQL не підтримує таблиці DUAL, у посібнику MySQL йдеться про це. Мій власний тест показує, що цього немає, оскільки він дає unknown table status: TABLE_TYPEповідомлення, хоча запит дав результат. Можливо, тому, що MySQL не вимагає цього FROM DUALпункту?
not2qubit


3

Ви вставляєте не Оновлення результату. Ви можете визначити стовпчик імен у первинному стовпчику або встановити його унікальним.


3

У мене виникла проблема, і метод, який радив Майк, працював частково, у мене з’явилася помилка дублікату стовпця назва = '0' і змінив синтаксис вашого запиту таким чином '

     $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;";

Проблема була у назвах стовпців. sum3 дорівнював sum4 і mysql кидав дублюючі імена стовпців, і я написав код у цей синтаксис, і він працював ідеально,


1
Власником DUAL є SYS (SYS володіє словником даних, тому DUAL є частиною словника даних.), Але DUAL може отримати доступ будь-який користувач. У таблиці є один стовпець VARCHAR2 (1) під назвою DUMMY, який має значення "X". MySQL дозволяє вказати DUAL як таблицю в запитах, яким не потрібні дані з будь-яких таблиць. Таблиця DUAL - це спеціальна однорядкова таблиця, одна колонка, яка за замовчуванням присутня в установках Oracle та інших базах даних. У Oracle у таблиці є один стовпець VARCHAR2 (1), який називається DUMMY, який має значення "X". Він підходить для використання у виборі псевдо колони, такої як SYSDATE або USER.
Аднан Хайдер

3

У мене була подібна проблема, і мені потрібно було вставити кілька, якщо таких не було. Тож із наведених вище прикладів я прийшов до цього поєднання ... воно тут на всякий випадок, якщо комусь це буде потрібно.

Зверніть увагу: мені довелося визначати ім'я скрізь, як цього вимагав MSSQL ... MySQL також працює з *.

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;

або

MSSQL: СТВОРИТИ ТАБЛИЦЮ [імена] ([OID] INT IDENTITY (1, 1) NOT NULL, [name] NVARCHAR (32) NOT NULL, PRIMARY KEY CLUSTERED ([OID] ASC)); СТВОРИТИ УНІКАЛЬНИЙ НЕКЛЮСТОВАНИЙ ІНДЕКС [Index_Names_Name] ON [імена] ([ім'я] ASC);


2

Цей запит можна використовувати в PHP-коді.

У цій таблиці у мене стовпчик ідентифікаторів, тому мені потрібно перевірити дублювання всіх стовпців, крім цього стовпця ідентифікаторів:

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

і тепер новий елемент буде доданий лише у випадку, якщо не існує рядка зі значеннями, налаштованими в SETрядку

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.