MySQL: แทรกบันทึกหากไม่มีอยู่ในตาราง


384

ฉันพยายามที่จะดำเนินการค้นหาต่อไปนี้:

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

แต่นี่กลับข้อผิดพลาด โดยทั่วไปฉันไม่ต้องการที่จะแทรกบันทึกถ้าเขตข้อมูล 'ชื่อ' ของบันทึกที่มีอยู่แล้วในบันทึกอื่น - วิธีการตรวจสอบว่าชื่อใหม่ไม่ซ้ำกัน?


5
เป็นไปได้ที่ซ้ำกันของวิธีการ 'แทรกถ้าไม่มี' ใน MySQL?
วอร์เรน

12
คำตอบปัจจุบันทั้งหมดนี้หรือซ้ำซ้อนคิดว่าคุณสามารถเพิ่มดัชนีที่ไม่ซ้ำกัน บางครั้งการตัดสินใจขึ้นอยู่กับตรรกะทางธุรกิจที่ไม่สามารถกำหนดได้ทั้งตาราง ตัวอย่างเช่นคุณอนุญาตให้มีหลายแถวที่มีค่าบางค่าในคอลัมน์ แต่ค่าอื่นในคอลัมน์จะได้รับอนุญาตให้ปรากฏในหนึ่งแถวเท่านั้น เราจะทำเช่นนั้นได้อย่างไร
ออสการ์

ดูเพิ่มเติมได้ที่: stackoverflow.com/q/1361340/4418
warren

คำตอบ:


502

ฉันไม่ได้แนะนำให้คุณทำเช่นนี้เนื่องจากUNIQUEดัชนีตามที่ Piskvor แนะนำและอื่น ๆ เป็นวิธีที่ดีกว่าที่จะทำ แต่คุณสามารถทำสิ่งที่คุณพยายาม:

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ข้อผิดพลาดในกรณีที่ค่าสองค่าอาจเท่ากันคุณต้องตั้งชื่อคอลัมน์ของ Inner 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มีหนึ่งแถวขึ้นไปแล้ว ฉันสงสัยว่าความเร็วจะแตกต่างกันอย่างไรดังนั้นมันจึงอาจไม่น่าเป็นห่วง
Mike

3
@VijayRamamurthy: ใช้งานได้เนื่องจากคุณแทรกผลลัพธ์ของคำสั่ง Select อ่านแบบสอบถามอย่างระมัดระวัง -> WHEREคำสั่งนั้นเป็นของSELECTแบบสอบถาม แบบสอบถามแบบใช้เลือกจะส่งคืนแถวข้อมูลเดียว (แทรกข้อมูล) หรือไม่มีข้อมูลเลย (ไม่มีการแทรกใด ๆ )
ฟิลิปส์

13
สิ่งนี้ดูเหมือนจะไม่ทำงานหากคุณต้องการแทรกค่าเดียวกันสองครั้งในฟิลด์ที่ต่างกัน (เช่นSELECT 'Humbert', 'Humbert', '1'ในตัวเลือกด้านใน) ฉันได้รับ aERROR 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ในไวยากรณ์

คุณสามารถทำอะไรได้บ้าง: สร้าง a UNIQUE INDEXบนฟิลด์ซึ่งควรไม่ซ้ำกัน ( name) จากนั้นใช้:

  • ปกติINSERT(และจัดการข้อผิดพลาดหากชื่อมีอยู่แล้ว)
  • INSERT IGNORE (ซึ่งจะ ล้มเหลวอย่างเงียบ ๆทำให้เกิดคำเตือน (แทนที่จะเป็นข้อผิดพลาด) หากมีชื่ออยู่แล้ว)
  • INSERT ... ON DUPLICATE KEY UPDATE(ซึ่งจะดำเนินการUPDATEในตอนท้ายหากชื่อมีอยู่แล้วดูเอกสารประกอบ )

1
หากคุณต้องการได้รับข้อความเตือนคุณสามารถทำได้mysql> show warnings\G
fiorentinoing

2
ime, ข้อความเตือน, ในกรณีของINSERT IGNORE, ตรงกับ regex ของ^Duplicate entry '.+' for key '.+'$
user2426679

1
หากคุณใช้การเพิกเฉยต่อการใส่ก็จะเพิกเฉยต่อข้อผิดพลาดอื่น ๆ ที่อาจเกิดขึ้นซึ่งไม่ใช่เพราะซ้ำกันหรือไม่
gepex

1
@gepex ใช่มันจะ นั่นเป็นปัญหาใหญ่ในการใช้INSERT IGNOREIMO
shmosel

1
หมายเหตุ: คำตอบนี้อาจไม่ใช่ตัวเลือกที่ดีที่สุดหากหนึ่งในคอลัมน์ในข้อ จำกัด คีย์ที่ไม่ซ้ำกันนั้นเป็นโมฆะ! ข้อ จำกัด ใช้งานได้ แต่คุณอาจคาดหวังผลลัพธ์อื่น ๆ :) การแทรก ("John Doe", NULL), ("John Doe", NULL) ให้ผลลัพธ์เป็นสองแถวแม้ว่าเขตข้อมูลทั้งสองจะอยู่ในข้อ จำกัด ของคีย์ที่ไม่ซ้ำกัน ดูstackoverflow.com/questions/4081783/unique-key-wull-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 และทดสอบ
Mahbub Tito

9
ฉันได้เรียนรู้ตั้งแต่ "dual" ใน MySQL แต่ไม่ใช่ใน MariaDB ซึ่งเป็นสาขาโอเพ่นซอร์สของ MySQL
Highly Irregular

3
จากลิงก์ wikipedia ของ @ HighlyIrregular "ฐานข้อมูลอื่น ๆ (รวมถึง Microsoft SQL Server, MySQL, PostgreSQL, SQLite และ Teradata) ช่วยให้ผู้ใช้สามารถตัดส่วนคำสั่ง FROM ทั้งหมดได้หากไม่จำเป็นต้องใช้ตารางใด ๆ ดูเหมือนว่าแบบสอบถามเดียวกันจะทำงานใน MySql (5.7 หากมีความสำคัญ) โดยไม่มี FROM DUAL
stannius

1
ผ่านการทดสอบและทำงานได้ดีใน MariaDB! ขอบคุณ @MahbubTito
Jeff Monteiro

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 /
replace.html

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

นี่คือแบบสอบถาม mysql ซึ่งจะแทรกเร็กคอร์ดหากไม่มีอยู่และจะละเว้นเร็กคอร์ดที่คล้ายกันที่มีอยู่

----Untested----

4
มันใช้งานไม่ได้สำหรับฉัน INSERT IGNORE INTO emAssignedTagsForEvent SET eventId = '2', defaultTagId = '1';
pitu

8
ดูเหมือนว่าส่วนผสมของการแทรกและการปรับปรุงไวยากรณ์ คุณหมายถึงINSERT IGNORE INTO `mytable` (`field0`, `field1`, `field2`) values ('2', 12345, 12678);อะไร
กุ๊ย

@Hobo จากคู่มือ MySQL dev.mysql.com/doc/refman/5.7/en/insert.htmlไวยากรณ์ INSERT: สามารถINSERT [...] IGNORE INTO mytable VALUES ...หรือINSERT [...] IGNORE INTO mytable SET col_name={expr | DEFAULT},...
เชิร์

"ถ้าคุณใช้โมดิฟายเออร์ IGNORE ข้อผิดพลาดที่เกิดขึ้นขณะดำเนินการคำสั่ง INSERT จะถูกละเว้น". โดยพื้นฐานแล้วคุณไม่ได้แก้ไขอะไรเลย
Marcelo Agimóvel

18

คุณสามารถใช้วิธีการต่อไปนี้:

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

ด้วยวิธีนี้คุณสามารถแทรกraw ใหม่ใด ๆและถ้าคุณมีข้อมูลที่ซ้ำกันให้แทนที่คอลัมน์เฉพาะ (คอลัมน์ที่ดีที่สุดคือ timestamps)
ตัวอย่างเช่น :

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'

ฉันชอบวิธีนี้
Ilyas karim

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 ;

หมายเหตุ: สร้างตารางโดยใช้คิวรีที่สองก่อนลองใช้คิวรีแรก


mash รหัสผ่าน md5 .... yikes!
ชาดแกรนท์

4

Brian Hooper: คุณเกือบจะได้คะแนน แต่คุณมีข้อผิดพลาดใน synatx ของคุณ ส่วนแทรกของคุณจะไม่ทำงาน ฉันทดสอบบนฐานข้อมูลของฉันและนี่คือคำตอบที่ถูกต้อง:

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 ส่วนแทรกนั้นแทบจะเหมือนที่ Bian Hooper เขียนยกเว้นว่าฉันใส่ตัวเลือก FROM DUAL จากตารางอื่น อ้างอิงถึงอีวาน


2
"คู่" คืออะไร? คำหลักในตัวหรือไม่ ฉันแค่ไม่เห็นความแตกต่างจากสิ่งที่ไบรอันกล่าวว่า ... แก้ไข: การสอบสวนเพิ่มเติมได้ผลที่หลากหลายและขัดแย้งกัน (?) ในขณะที่หน้าตาราง SQL DUALบอกว่า MySQL ไม่สนับสนุนตาราง DUAL, คู่มือ MySQLบอกว่าทำ การทดสอบของฉันแสดงให้เห็นว่ามันไม่ได้เนื่องจากมันให้unknown table status: TABLE_TYPEข้อความแม้ว่าแบบสอบถามจะให้ผลลัพธ์ อาจเป็นเพราะ MySQL ไม่ต้องการFROM DUALประโยคหรือไม่
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 เป็นตาราง Oracle ไม่ใช่ MySQL
Highly Irregular

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 โยนชื่อคอลัมน์ dublicate และฉันเขียนรหัสในรูปแบบนี้และมันทำงานได้อย่างสมบูรณ์


1
เจ้าของ DUAL คือ SYS (SYS เป็นเจ้าของพจนานุกรมข้อมูลดังนั้น DUAL จึงเป็นส่วนหนึ่งของพจนานุกรมข้อมูล) แต่ผู้ใช้ทุกคนสามารถเข้าถึง DUAL ได้ ตารางมีคอลัมน์ VARCHAR2 (1) เดียวที่เรียกว่า DUMMY ที่มีค่าเป็น 'X' MySQL อนุญาตให้ระบุ DUAL เป็นตารางในแบบสอบถามที่ไม่ต้องการข้อมูลจากตารางใด ๆ ตาราง DUAL เป็นตารางแบบหนึ่งแถวพิเศษซึ่งเป็นหนึ่งคอลัมน์ที่นำเสนอโดยค่าเริ่มต้นใน Oracle และการติดตั้งฐานข้อมูลอื่น ๆ ใน Oracle ตารางมีคอลัมน์ VARCHAR2 (1) เดียวที่เรียกว่า DUMMY ที่มีค่า 'X' เหมาะสำหรับใช้ในการเลือกคอลัมน์หลอกเช่น SYSDATE หรือ USER
Adnan haider

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: สร้างตารางnames( OIDint (11) ไม่ใช่ NULL AUTO_INCREMENT, namevarchar (32) รวม utf8_unicode_ci COLLATE ไม่ใช่ NULL, คีย์หลัก ( OID), คีย์ UNIQUE name_UNIQUE()name )) Engine = InnoDB AUTO_INCREMENT = 1;

หรือ

MSSQL: สร้างตาราง [ชื่อ] ([OID] INT IDENTITY (1, 1) ไม่เป็น NULL, [ชื่อ] NVARCHAR (32) ไม่เป็น NULL, คีย์หลักที่คลัสเตอร์ ([OID] ASC)); สร้าง INDEX NONCLUSTERED INDEX ที่ไม่ซ้ำกัน [Index_Names_Name] เปิด [ชื่อ] ([ชื่อ] ASC)


2

แบบสอบถามนี้สามารถใช้ในรหัส PHP

ฉันมีคอลัมน์ ID ในตารางนี้ดังนั้นฉันต้องการตรวจสอบการทำซ้ำสำหรับคอลัมน์ทั้งหมดยกเว้นคอลัมน์ ID นี้:

#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สตริง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.