Làm cách nào để CẬP NHẬT một hàng trong bảng hoặc CHÈN hàng nếu nó không tồn tại?


83

Tôi có bảng quầy sau:

CREATE TABLE cache (
    key text PRIMARY KEY,
    generation int
);

Tôi muốn tăng một trong các bộ đếm hoặc đặt nó thành 0 nếu hàng tương ứng chưa tồn tại. Có cách nào để làm điều này mà không gặp vấn đề đồng thời trong SQL chuẩn không? Hoạt động đôi khi là một phần của giao dịch, đôi khi tách biệt.

SQL phải chạy không sửa đổi trên SQLite, PostgreSQL và MySQL, nếu có thể.

Một tìm kiếm mang lại một số ý tưởng hoặc gặp phải các vấn đề đồng thời hoặc cụ thể đối với cơ sở dữ liệu:

  • Hãy thử INSERTmột hàng mới và UPDATEnếu có lỗi. Rất tiếc, lỗi khi INSERThủy giao dịch hiện tại.

  • UPDATEhàng và nếu không có hàng nào được sửa đổi, thì INSERTmột hàng mới.

  • MySQL có một ON DUPLICATE KEY UPDATEmệnh đề.

CHỈNH SỬA: Cảm ơn vì tất cả các câu trả lời tuyệt vời. Có vẻ như Paul đã đúng, và không có một cách di động nào để làm điều này. Điều đó khá ngạc nhiên đối với tôi, vì nó có vẻ giống như một hoạt động rất cơ bản.


6
Bạn sẽ không tìm thấy một giải pháp duy nhất hoạt động cho tất cả các RDBMS này. Lấy làm tiếc.
Paul Tomblin


Câu trả lời:


137

MySQL (và sau đó là SQLite) cũng hỗ trợ cú pháp REPLACE INTO:

REPLACE INTO my_table (pk_id, col1) VALUES (5, '123');

Thao tác này tự động xác định khóa chính và tìm một hàng phù hợp để cập nhật, chèn một hàng mới nếu không tìm thấy.


13
Trên thực tế, để cụ thể, REPLACE của MySQL luôn thực hiện chèn, nhưng nó sẽ xóa hàng nếu nó đã tồn tại, trước tiên. dev.mysql.com/doc/refman/4.1/en/replace.html
Evan

90
Điều quan trọng là phải hiểu rằng đó là chèn + xóa và không bao giờ và cập nhật. Hệ quả của điều này là bạn sẽ luôn muốn đảm bảo rằng khi thực hiện thay thế, bạn phải luôn bao gồm dữ liệu cho tất cả các trường.
Zoredache

2
@Zoredache Về mặt kỹ thuật, đó là một 'xóa, sau đó chèn', vì một (n) 'chèn + xóa' thực sự giống như một thao tác xóa nhưng điều đó đang chia cắt.
Agi Hammerthief

2
@Agihammerthief có một sự khác biệt rất thực tế là hàng mới được chèn sẽ KHÔNG có cùng khóa chính với hàng đã bị xóa. Với ON DUPLICATE, nó sẽ là cùng một khóa chính (trừ khi bạn thay đổi cụ thể nó).
Tim Strijdhorst 20/12/16

32

SQLite hỗ trợ thay thế một hàng nếu nó đã tồn tại:

INSERT OR REPLACE INTO [...blah...]

Bạn có thể rút ngắn điều này thành

REPLACE INTO [...blah...]

Lối tắt này đã được thêm vào để tương thích với REPLACE INTObiểu thức MySQL .


Nó yêu cầu bạn xác định a PRAMARY KEYtrong các giá trị của mình.
DawnSong

24

Tôi sẽ làm một cái gì đó như sau:

INSERT INTO cache VALUES (key, generation)
ON DUPLICATE KEY UPDATE (key = key, generation = generation + 1);

Đặt giá trị tạo thành 0 trong mã hoặc trong sql nhưng sử dụng ON DUP ... để tăng giá trị. Tôi nghĩ đó là cú pháp dù sao.


1
Câu trả lời này thành thật phải là câu trả lời được chọn. Đó là một chỉnh sửa không phá hủy nếu bạn không gửi tất cả các trường cho một mục nhập.
Jordan

9

mệnh đề ON DUPLICATE KEY UPDATE là giải pháp tốt nhất vì: REPLACE thực hiện XÓA, theo sau là CHÈN, do đó, trong một khoảng thời gian ngắn, bản ghi sẽ bị xóa, tạo ra khả năng rất nhỏ là một truy vấn có thể quay lại sau khi bỏ qua điều đó nếu trang đó được xem trong truy vấn REPLACE.

Tôi thích INSERT ... ON DUPLICATE UPDATE ... vì lý do đó.

giải pháp của jmoz là tốt nhất: mặc dù tôi thích cú pháp SET hơn là dấu ngoặc đơn

INSERT INTO cache 
SET key = 'key', generation = 'generation'
ON DUPLICATE KEY 
UPDATE key = 'key', generation = (generation + 1)
;

4
REPLACE là nguyên tử, vì vậy không có dấu chấm nào mà hàng không tồn tại.
Brilliand


5

Trong PostgreSQL không có lệnh hợp nhất, và thực sự viết nó không phải là tầm thường - thực sự có những trường hợp cạnh kỳ lạ khiến tác vụ trở nên "thú vị".

Cách tiếp cận tốt nhất (như trong: làm việc trong điều kiện có thể nhất), là sử dụng hàm - chẳng hạn như hàm được hiển thị trong thủ công (merge_db).

Nếu bạn không muốn sử dụng chức năng, bạn thường có thể bỏ qua:

updated = db.execute(UPDATE ... RETURNING 1)
if (!updated)
  db.execute(INSERT...)

Chỉ cần nhớ rằng nó không phải là bằng chứng lỗi và cuối cùng nó sẽ thất bại.


4

SQL chuẩn cung cấp câu lệnh MERGE cho tác vụ này. Không phải tất cả DBMS đều hỗ trợ câu lệnh MERGE.


0

Nếu bạn không có cách phổ biến để cập nhật hoặc chèn nguyên tử (ví dụ: thông qua một giao dịch) thì bạn có thể dự phòng cho một sơ đồ khóa khác. Tệp 0 byte, mutex hệ thống, đường dẫn có tên, v.v.


0

Bạn có thể sử dụng một trình kích hoạt chèn? Nếu nó không thành công, hãy cập nhật.


Kích hoạt (ít nhất là trong PostgreSQL) đang chạy khi lệnh hoạt động. tức là bạn không thể có trình kích hoạt chạy khi lệnh cơ sở không thành công.

0

Nếu bạn đồng ý với việc sử dụng một thư viện viết SQL cho bạn, thì bạn có thể sử dụng Upsert (hiện chỉ có Ruby và Python):

Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
Pet.upsert({:name => 'Jerry'}, :color => 'brown')

Điều đó hoạt động trên MySQL, Postgres và SQLite3.

Nó viết một thủ tục được lưu trữ hoặc hàm do người dùng định nghĩa (UDF) trong MySQL và Postgres. Nó sử dụng INSERT OR REPLACEtrong SQLite3.

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.