CHERTN NẾU KHÔNG EXISTS CẬP NHẬT ELSE?


275

Tôi đã tìm thấy một vài giải pháp "sẽ là" cho cổ điển "Làm cách nào để chèn bản ghi mới hoặc cập nhật bản ghi nếu nó đã tồn tại" nhưng tôi không thể làm cho bất kỳ giải pháp nào hoạt động trong SQLite.

Tôi có một bảng được định nghĩa như sau:

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

Những gì tôi muốn làm là thêm một bản ghi với một tên duy nhất. Nếu Tên đã tồn tại, tôi muốn sửa đổi các trường.

Ai đó có thể cho tôi biết làm thế nào để làm điều này xin vui lòng?


3
"Chèn hoặc thay thế" hoàn toàn khác với "chèn hoặc cập nhật"
Fattie

Câu trả lời:


320

Hãy xem http://sqlite.org/lang_conflict.html .

Bạn muốn một cái gì đó như:

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

Lưu ý rằng bất kỳ trường nào không có trong danh sách chèn sẽ được đặt thành NULL nếu hàng đã tồn tại trong bảng. Đây là lý do tại sao có một phần phụ cho IDcột: Trong trường hợp thay thế, câu lệnh sẽ đặt nó thành NULL và sau đó một ID mới sẽ được cấp.

Cách tiếp cận này cũng có thể được sử dụng nếu bạn muốn để riêng các giá trị trường cụ thể nếu hàng trong trường hợp thay thế nhưng đặt trường thành NULL trong trường hợp chèn.

Ví dụ: giả sử bạn muốn để lại Seenmột mình:

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
Sai "chèn hoặc thay thế" khác với "chèn hoặc cập nhật". Để có câu trả lời hợp lệ, hãy xem stackoverflow.com/questions/418898/ từ
rds

12
@rds Không, không sai vì câu hỏi này nói "sửa đổi các trường" và khóa chính không phải là một phần của danh sách cột, nhưng tất cả các trường khác là. Nếu bạn sẽ có các trường hợp góc mà bạn không thay thế tất cả các giá trị trường hoặc nếu bạn đang làm rối tung với khóa chính, bạn nên làm gì đó khác đi. Nếu bạn có một bộ hoàn chỉnh các lĩnh vực mới, phương pháp này rất tốt. Bạn có một vấn đề cụ thể tôi không thể nhìn thấy?
janm

9
Nó hợp lệ nếu bạn biết tất cả giá trị mới cho tất cả các trường. Nếu người dùng chỉ cập nhật, giả sử, Levelphương pháp này không thể được theo sau.
rds

4
Đúng . Câu trả lời khác có liên quan, nhưng cách tiếp cận này có giá trị để cập nhật tất cả các trường (không bao gồm khóa).
Ярослав Рахматуллин

2
Có Điều này hoàn toàn sai. Điều gì sẽ xảy ra nếu tôi chỉ muốn cập nhật giá trị cột đơn trên Xung đột từ cái mới. Trong trường hợp trên, tất cả các dữ liệu khác sẽ được thay thế bằng dữ liệu mới không chính xác.
Mrug

85

Bạn nên sử dụng INSERT OR IGNORElệnh theo sau bởi một UPDATElệnh: Trong ví dụ sau đây namelà khóa chính:

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

Lệnh đầu tiên sẽ chèn bản ghi. Nếu bản ghi tồn tại, nó sẽ bỏ qua lỗi gây ra bởi xung đột với khóa chính hiện có.

Lệnh thứ hai sẽ cập nhật bản ghi (hiện chắc chắn tồn tại)


6
tại thời điểm nào nó sẽ bỏ qua? Khi nào tên và tuổi đều giống nhau?
mou

Đây sẽ là giải pháp ... nếu bạn đang sử dụng bất kỳ trình kích hoạt nào khi chèn, câu trả lời được chấp nhận sẽ kích hoạt mỗi lần. Điều này không và chỉ thực hiện cập nhật
PodTech.io

1
Nó bỏ qua chỉ dựa trên tên. Hãy nhớ rằng chỉ có cột "tên" là khóa chính.
Gabriel Ferrer

3
Khi bản ghi mới, bản cập nhật không cần thiết nhưng dù sao cũng sẽ được thực hiện, dẫn đến hiệu suất kém?
MarcG

Làm thế nào để thực hiện một tuyên bố chuẩn bị cho điều này?
prashant

65

Bạn cần đặt một ràng buộc trên bảng để kích hoạt một " xung đột " mà sau đó bạn giải quyết bằng cách thay thế:

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

Sau đó, bạn có thể phát hành:

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

"CHỌN * TỪ dữ liệu" sẽ cung cấp cho bạn:

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

Lưu ý rằng data.id là "3" chứ không phải "1" vì REPLACE thực hiện XÓA và XÓA, không phải CẬP NHẬT. Điều này cũng có nghĩa là bạn phải đảm bảo rằng bạn xác định tất cả các cột cần thiết hoặc bạn sẽ nhận được các giá trị NULL bất ngờ.


33

Đầu tiên hãy cập nhật nó. Nếu số hàng bị ảnh hưởng = 0 thì chèn nó. Nó dễ nhất và phù hợp cho tất cả RDBMS .


11
Hai hoạt động sẽ không có vấn đề với một giao dịch ở mức cô lập phù hợp, bất kể cơ sở dữ liệu.
janm

5
Insert or Replacethực sự là thích hợp hơn
MPelletier

1
Tôi thực sự muốn tài liệu CNTT sẽ chứa nhiều ví dụ hơn. Tôi đã thử cách này dưới đây và nó không hoạt động (cú pháp của tôi rõ ràng là sai). Bất kỳ ý tưởng về những gì nó nên được? XÁC NHẬN VÀO Sách (Tên, TypeID, Cấp độ, Đã xem) GIÁ TRỊ ('Siêu nhân', '2', '14', '0') TRÊN CONFLICT Thay thế Sách (Tên, TypeID, Cấp độ, Đã xem) GIÁ TRỊ ('Siêu nhân', ' 2 ',' 14 ',' 0 ')
SparkyNZ

6
Ngoài ra, nếu giá trị hàng giống hệt nhau, thì số hàng bị ảnh hưởng sẽ bằng 0 và một hàng trùng lặp mới sẽ được tạo.
sủa

2
+1, vì CHERTN HOẶC THAY THẾ sẽ xóa hàng gốc khi xung đột và nếu bạn không đặt tất cả các cột, bạn sẽ mất các giá trị ban đầu
Pavel Machyniak

20

INSERT OR REPLACE sẽ thay thế các trường khác thành giá trị mặc định.

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

Nếu bạn muốn bảo tồn các lĩnh vực khác

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

hoặc sử dụng UPSERT (cú pháp đã được thêm vào SQLite với phiên bản 3.24.0 (2018-06-04))

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

Các excluded.tiền tố tương đương với giá trị trong VALUES.


16

Upsert là những gì bạn muốn. UPSERTcú pháp đã được thêm vào SQLite với phiên bản 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;

Được cảnh báo rằng tại thời điểm này, từ "UPSERT" thực sự không phải là một phần của cú pháp nâng cấp.

Cú pháp đúng là

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

và nếu bạn đang thực hiện INSERT INTO SELECT ...các nhu cầu chọn của mình ít nhất là WHERE trueđể giải quyết sự mơ hồ của trình phân tích cú pháp về mã thông báoON bằng cú pháp nối.

Được cảnh báo rằng INSERT OR REPLACE...sẽ xóa bản ghi trước khi chèn một bản ghi mới nếu nó phải thay thế, điều này có thể xấu nếu bạn có các tầng khóa ngoại hoặc các trình kích hoạt xóa khác.


Nó tồn tại trong tài liệu và tôi có phiên bản sqlite mới nhất là 3.25.1 nhưng nó không hoạt động với tôi
Bentaiba Miled Basma

Bạn đã thử hai truy vấn trên nguyên văn?
Đồng chíJoecool

Lưu ý rằng Ubuntu 18.04 đi kèm với SQLite3 v3.22 , không phải 3.25, vì vậy nó không hỗ trợ UPSERTcú pháp.
starbeamrainbowlabs

Cảm ơn các phiên bản tham khảo về tính năng!
Matteo

6

Nếu bạn không có khóa chính, Bạn có thể chèn nếu không tồn tại, sau đó thực hiện cập nhật. Bảng phải chứa ít nhất một mục trước khi sử dụng.

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

Đây là giải pháp duy nhất phù hợp với tôi mà không tạo các mục trùng lặp. Có lẽ vì bảng tôi đang sử dụng không có khóa chính và có 2 cột không có giá trị mặc định. Mặc dù đó là một chút giải pháp dài dòng, nó thực hiện công việc đúng cách và hoạt động như mong đợi.
Inbar Rose

4

Tôi tin rằng bạn muốn UPSERT .

"CHERTN HOẶC THAY THẾ" mà không có thủ thuật bổ sung trong câu trả lời đó sẽ đặt lại bất kỳ trường nào bạn không chỉ định thành NULL hoặc giá trị mặc định khác. (Hành vi này của INSERT HOẶC REPLACE không giống như CẬP NHẬT; nó chính xác giống như INSERT, bởi vì nó thực sự là INSERT; tuy nhiên nếu điều bạn muốn là CẬP NHẬT thì nếu bạn tồn tại có thể muốn ngữ nghĩa CẬP NHẬT và sẽ ngạc nhiên về kết quả thực tế.)

Thủ thuật từ việc triển khai UPSERT được đề xuất về cơ bản là sử dụng CHERTN HOẶC THAY THẾ, nhưng chỉ định tất cả các trường, sử dụng mệnh đề CHỌN được nhúng để truy xuất giá trị hiện tại cho các trường bạn không muốn thay đổi.


1

Tôi nghĩ rằng đáng để chỉ ra rằng có thể có một số hành vi không mong muốn ở đây nếu bạn không hiểu thấu đáo cách thức PRIMARY KEYUNIQUE tương tác.

Ví dụ: nếu bạn chỉ muốn chèn một bản ghi nếu trường NAME hiện không được thực hiện và nếu có, bạn muốn có một ngoại lệ ràng buộc để bắn cho bạn biết, thì INSERT HOẶC REPLACE sẽ không ném và ngoại lệ và thay vào đó sẽ tự giải quyết các ràng buộc ĐỘC ĐÁO bằng cách thay thế bản ghi xung đột (bản ghi hiện có cùng TÊN ). Gaspard đã chứng minh điều này thực sự tốt trong câu trả lời của ông ở trên.

Nếu bạn muốn có một ngoại lệ ràng buộc để kích hoạt, bạn phải sử dụng câu lệnh INSERT và dựa vào lệnh UPDATE riêng để cập nhật bản ghi khi bạn biết tên không được sử dụng.

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.