MySQL: Chèn bản ghi nếu không tồn tại trong bảng


384

Tôi đang cố gắng thực hiện các truy vấn sau đây:

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

Nhưng điều này trả về một lỗi. Về cơ bản, tôi không muốn chèn một bản ghi nếu trường 'tên' của bản ghi đã tồn tại trong một bản ghi khác - làm thế nào để kiểm tra xem tên mới có phải là duy nhất không?



12
Tất cả các câu trả lời hiện tại cho điều này hoặc bản sao đều cho rằng bạn có thể thêm một chỉ mục duy nhất. Đôi khi quyết định dựa trên logic kinh doanh không thể áp đặt trên toàn bộ bảng. Ví dụ: bạn cho phép nhiều hàng có một giá trị nhất định trong một cột, nhưng một giá trị khác trong cột sẽ chỉ được phép xuất hiện trên một hàng. Làm thế nào để chúng ta thực hiện điều đó?
Oscar

Câu trả lời:


502

Tôi thực sự không gợi ý rằng bạn làm điều này, vì UNIQUEchỉ số như Piskvor và những người khác đề xuất là cách tốt hơn để làm điều đó, nhưng bạn thực sự có thể làm những gì bạn đang cố gắng:

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;

Chèn một bản ghi:

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

Hãy thử chèn lại cùng một bản ghi:

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

Chèn một bản ghi khác:

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

Và như thế...


Cập nhật:

Để tránh #1060 - Duplicate column namelỗi trong trường hợp hai giá trị có thể bằng nhau, bạn phải đặt tên cho các cột của CHỌN bên trong:

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
Cảm ơn đã giúp. Vấn đề thực tế của tôi phức tạp hơn nhiều và cột không thể là duy nhất và tôi không thể phụ thuộc vào khóa chính. Nhưng đây chính xác là những gì tôi đang tìm kiếm.
Rupert

2
@Piskovar: Đồng ý. @Rupert: bạn nên lập chỉ mục cột được đề cập trong câu lệnh chọn bên trong ( name, trong trường hợp này), nếu có thể. Cũng lưu ý rằng bạn có thể làm SELECT 'John', 'Doe', '022' FROM table_listnames, thay vì SELECT * FROM (SELECT 'John', 'Doe', '022') AS tmp- nhưng điều đó sẽ chỉ hoạt động nếu table_listnamesđã chứa một hoặc nhiều hàng. Tôi nghi ngờ tốc độ là khác nhau, vì vậy nó có thể không phải là một mối quan tâm.
Mike

3
@VijayRamamurthy: Điều này hoạt động vì bạn Chèn kết quả của câu lệnh Chọn. Đọc truy vấn cẩn thận -> Câu WHERElệnh thuộc về SELECTtruy vấn. Truy vấn Chọn trả về một hàng dữ liệu duy nhất (dữ liệu được chèn) hoặc hoàn toàn không có dữ liệu (không có gì được chèn)
Philipp

13
Điều này dường như không hoạt động nếu bạn muốn chèn cùng một giá trị hai lần vào các trường khác nhau (ví dụ: SELECT 'Humbert', 'Humbert', '1'trong phần chọn bên trong). Tôi nhận được mộtERROR 1060 (42S21): Duplicate column name
cburgmer

28
@cburgmer Mình gặp vấn đề tương tự #1060 - Duplicate column name. Bạn đã tìm thấy một giải pháp? Chỉnh sửa: Tìm thấy nó. Thêm đằng sau mỗi giá trị một AS:SELECT * FROM (SELECT 'Rupert' as name, 'Rupert' as friend, '022' as number) AS tmp
Kai Noack

292

INSERT không cho phép WHEREtrong cú pháp .

Những gì bạn có thể làm: tạo một trường UNIQUE INDEXtrên trường duy nhất ( name), sau đó sử dụng một trong hai:

  • bình thường INSERT(và xử lý lỗi nếu tên đã tồn tại)
  • INSERT IGNORE(sẽ không âm thầm gây ra cảnh báo (thay vì lỗi) nếu tên đã tồn tại)
  • INSERT ... ON DUPLICATE KEY UPDATE(sẽ thực hiện UPDATEở cuối nếu tên đã tồn tại, xem tài liệu )

1
nếu bạn cần nhận được thông báo cảnh báo, sau đó bạn có thể thực hiệnmysql> show warnings\G
fiorentinoing

2
ime, thông báo cảnh báo, trong trường hợp INSERT IGNORE, khớp với biểu thức của^Duplicate entry '.+' for key '.+'$
user2426679

1
Nếu bạn sử dụng chèn bỏ qua, nó sẽ bỏ qua các lỗi khác có thể xảy ra không phải do trùng lặp?
gepex

1
@gepex Vâng, nó sẽ. Đó là một vấn đề lớn khi sử dụng INSERT IGNOREIMO.
shmosel

1
Lưu ý: câu trả lời này có thể không phải là lựa chọn tốt nhất nếu một trong các cột trong ràng buộc khóa duy nhất là null! Ràng buộc hoạt động, nhưng bạn có thể mong đợi các kết quả khác :) Chèn ("John Doe", NULL), ("John Doe", NULL) dẫn đến hai hàng mặc dù cả hai trường nằm trong một ràng buộc khóa duy nhất. Đồng thời xem stackoverflow.com/questions/4081783/unique-key-with-nulls
Piemol

57

Đã làm việc :

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

Điều này chỉ hoạt động cho cơ sở dữ liệu Oracle phải không? vi.wikipedia.org/wiki/DUAL_table Câu hỏi này đặc biệt về MySQL!
Rất bất thường

1
Tôi không kiểm tra trong cơ sở dữ liệu của Oracle. Nó hoạt động tốt tại MySQL và đã thử nghiệm.
Mahbub Tito

9
Tôi đã học "dual" có sẵn trong MySQL nhưng không có trong MariaDB, nhánh nguồn mở của MySQL
Rất bất thường

3
Từ liên kết wikipedia của @ HighIruity '"Một số cơ sở dữ liệu khác (bao gồm Microsoft SQL Server, MySQL, PostgreQuery, SQLite và Teradata) cho phép một người bỏ hoàn toàn mệnh đề TỪ nếu không cần bảng. Điều này tránh được sự cần thiết của bất kỳ bảng giả nào." Truy vấn tương tự dường như hoạt động trong MySql (5.7, nếu có vấn đề) mà không có TỪ DUAL.
stannius

1
Đã thử nghiệm và hoạt động tốt trong MariaDB! Cảm ơn bạn @MahbubTito
Jeff Monteiro

27

MySQL cung cấp một giải pháp rất dễ thương:

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

Rất dễ sử dụng vì bạn đã khai báo một khóa chính duy nhất (ở đây có giá trị 5).


để ngăn chặn nó, bạn cần tạo một chỉ mục duy nhất, thêm thông tin tại đây: link
Azmeer

Rất nguy hiểm. Đừng cố gắng trừ khi bạn biết những gì bạn đang làm. Thật ra, đừng cố gắng chút nào. Kiểm tra các câu trả lời khác.
Jhourlad Estrella

3
Lưu ý, điều này XÓA bất kỳ hàng phù hợp nào và sau đó chèn lại chúng, một phiên bản an toàn hơn của hành vi tương tự này là sử dụng ON DUPLICTE KEY UPDATE, cập nhật các hàng hóa toán học thay vì xóa và chèn lại chúng: dev.mysql.com/doc/ refman / 5.7 / en /
thay.html

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

Ở đây truy vấn mysql, chèn các bản ghi nếu không tồn tại và sẽ bỏ qua các bản ghi tương tự hiện có.

----Untested----

4
nó không hoạt động với tôi, INSERT IGNORE INTO emAssignTagsForEvent SET eventId = '2', defaultTagId = '1';
pote

8
Trông giống như một hỗn hợp của cú pháp chèn và cập nhật. Ý bạn là INSERT IGNORE INTO `mytable` (`field0`, `field1`, `field2`) values ('2', 12345, 12678);sao
Hobo

@Hobo Từ MySQL Manual dev.mysql.com/doc/refman/5.7/en/insert.html cú pháp INSERT: Nó có thể INSERT [...] IGNORE INTO mytable VALUES ...HoặcINSERT [...] IGNORE INTO mytable SET col_name={expr | DEFAULT},...
Shirkam

"Nếu bạn sử dụng công cụ sửa đổi IGNORE, các lỗi xảy ra trong khi thực hiện câu lệnh INSERT sẽ bị bỏ qua.". Vì vậy, về cơ bản, bạn đã không giải quyết bất cứ điều gì.
Marcelo Agimóvel

18

Bạn có thể dễ dàng sử dụng theo cách sau:

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

Bằng cách này, bạn có thể chèn bất kỳ dữ liệu thô mới nào và nếu bạn có dữ liệu trùng lặp, hãy thay thế cột cụ thể (cột tốt nhất là dấu thời gian).
Ví dụ :

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

Nếu bạn thực sự không thể có được một chỉ mục duy nhất trên bàn, bạn có thể thử ...

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

Để khắc phục một vấn đề tương tự, tôi đã sửa đổi bảng để có một cột duy nhất. Sử dụng ví dụ của bạn, khi tạo, tôi sẽ có một cái gì đó như:

name VARCHAR(20),
UNIQUE (name)

và sau đó sử dụng truy vấn sau đây khi chèn vào nó:

INSERT IGNORE INTO train
set table_listnames='Rupert'

Tôi thích giải pháp này.
Ilyas karim

9

Truy vấn này hoạt động tốt:

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

Và bạn có thể tạo bảng bằng truy vấn sau:

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 ;

Lưu ý: Tạo bảng bằng truy vấn thứ hai trước khi thử sử dụng truy vấn đầu tiên.


băm mật khẩu md5 .... yike!
Chad Grant

4

Brian Hooper: Bạn gần như đạt được điểm nhưng bạn có một lỗi trong synatx của bạn. Chèn của bạn sẽ không bao giờ làm việc. Tôi đã thử nghiệm trên cơ sở dữ liệu của mình và đây là câu trả lời đúng:

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'

Tôi sẽ cho bạn ví dụ của tôi về bảng y. Chèn gần giống như Bian Hooper đã viết, ngoại trừ việc tôi đặt TỪ DUAL ont từ bảng khác. Cind liên quan, Ivan


2
Cái "kép" đó là gì? Một từ khóa được xây dựng? Tôi chỉ không thấy sự khác biệt so với những gì Brian nói ... EDIT: Điều tra sâu hơn có kết quả trái ngược và mâu thuẫn (?). Trong khi trang bảng SQL DUAL nói rằng MySQL không hỗ trợ các bảng DUAL, thì hướng dẫn sử dụng MySQL nói rằng nó có. Thử nghiệm của riêng tôi cho thấy rằng nó không, vì nó đưa ra unknown table status: TABLE_TYPEthông báo, mặc dù truy vấn mang lại kết quả. Có lẽ bởi vì MySQL không yêu cầu FROM DUALđiều khoản?
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 )

dual là một bảng Oracle không phải MySQL
Rất bất thường vào

3

Bạn đang chèn không cập nhật kết quả. Bạn có thể xác định cột tên trong cột chính hoặc đặt nó là duy nhất.


3

Tôi đã gặp sự cố và phương thức mà Mike khuyên nên hoạt động một phần, tôi đã gặp lỗi Dublicate Cột name = '0' và đã thay đổi cú pháp truy vấn của bạn như thế này`

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

Vấn đề là với tên cột. sum3 bằng với sum4 và mysql đã ném các tên cột công khai và tôi đã viết mã theo cú pháp này và nó hoạt động hoàn hảo,


1
Chủ sở hữu của DUAL là SYS (SYS sở hữu từ điển dữ liệu, do đó DUAL là một phần của từ điển dữ liệu.) Nhưng bất kỳ người dùng nào cũng có thể truy cập DUAL. Bảng có một cột VARCHAR2 (1) được gọi là DUMMY có giá trị 'X'. MySQL cho phép DUAL được chỉ định làm bảng trong các truy vấn không cần dữ liệu từ bất kỳ bảng nào. Bảng DUAL là bảng một hàng, một cột đặc biệt được trình bày theo mặc định trong Oracle và các cài đặt cơ sở dữ liệu khác. Trong Oracle, bảng có một cột VARCHAR2 (1) được gọi là DUMMY có giá trị 'X'. Nó phù hợp để sử dụng trong việc chọn một cột giả như SYSDATE hoặc USER.
Adnan

3

Tôi đã có một vấn đề tương tự và tôi cần phải chèn nhiều nếu không tồn tại. Vì vậy, từ các ví dụ trên tôi đã đến với sự kết hợp này ... nó ở đây chỉ trong trường hợp ai đó sẽ cần nó.

Lưu ý: Tôi phải xác định tên ở mọi nơi vì MSSQL yêu cầu ... MySQL cũng hoạt động với *.

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) đối chiếu utf8_unicode_ci NOT NULL, PRIMARY KEY ( OID), UNIQUE KEY name_UNIQUE( name)) ENGINE = InnoDB AUTO_INCREMENT = 1;

hoặc là

MSSQL: TẠO BẢNG [tên] ([OID] INT IDENTITY (1, 1) KHÔNG NULL, [name] NVARCHAR (32) KHÔNG NULL, PRIMARY KEY CLUSTERED ([OID] ASC)); TẠO ĐỘC LẬP KHÔNG GIỚI HẠN [Index_Names_Name] ON [tên] ([tên] ASC);


2

Truy vấn này có thể được sử dụng trong mã PHP.

Tôi có một cột ID trong bảng này, vì vậy tôi cần kiểm tra trùng lặp cho tất cả các cột ngoại trừ cột ID này:

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

và bây giờ mục mới sẽ chỉ được thêm vào trong trường hợp không có hàng tồn tại với các giá trị được cấu hình trong SETchuỗi

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.