ใส่ถ้าไม่ได้มีการอัพเดทหรือไม่


275

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

ฉันมีตารางที่กำหนดดังนี้:

CREATE TABLE Book 
ID     INTEGER PRIMARY KEY AUTOINCREMENT,
Name   VARCHAR(60) UNIQUE,
TypeID INTEGER,
Level  INTEGER,
Seen   INTEGER

สิ่งที่ฉันต้องการจะทำคือเพิ่มระเบียนที่มีชื่อเฉพาะ หากชื่อมีอยู่แล้วฉันต้องการแก้ไขฟิลด์

ใครช่วยเล่าเรื่องนี้ให้ฉันฟังได้ไหม


3
"insert or replace" แตกต่างอย่างสิ้นเชิงจาก "insert or update"
Fattie

คำตอบ:


320

มีลักษณะที่http://sqlite.org/lang_conflict.html

คุณต้องการสิ่งที่ชอบ:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values
((select ID from Book where Name = "SearchName"), "SearchName", ...);

โปรดทราบว่าเขตข้อมูลใด ๆ ที่ไม่อยู่ในรายการแทรกจะถูกตั้งค่าเป็น NULL หากแถวนั้นมีอยู่แล้วในตาราง นี่คือสาเหตุที่มีการเลือกย่อยสำหรับIDคอลัมน์: ในกรณีการแทนที่คำสั่งจะตั้งค่าเป็น NULL จากนั้น ID ใหม่จะถูกจัดสรร

วิธีการนี้ยังสามารถใช้ถ้าคุณต้องการที่จะปล่อยให้ค่าของเขตข้อมูลโดยเฉพาะอย่างยิ่งถ้าแถวในกรณีการเปลี่ยน แต่ตั้งค่าฟิลด์เป็น NULL ในกรณีที่แทรก

ตัวอย่างเช่นสมมติว่าคุณต้องการออกจากSeenคนเดียว:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values (
   (select ID from Book where Name = "SearchName"),
   "SearchName",
    5,
    6,
    (select Seen from Book where Name = "SearchName"));

109
"แทรกหรือแทนที่" ผิดนั้นแตกต่างจาก "แทรกหรืออัปเดต" สำหรับคำตอบที่ถูกต้องโปรดดูstackoverflow.com/questions/418898/…
rds

12
@rds ไม่ไม่ผิดเพราะคำถามนี้บอกว่า "แก้ไขฟิลด์" และคีย์หลักไม่ได้เป็นส่วนหนึ่งของรายการคอลัมน์ แต่ฟิลด์อื่นทั้งหมดเป็น หากคุณกำลังจะมีกรณีมุมที่คุณไม่ได้แทนที่ค่าของเขตข้อมูลทั้งหมดหรือถ้าคุณกำลังยุ่งกับคีย์หลักคุณควรจะทำสิ่งที่แตกต่างกัน หากคุณมีฟิลด์ใหม่ครบชุดแนวทางนี้ก็ใช้ได้ คุณมีปัญหาเฉพาะที่ฉันไม่เห็นหรือไม่?
Janm

9
ถูกต้องถ้าคุณรู้ค่าใหม่ทั้งหมดสำหรับฟิลด์ทั้งหมด หากผู้ใช้อัปเดตเท่านั้นพูดLevelวิธีนี้ไม่สามารถติดตามได้
rds

4
ถูกต้อง คำตอบอื่น ๆ เกี่ยวข้อง แต่วิธีนี้ใช้ได้สำหรับการอัปเดตทุกฟิลด์ (ไม่รวมคีย์)
ЯрославРахматуллин

2
ใช่มันผิดทั้งหมด จะเกิดอะไรขึ้นถ้าฉันต้องการอัปเดตค่าคอลัมน์เดี่ยวใน Confliction จากใหม่ ในกรณีข้างต้นข้อมูลอื่น ๆ ทั้งหมดจะถูกแทนที่ด้วยข้อมูลใหม่ที่ไม่ถูกต้อง
Mrug

85

คุณควรใช้INSERT OR IGNOREคำสั่งตามด้วยUPDATEคำสั่ง: ในตัวอย่างต่อไปนี้nameเป็นคีย์หลัก:

INSERT OR IGNORE INTO my_table (name, age) VALUES ('Karen', 34)
UPDATE my_table SET age = 34 WHERE name='Karen'

คำสั่งแรกจะแทรกบันทึก หากมีบันทึกอยู่มันจะเพิกเฉยต่อข้อผิดพลาดที่เกิดจากความขัดแย้งกับคีย์หลักที่มีอยู่

คำสั่งที่สองจะอัปเดตบันทึก (ซึ่งตอนนี้มีอยู่แน่นอน)


6
ช่วงเวลาไหนที่มันจะไม่สนใจ? เมื่อชื่อและอายุเหมือนกันทั้งคู่?
MOU

นี่ควรเป็นทางออก ... ถ้าคุณใช้ทริกเกอร์ในการแทรกคำตอบที่ยอมรับจะใช้ไฟทุกครั้ง สิ่งนี้ไม่ได้และดำเนินการอัปเดตเท่านั้น
PodTech.io

1
มันไม่สนใจชื่อเพียงอย่างเดียว โปรดจำไว้ว่าเฉพาะคอลัมน์ "ชื่อ" เท่านั้นที่เป็นคีย์หลัก
Gabriel Ferrer

3
เมื่อมีการบันทึกใหม่การอัพเดทนั้นไม่จำเป็น แต่จะต้องดำเนินการต่อไปซึ่งจะนำไปสู่ประสิทธิภาพที่ไม่ดี?
MarcG

วิธีรันคำสั่งที่เตรียมไว้สำหรับสิ่งนี้
Prashant

65

คุณต้องตั้งค่าข้อ จำกัด บนตารางเพื่อทริกเกอร์ " ข้อขัดแย้ง " ที่คุณจะแก้ไขด้วยการแทนที่:

CREATE TABLE data   (id INTEGER PRIMARY KEY, event_id INTEGER, track_id INTEGER, value REAL);
CREATE UNIQUE INDEX data_idx ON data(event_id, track_id);

จากนั้นคุณสามารถออก:

INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 2, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 5);

"SELECT * FROM data" จะให้คุณ:

2|2|2|3.0
3|1|2|5.0

โปรดทราบว่า data.id คือ "3" และไม่ใช่ "1" เนื่องจาก REPLACE ลบ DELETE และ INSERT ไม่ใช่ UPDATE นอกจากนี้ยังหมายความว่าคุณต้องให้แน่ใจว่าคุณกำหนดคอลัมน์ที่จำเป็นทั้งหมดหรือคุณจะได้รับค่า NULL ที่ไม่คาดคิด


33

อัปเดตเป็นครั้งแรก หากจำนวนแถวที่ได้รับผลกระทบ = 0 ให้ใส่เข้าไป ใช้ง่ายและเหมาะสำหรับทุกRDBMS


11
สองการดำเนินการไม่ควรมีปัญหากับการทำธุรกรรมในระดับการแยกที่ถูกต้องโดยไม่คำนึงถึงฐานข้อมูล
มกราคม

5
Insert or Replaceเป็นที่นิยมมากกว่าจริงๆ
MPelletier

1
ฉันหวังว่าเอกสารไอทีจะมีตัวอย่างเพิ่มเติม ฉันได้ลองด้านล่างแล้ว แต่ใช้งานไม่ได้ (ไวยากรณ์ของฉันผิดไปอย่างเห็นได้ชัด) มีความคิดเห็นเกี่ยวกับสิ่งที่ควรเป็นอย่างไร INSERT INTO Book (ชื่อ, TypeID, Level, Seen) VALUES ('Superman', '2', '14', '0') ON ความขัดแย้งแทนที่หนังสือ (ชื่อ, TypeID, Level, Seen) VALUES ('Superman', ' 2 ',' 14 ',' 0 ')
SparkyNZ

6
นอกจากนี้จะเกิดอะไรขึ้นถ้าค่าของแถวเหมือนกันทั้งหมดการนับแถวที่ได้รับผลกระทบจะเป็นศูนย์และจะสร้างแถวที่ซ้ำกันใหม่
barkside

2
+1 เนื่องจากINSERT หรือ REPLACEจะลบแถวเดิมเมื่อมีข้อขัดแย้งและหากคุณไม่ได้ตั้งค่าคอลัมน์ทั้งหมดคุณจะสูญเสียค่าเดิม
Pavel Machyniak

20

INSERT OR REPLACE จะแทนที่ฟิลด์อื่นเป็นค่าเริ่มต้น

sqlite> CREATE TABLE Book (
  ID     INTEGER PRIMARY KEY AUTOINCREMENT,
  Name   TEXT,
  TypeID INTEGER,
  Level  INTEGER,
  Seen   INTEGER
);

sqlite> INSERT INTO Book VALUES (1001, 'C++', 10, 10, 0);
sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR REPLACE INTO Book(ID, Name) VALUES(1001, 'SQLite');

sqlite> SELECT * FROM Book;
1001|SQLite|||

ถ้าคุณต้องการรักษาเขตข้อมูลอื่น

sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR IGNORE INTO Book(ID) VALUES(1001);
sqlite> UPDATE Book SET Name='SQLite' WHERE ID=1001;

sqlite> SELECT * FROM Book;
1001|SQLite|10|10|0

หรือใช้UPSERT (เพิ่มไวยากรณ์ใน SQLite ด้วยรุ่น 3.24.0 (2018-06-04))

INSERT INTO Book (ID, Name)
  VALUES (1001, 'SQLite')
  ON CONFLICT (ID) DO
  UPDATE SET Name=excluded.Name;

คำนำหน้าเท่ากับค่าในexcluded.VALUES


16

ความปั่นป่วนเป็นสิ่งที่คุณต้องการ UPSERTเพิ่มไวยากรณ์ใน SQLite ด้วยรุ่น 3.24.0 (2018-06-04)

CREATE TABLE phonebook2(
  name TEXT PRIMARY KEY,
  phonenumber TEXT,
  validDate DATE
);

INSERT INTO phonebook2(name,phonenumber,validDate)
  VALUES('Alice','704-555-1212','2018-05-08')
  ON CONFLICT(name) DO UPDATE SET
    phonenumber=excluded.phonenumber,
    validDate=excluded.validDate
  WHERE excluded.validDate>phonebook2.validDate;

ถูกเตือนว่า ณ จุดนี้คำว่า "UPSERT" ที่แท้จริงนั้นไม่ได้เป็นส่วนหนึ่งของไวยากรณ์ขั้นสูงสุด

ไวยากรณ์ที่ถูกต้องคือ

INSERT INTO ... ON CONFLICT(...) DO UPDATE SET...

และถ้าคุณINSERT INTO SELECT ...เลือกอย่างน้อยที่สุดก็WHERE trueต้องแก้ปัญหาความสับสนในตัวแยกวิเคราะห์เกี่ยวกับโทเค็นONด้วยไวยากรณ์การรวม

ได้รับการเตือนว่าINSERT OR REPLACE...จะลบบันทึกก่อนที่จะแทรกใหม่ถ้ามันจะต้องเปลี่ยนซึ่งอาจจะไม่ดีถ้าคุณมีน้ำตกที่สำคัญต่างประเทศหรือทริกเกอร์ลบอื่น ๆ


มันมีอยู่ในเอกสารประกอบและฉันมี sqlite เวอร์ชันล่าสุดซึ่งเป็น 3.25.1 แต่มันใช้ไม่ได้สำหรับฉัน
Bentaiba Miled Basma

คุณลองคำค้นหาทั้งสองด้านบนคำต่อคำหรือไม่
ComradeJoecool

โปรดทราบว่า Ubuntu 18.04 มาพร้อมกับ SQLite3 v3.22ไม่ใช่ 3.25 ดังนั้นจึงไม่รองรับUPSERTไวยากรณ์
starbeamrainbowlabs

ขอบคุณสำหรับรุ่นอ้างอิงในฟีเจอร์!
Matteo

6

หากคุณไม่มีคีย์หลักคุณสามารถแทรกหากไม่มีอยู่จากนั้นทำการอัปเดต ตารางต้องมีอย่างน้อยหนึ่งรายการก่อนใช้งาน

INSERT INTO Test 
   (id, name)
   SELECT 
      101 as id, 
      'Bob' as name
   FROM Test
       WHERE NOT EXISTS(SELECT * FROM Test WHERE id = 101 and name = 'Bob') LIMIT 1;

Update Test SET id='101' WHERE name='Bob';

นี่เป็นทางออกเดียวที่ทำงานได้โดยไม่ต้องสร้างรายการซ้ำ อาจเป็นเพราะตารางที่ฉันใช้ไม่มีคีย์หลักและมี 2 คอลัมน์ที่ไม่มีค่าเริ่มต้น แม้ว่าจะเป็นวิธีแก้ปัญหาที่ยืดยาว แต่ก็ทำงานได้อย่างถูกต้องและทำงานตามที่คาดไว้
Inbar Rose

4

ฉันเชื่อว่าคุณต้องการUPSERT Upsert

"INSERT หรือ REPLACE" ที่ไม่มีเล่ห์เหลี่ยมเพิ่มเติมในคำตอบนั้นจะรีเซ็ตฟิลด์ใด ๆ ที่คุณไม่ได้ระบุเป็นค่า NULL หรือค่าเริ่มต้นอื่น ๆ (พฤติกรรมของ INSERT หรือ REPLACE นี้แตกต่างจาก UPDATE เหมือนกับ INSERT เพราะจริง ๆ แล้วเป็น INSERT อย่างไรก็ตามถ้าสิ่งที่คุณต้องการคือ UPDATE ถ้ามีอยู่คุณอาจต้องการซีแมนติก UPDATE และจะประหลาดใจกับผลลัพธ์ที่เกิดขึ้นจริง)

กลอุบายจากการใช้ UPSERT ที่แนะนำนั้นโดยทั่วไปแล้วจะใช้ INSERT หรือ REPLACE แต่ระบุฟิลด์ทั้งหมดโดยใช้คำสั่ง SELECT ที่ฝังตัวเพื่อดึงค่าปัจจุบันสำหรับฟิลด์ที่คุณไม่ต้องการเปลี่ยน


1

ฉันคิดว่ามันมีค่าที่ชี้ให้เห็นว่าอาจมีพฤติกรรมที่ไม่คาดคิดที่นี่หากคุณไม่เข้าใจวิธีการที่คีย์หลักและUNIQUEโต้ตอบกัน

ตัวอย่างเช่นหากคุณต้องการแทรกระเบียนเฉพาะในกรณีที่ไม่มีการใช้ฟิลด์NAMEในขณะนี้และถ้าเป็นเช่นนั้นคุณต้องการให้ข้อยกเว้นข้อ จำกัด เพื่อบอกให้คุณทราบINSERT หรือ REPLACEจะไม่โยนและยกเว้นและแทนที่จะเป็น แก้ไขข้อ จำกัดUNIQUEด้วยการแทนที่ระเบียนที่ขัดแย้งกัน (ระเบียนที่มีอยู่ด้วยNAMEเดียวกัน) แกสปาร์ดแสดงให้เห็นอย่างนี้ในคำตอบของเขาด้านบน

หากคุณต้องการให้ข้อยกเว้นมีข้อผิดพลาดเกิดขึ้นคุณต้องใช้คำสั่งINSERTและใช้คำสั่งUPDATEแยกต่างหากเพื่ออัปเดตระเบียนเมื่อคุณรู้ว่าชื่อไม่ได้ถูกใช้

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