SQLite - UPSERT * không * INSERT hoặc REPLACE


535

http://en.wikipedia.org/wiki/Upsert

Chèn Cập nhật Proc được lưu trữ trên SQL Server

Có một số cách thông minh để làm điều này trong SQLite mà tôi chưa nghĩ đến?

Về cơ bản tôi muốn cập nhật ba trong bốn cột nếu bản ghi tồn tại, Nếu nó không tồn tại tôi muốn XÁC NHẬN bản ghi với giá trị mặc định (NUL) cho cột thứ tư.

ID là khóa chính vì vậy sẽ chỉ có một bản ghi cho UPSERT.

(Tôi đang cố gắng tránh chi phí hoạt động của CHỌN để xác định xem tôi có cần CẬP NHẬT hay CHERTN rõ ràng không)

Gợi ý?


Tôi không thể xác nhận rằng Cú pháp trên trang SQLite cho TABLE CREATE. Tôi chưa xây dựng bản demo để kiểm tra, nhưng có vẻ như nó không được hỗ trợ ..

Nếu có, tôi có ba cột để nó thực sự trông như sau:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    Blob1 BLOB ON CONFLICT REPLACE, 
    Blob2 BLOB ON CONFLICT REPLACE, 
    Blob3 BLOB 
);

nhưng hai đốm màu đầu tiên sẽ không gây ra xung đột, chỉ có ID nên tôi sẽ thay thế Blob1 và Blob2 (như mong muốn)


CẬP NHẬT trong SQLite khi dữ liệu ràng buộc là một giao dịch hoàn chỉnh, có nghĩa là mỗi hàng đã gửi được cập nhật yêu cầu: Chuẩn bị / Bind / Step / Hoàn thiện các câu lệnh không giống như INSERT cho phép sử dụng chức năng đặt lại

Cuộc sống của một đối tượng tuyên bố đi như thế này:

  1. Tạo đối tượng bằng sqlite3_prepare_v2 ()
  2. Ràng buộc các giá trị để lưu trữ các tham số bằng các giao diện sqlite3_bind_.
  3. Chạy SQL bằng cách gọi sqlite3_step ()
  4. Đặt lại câu lệnh bằng sqlite3_reset () sau đó quay lại bước 2 và lặp lại.
  5. Tiêu diệt đối tượng câu lệnh bằng sqlite3_finalize ().

CẬP NHẬT Tôi đoán là chậm so với INSERT, nhưng làm thế nào để so sánh với CHỌN bằng cách sử dụng khóa Chính?

Có lẽ tôi nên sử dụng lựa chọn để đọc cột thứ 4 (Blob3) và sau đó sử dụng REPLACE để viết một bản ghi mới pha trộn Cột thứ 4 ban đầu với dữ liệu mới cho 3 cột đầu tiên?


5
SQLite - UPSERT có sẵn trong bản phát hành trước tham khảo: sqlite.1065341.n5.nabble.com/ từ
gaurav

3
UPSERT có sẵn trong phiên bản 3.24.0 của SQLite
pablo_worker

Câu trả lời:


854

Giả sử ba cột trong bảng: ID, NAME, ROLE


BAD: Điều này sẽ chèn hoặc thay thế tất cả các cột bằng các giá trị mới cho ID = 1:

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (1, 'John Foo', 'CEO');

BAD: Điều này sẽ chèn hoặc thay thế 2 trong số các cột ... cột NAME sẽ được đặt thành NULL hoặc giá trị mặc định:

INSERT OR REPLACE INTO Employee (id, role) 
  VALUES (1, 'code monkey');

TỐT : Sử dụng SQLite Trên điều khoản xung đột Hỗ trợ UPSERT trong SQLite! Cú pháp UPSERT đã được thêm vào SQLite với phiên bản 3.24.0!

UPSERT là một bổ sung cú pháp đặc biệt cho INSERT, làm cho INSERT hoạt động như một CẬP NHẬT hoặc không hoạt động nếu INSERT vi phạm một ràng buộc duy nhất. UPSERT không phải là SQL chuẩn. UPSERT trong SQLite tuân theo cú pháp được thiết lập bởi PostgreSQL.

nhập mô tả hình ảnh ở đây

TỐT nhưng có xu hướng: Điều này sẽ cập nhật 2 trong số các cột. Khi ID = 1 tồn tại, NAME sẽ không bị ảnh hưởng. Khi ID = 1 không tồn tại, tên sẽ là mặc định (NULL).

INSERT OR REPLACE INTO Employee (id, role, name) 
  VALUES (  1, 
            'code monkey',
            (SELECT name FROM Employee WHERE id = 1)
          );

Điều này sẽ cập nhật 2 trong số các cột. Khi ID = 1 tồn tại, ROLE sẽ không bị ảnh hưởng. Khi ID = 1 không tồn tại, vai trò sẽ được đặt thành 'Băng ghế dự bị' thay vì giá trị mặc định.

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (  1, 
            'Susan Bar',
            COALESCE((SELECT role FROM Employee WHERE id = 1), 'Benchwarmer')
          );

33
+1 xuất sắc! Mệnh đề chọn được nhúng cho phép bạn linh hoạt ghi đè chức năng ON CONFLICT REPLACE mặc định nếu bạn cần kết hợp / so sánh giá trị cũ và giá trị mới cho bất kỳ trường nào.
G__

24
Nếu Nhân viên được tham chiếu bởi các hàng khác có xóa tầng, thì các hàng khác sẽ vẫn bị xóa bằng cách thay thế.
Don Reba

246
Điều này đau. SQlite cần UPSERT.
andig

21
Bạn có thể giải thích lý do tại sao Điều này sẽ chèn hoặc thay thế tất cả các cột bằng các giá trị mới cho ID = 1: được coi là BAD trong ví dụ đầu tiên của bạn không? Lệnh bạn trình bày có nghĩa là tạo một bản ghi mới với ID 1, tên John Foo và vai trò CEO , hoặc ghi đè lên bản ghi có ID là 1, nếu nó đã có, với dữ liệu đó (giả sử id là khóa chính) . Vì vậy, tại sao nó là xấu nếu chính xác điều đó xảy ra?
HOẶC Mapper

10
@Cornelius: Điều đó rõ ràng, nhưng đó không phải là điều xảy ra trong ví dụ đầu tiên. Ví dụ đầu tiên có nghĩa là buộc tất cả các cột, đó chính xác là những gì xảy ra, bất kể bản ghi được chèn hay thay thế. Vì vậy, tại sao điều đó được coi là xấu? Câu trả lời được liên kết cũng chỉ chỉ ra tại sao điều gì đó xấu có thể xảy ra khi chỉ định một tập hợp con của các cột, như trong ví dụ thứ hai của bạn ; nó dường như không giải thích bất kỳ tác động xấu nào của những gì xảy ra trong ví dụ đầu tiên của bạn , INSERT OR REPLACEtrong khi chỉ định các giá trị cho tất cả các cột.
HOẶC Mapper

134

CHERTN HOẶC THAY THẾ KHÔNG tương đương với "UPSERT".

Giả sử tôi có bảng Nhân viên với các trường id, tên và vai trò:

INSERT OR REPLACE INTO Employee ("id", "name", "role") VALUES (1, "John Foo", "CEO")
INSERT OR REPLACE INTO Employee ("id", "role") VALUES (1, "code monkey")

Bùng nổ, bạn đã mất tên của nhân viên số 1. SQLite đã thay thế nó bằng một giá trị mặc định.

Đầu ra dự kiến ​​của một UPSERT sẽ là thay đổi vai trò và giữ nguyên tên.


21
-1 từ tôi tôi sợ. Có, câu trả lời được chấp nhận là sai, nhưng trong khi câu trả lời của bạn chỉ ra vấn đề thì đó cũng không phải là câu trả lời. Để biết câu trả lời thực tế, hãy xem giải pháp thông minh của Eric B bằng cách sử dụng coalesce((select ..), 'new value')mệnh đề nhúng . Câu trả lời của Eric cần nhiều phiếu hơn ở đây tôi nghĩ.
Ngày

22
Thật. Eric chắc chắn là câu trả lời tốt nhất và xứng đáng được nhiều phiếu hơn. Điều đó đang được nói, tôi nghĩ rằng bằng cách chỉ ra vấn đề, tôi đã đóng góp một chút để tìm ra câu trả lời hay (câu trả lời của Eric đến sau và dựa trên các bảng sql mẫu trong câu trả lời của tôi). Vì vậy, không chắc chắn nếu tôi xứng đáng với -1, nhưng
đừng bận tâm

6
+1 để bù -1 ở trên. cười lớn. Dòng thời gian thú vị trên chủ đề này mặc dù. Rõ ràng câu trả lời của bạn đã hơn một tuần trước Eric, nhưng cả hai bạn đã trả lời câu hỏi gần hai năm sau khi được hỏi. +1 cho Eric quá mặc dù để xây dựng.
Giàu


2
@QED không, vì xóa + insert (là thay thế) là 2 câu lệnh dml, với các kích hoạt riêng của chúng chẳng hạn. Nó không giống như 1 tuyên bố cập nhật.
Sabas

109

Câu trả lời của Eric B là OK nếu bạn muốn giữ lại chỉ một hoặc có thể hai cột từ hàng hiện có. Nếu bạn muốn bảo tồn nhiều cột, nó sẽ quá nhanh.

Đây là một cách tiếp cận sẽ mở rộng tốt cho bất kỳ số lượng cột nào ở hai bên. Để minh họa nó tôi sẽ giả sử lược đồ sau:

 CREATE TABLE page (
     id      INTEGER PRIMARY KEY,
     name    TEXT UNIQUE,
     title   TEXT,
     content TEXT,
     author  INTEGER NOT NULL REFERENCES user (id),
     ts      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 );

Đặc biệt lưu ý đó namelà khóa tự nhiên của hàng - idchỉ được sử dụng cho các khóa ngoại, vì vậy điểm cần dành cho SQLite là tự chọn giá trị ID khi chèn một hàng mới. Nhưng khi cập nhật một hàng hiện có dựa trên hàng đó name, tôi muốn nó tiếp tục có giá trị ID cũ (rõ ràng!).

Tôi đạt được một sự thật UPSERTvới cấu trúc sau:

 WITH new (name, title, author) AS ( VALUES('about', 'About this site', 42) )
 INSERT OR REPLACE INTO page (id, name, title, content, author)
 SELECT old.id, new.name, new.title, old.content, new.author
 FROM new LEFT JOIN page AS old ON new.name = old.name;

Các hình thức chính xác của truy vấn này có thể thay đổi một chút. Chìa khóa là việc sử dụng INSERT SELECTvới một phép nối ngoài bên trái, để nối một hàng hiện có với các giá trị mới.

Ở đây, nếu một hàng không tồn tại trước đó, old.idsẽ có NULLvà SQLite sẽ tự động gán ID, nhưng nếu đã có một hàng như vậy, old.idsẽ có một giá trị thực và điều này sẽ được sử dụng lại. Đó chính xác là những gì tôi muốn.

Trong thực tế điều này là rất linh hoạt. Lưu ý cách tscột bị thiếu hoàn toàn ở tất cả các mặt - vì nó có DEFAULTgiá trị, SQLite sẽ chỉ làm đúng trong mọi trường hợp, vì vậy tôi không phải tự chăm sóc nó.

Bạn cũng có thể bao gồm một cột trên cả newoldhai bên và sau đó sử dụng ví dụ COALESCE(new.content, old.content)trong ngoài SELECTđể nói “chèn nội dung mới nếu có bất kỳ, nếu không giữ cho nội dung cũ” - ví dụ như nếu bạn đang sử dụng một truy vấn cố định và ràng buộc mới giá trị với giữ chỗ.


13
+1, hoạt động rất tốt, nhưng thêm một WHERE name = "about"ràng buộc vào SELECT ... AS oldđể tăng tốc mọi thứ. Nếu bạn có hàng 1m +, điều này rất chậm.

Điểm tốt, +1 trên bình luận của bạn. Mặc dù vậy, tôi sẽ loại bỏ câu trả lời đó, bởi vì việc thêm một WHEREmệnh đề như vậy chỉ đòi hỏi loại dư thừa trong truy vấn mà tôi đang cố gắng bỏ qua ngay từ đầu khi tôi đưa ra phương pháp này. Như mọi khi: khi bạn cần hiệu suất, không chuẩn hóa - cấu trúc của truy vấn, trong trường hợp này.
Aristotle Pagaltzis

4
Bạn có thể đơn giản hóa ví dụ của aristotle xuống đây, nếu bạn muốn: INSERT OR REPLACE INTO page (id, name, title, content, author) SELECT id, 'about', 'About this site', content, 42 FROM ( SELECT NULL ) LEFT JOIN ( SELECT * FROM page WHERE name = 'about' )
jcox

4
Điều này sẽ không kích hoạt không cần thiết kích ON DELETEhoạt khi nó thực hiện thay thế (đó là một bản cập nhật)?
Karakuri

3
Nó chắc chắn sẽ kích hoạt kích ON DELETEhoạt. Dunno về không cần thiết. Đối với hầu hết người dùng, nó có thể không cần thiết, thậm chí không mong muốn, nhưng có thể không phải cho tất cả người dùng. Tương tự như vậy đối với thực tế là nó cũng sẽ xếp tầng - xóa bất kỳ hàng nào có khóa ngoại thành hàng được đề cập - có thể là một vấn đề đối với nhiều người dùng. Thật không may, SQLite không có gì gần với một UPSERT thực sự, thật không may. (Tôi tiết kiệm cho việc giả mạo nó bằng một INSTEAD OF UPDATEkích hoạt, tôi đoán vậy.)
Aristotle Pagaltzis

86

Nếu bạn thường làm cập nhật, tôi sẽ ..

  1. Bắt đầu giao dịch
  2. Làm cập nhật
  3. Kiểm tra số lượng hàng
  4. Nếu là 0, hãy chèn
  5. Cam kết

Nếu bạn thường làm chèn tôi sẽ

  1. Bắt đầu giao dịch
  2. Hãy thử chèn
  3. Kiểm tra lỗi vi phạm khóa chính
  4. Nếu chúng tôi gặp lỗi, hãy cập nhật
  5. Cam kết

Bằng cách này, bạn tránh được lựa chọn và bạn đang giao dịch âm thanh trên Sqlite.


Cảm ơn, chỉ cần hỏi cái này @ stackoverflow.com/questions/2717590/ . +1 từ tôi! =)
Alix Axel

4
Nếu bạn sẽ kiểm tra số lượng hàng bằng cách sử dụng sqlite3_changes () ở bước thứ 3, hãy đảm bảo bạn không sử dụng tay cầm DB từ nhiều luồng để sửa đổi.
Linulin

3
Sẽ không có nhiều từ sau với hiệu ứng tương tự: 1) chọn bảng biểu mẫu id trong đó id = 'x' 2) if (resultset.rows.length == 0) bảng cập nhật trong đó id = 'x';
Florin

20
Tôi thực sự muốn CHỨNG MINH HOẶC CẬP NHẬT là một phần của ngôn ngữ
Florin

Điều này cuối cùng đã làm việc tốt nhất đối với tôi, cố gắng thực hiện REPLACE INTO hoặc luôn luôn và chèn / cập nhật đã giết chết hiệu suất của tôi khi tôi chủ yếu thực hiện cập nhật thay vì chèn.
PherricOxide

83

Câu trả lời này đã được cập nhật và vì vậy các ý kiến ​​dưới đây không còn được áp dụng.

2018-05-18 DỪNG BÁO CHÍ.

Hỗ trợ UPSERT trong SQLite! Cú pháp UPSERT đã được thêm vào SQLite với phiên bản 3.24.0 (đang chờ xử lý)!

UPSERT là một bổ sung cú pháp đặc biệt cho INSERT, làm cho INSERT hoạt động như một CẬP NHẬT hoặc không hoạt động nếu INSERT vi phạm một ràng buộc duy nhất. UPSERT không phải là SQL chuẩn. UPSERT trong SQLite tuân theo cú pháp được thiết lập bởi PostgreSQL.

nhập mô tả hình ảnh ở đây

cách khác:

Một cách hoàn toàn khác để thực hiện việc này là: Trong ứng dụng của tôi, tôi đặt hàng trong bộ nhớ của mình là long.MaxValue khi tôi tạo hàng trong bộ nhớ. (MaxValue sẽ không bao giờ được sử dụng làm ID mà bạn sẽ không tồn tại đủ lâu .... Sau đó, nếu rowID không phải là giá trị đó thì nó phải có trong cơ sở dữ liệu vì vậy cần CẬP NHẬT nếu đó là MaxValue thì nó cần chèn. Điều này chỉ hữu ích nếu bạn có thể theo dõi các rowID trong ứng dụng của mình.


5
Amen. Đơn giản là tốt hơn phức tạp. Điều này cảm thấy đơn giản hơn một chút so với câu trả lời được chấp nhận.
kkurian

4
Tôi nghĩ bạn không thể XÁC NHẬN VÀO ... Ở đâu trong sqlite? Đây là lỗi cú pháp đối với tôi trong sqlite3
Charlie Martin

5
@CharlieMartin là chính xác. Cú pháp này không hợp lệ đối với SQLite - đó là những gì OP yêu cầu. Một WHEREmệnh đề không thể được thêm vào một INSERTtuyên bố: sqlite-insert ...
colm.anseo

4
Câu trả lời này đã khiến tôi mất rất nhiều thời gian, câu hỏi là về SQLITE và tôi không biết rằng INSERT WHERE không được hỗ trợ trong sqite, vui lòng thêm ghi chú này rằng nó không hợp lệ cho sqlite trong câu trả lời của bạn
Miquel

3
@colminator và những người khác: Tôi đã sửa câu lệnh SQL của anh ấy bằng đề xuất của bạn.
F Lekschas

62

Tôi nhận ra đây là một chủ đề cũ nhưng tôi đã làm việc trong sqlite3 vào cuối và đã đưa ra phương pháp này phù hợp hơn với nhu cầu của tôi về việc tạo các truy vấn tham số động:

insert or ignore into <table>(<primaryKey>, <column1>, <column2>, ...) values(<primaryKeyValue>, <value1>, <value2>, ...); 
update <table> set <column1>=<value1>, <column2>=<value2>, ... where changes()=0 and <primaryKey>=<primaryKeyValue>; 

Vẫn còn 2 truy vấn với mệnh đề where trên bản cập nhật nhưng dường như thực hiện thủ thuật. Tôi cũng có tầm nhìn này trong đầu rằng sqlite có thể tối ưu hóa hoàn toàn câu lệnh cập nhật nếu lệnh gọi thay đổi () lớn hơn 0. Có hay không điều đó thực sự nằm ngoài tầm hiểu biết của tôi, nhưng một người đàn ông có thể mơ không anh? ;)

Đối với các điểm thưởng, bạn có thể nối thêm dòng này sẽ trả về cho bạn id của hàng cho dù đó là hàng mới được chèn hoặc hàng hiện có.

select case changes() WHEN 0 THEN last_insert_rowid() else <primaryKeyValue> end;

+ 1. Chính xác những gì tôi đã cố gắng thay thế thực hiện chuyển đổi từ một số mã TSQL của SQL Server. Cảm ơn. SQL tôi đã cố gắng thay thế giống nhưUpdate <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
DarrenMB

14

Đây là một giải pháp thực sự là một UPSERT (CẬP NHẬT hoặc CHERTN) thay vì CHERTN HOẶC THAY THẾ (hoạt động khác nhau trong nhiều tình huống).

Nó hoạt động như thế này:
1. Cố gắng cập nhật nếu một bản ghi có cùng Id tồn tại.
2. Nếu bản cập nhật không thay đổi bất kỳ hàng nào ( NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)), thì hãy chèn bản ghi.

Vì vậy, một bản ghi hiện có đã được cập nhật hoặc chèn sẽ được thực hiện.

Chi tiết quan trọng là sử dụng hàm SQL thay đổi () để kiểm tra xem câu lệnh cập nhật có chạm vào bất kỳ bản ghi hiện có nào không và chỉ thực hiện câu lệnh chèn nếu nó không đạt bất kỳ bản ghi nào.

Một điều cần đề cập là hàm thay đổi () không trả về các thay đổi được thực hiện bởi các trình kích hoạt cấp thấp hơn (xem http://sqlite.org/lang_corefunc.html#changes ), vì vậy hãy đảm bảo tính đến điều đó.

Đây là SQL ...

Cập nhật kiểm tra:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 2;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 2, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

Kiểm tra chèn:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 3;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 3, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

2
Đây dường như là giải pháp tốt hơn cho tôi so với Eric. Tuy nhiên INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;cũng nên làm việc.
bkausbk 2/2/2015

Cảm ơn bạn, nó cũng hoạt động trong WebQuery (sử dụng plugin Cordova và SQLite)
Zappescu

11

Bắt đầu với phiên bản 3.24.0 UPSERT được SQLite hỗ trợ.

Từ tài liệu :

UPSERT là một bổ sung cú pháp đặc biệt cho INSERT, làm cho INSERT hoạt động như một CẬP NHẬT hoặc không hoạt động nếu INSERT vi phạm một ràng buộc duy nhất. UPSERT không phải là SQL chuẩn. UPSERT trong SQLite tuân theo cú pháp được thiết lập bởi PostgreSQL. Cú pháp UPSERT đã được thêm vào SQLite với phiên bản 3.24.0 (đang chờ xử lý).

Một UPSERT là một câu lệnh INSERT thông thường được theo sau bởi mệnh đề ON CONFLICT đặc biệt

nhập mô tả hình ảnh ở đây

Nguồn hình ảnh: https://www.sqlite.org/images/syntax/upsert-clause.gif


8

Bạn thực sự có thể làm một nâng cấp trong SQLite, nó chỉ trông hơi khác so với bạn đã quen. Nó sẽ trông giống như:

INSERT INTO table name (column1, column2) 
VALUES ("value12", "value2") WHERE id = 123 
ON CONFLICT DO UPDATE 
SET column1 = "value1", column2 = "value2" WHERE id = 123

5

Mở rộng dựa trên câu trả lời của Aristotle, bạn có thể CHỌN từ bảng 'singleton' giả (một bảng sáng tạo của riêng bạn với một hàng duy nhất). Điều này tránh một số trùng lặp.

Tôi cũng đã giữ ví dụ di động trên MySQL và SQLite và sử dụng cột 'date_added' làm ví dụ về cách bạn có thể đặt cột chỉ lần đầu tiên.

 REPLACE INTO page (
   id,
   name,
   title,
   content,
   author,
   date_added)
 SELECT
   old.id,
   "about",
   "About this site",
   old.content,
   42,
   IFNULL(old.date_added,"21/05/2013")
 FROM singleton
 LEFT JOIN page AS old ON old.name = "about";

4

Cách tiếp cận tốt nhất mà tôi biết là thực hiện cập nhật, tiếp theo là chèn. "Chi phí của một lựa chọn" là cần thiết, nhưng nó không phải là một gánh nặng khủng khiếp vì bạn đang tìm kiếm trên khóa chính, rất nhanh.

Bạn sẽ có thể sửa đổi các câu lệnh dưới đây với tên bảng & trường của bạn để làm những gì bạn muốn.

--first, update any matches
UPDATE DESTINATION_TABLE DT
SET
  MY_FIELD1 = (
              SELECT MY_FIELD1
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
 ,MY_FIELD2 = (
              SELECT MY_FIELD2
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
WHERE EXISTS(
            SELECT ST2.PRIMARY_KEY
            FROM
              SOURCE_TABLE ST2
             ,DESTINATION_TABLE DT2
            WHERE ST2.PRIMARY_KEY = DT2.PRIMARY_KEY
            );

--second, insert any non-matches
INSERT INTO DESTINATION_TABLE(
  MY_FIELD1
 ,MY_FIELD2
)
SELECT
  ST.MY_FIELD1
 ,NULL AS MY_FIELD2  --insert NULL into this field
FROM
  SOURCE_TABLE ST
WHERE NOT EXISTS(
                SELECT DT2.PRIMARY_KEY
                FROM DESTINATION_TABLE DT2
                WHERE DT2.PRIMARY_KEY = ST.PRIMARY_KEY
                );

7
Cách phức tạp.
frast

Tôi nghĩ rằng đó không phải là một ý tưởng tốt bởi vì bạn cần phải thực hiện hai lần yêu cầu cơ sở dữ liệu.
Ricardo da Rocha Vitor

3

Nếu ai đó muốn đọc giải pháp của tôi cho SQLite trong Cordova, tôi đã có phương pháp js chung này nhờ câu trả lời @david ở trên.

function    addOrUpdateRecords(tableName, values, callback) {
get_columnNames(tableName, function (data) {
    var columnNames = data;
    myDb.transaction(function (transaction) {
        var query_update = "";
        var query_insert = "";
        var update_string = "UPDATE " + tableName + " SET ";
        var insert_string = "INSERT INTO " + tableName + " SELECT ";
        myDb.transaction(function (transaction) {
            // Data from the array [[data1, ... datan],[()],[()]...]:
            $.each(values, function (index1, value1) {
                var sel_str = "";
                var upd_str = "";
                var remoteid = "";
                $.each(value1, function (index2, value2) {
                    if (index2 == 0) remoteid = value2;
                    upd_str = upd_str + columnNames[index2] + "='" + value2 + "', ";
                    sel_str = sel_str + "'" + value2 + "', ";
                });
                sel_str = sel_str.substr(0, sel_str.length - 2);
                sel_str = sel_str + " WHERE NOT EXISTS(SELECT changes() AS change FROM "+tableName+" WHERE change <> 0);";
                upd_str = upd_str.substr(0, upd_str.length - 2);
                upd_str = upd_str + " WHERE remoteid = '" + remoteid + "';";                    
                query_update = update_string + upd_str;
                query_insert = insert_string + sel_str;  
                // Start transaction:
                transaction.executeSql(query_update);
                transaction.executeSql(query_insert);                    
            });
        }, function (error) {
            callback("Error: " + error);
        }, function () {
            callback("Success");
        });
    });
});
}

Vì vậy, trước tiên hãy chọn tên cột có chức năng này:

function get_columnNames(tableName, callback) {
myDb.transaction(function (transaction) {
    var query_exec = "SELECT name, sql FROM sqlite_master WHERE type='table' AND name ='" + tableName + "'";
    transaction.executeSql(query_exec, [], function (tx, results) {
        var columnParts = results.rows.item(0).sql.replace(/^[^\(]+\(([^\)]+)\)/g, '$1').split(','); ///// RegEx
        var columnNames = [];
        for (i in columnParts) {
            if (typeof columnParts[i] === 'string')
                columnNames.push(columnParts[i].split(" ")[0]);
        };
        callback(columnNames);
    });
});
}

Sau đó xây dựng các giao dịch theo chương trình.

"Giá trị" là một mảng bạn nên xây dựng trước đó và nó đại diện cho các hàng bạn muốn chèn hoặc cập nhật vào bảng.

"Remoteid" là id tôi sử dụng làm tài liệu tham khảo, vì tôi đang đồng bộ hóa với máy chủ từ xa của mình.

Để sử dụng plugin SQLite Cordova, vui lòng tham khảo liên kết chính thức


2

Phương pháp này phối lại một vài phương pháp khác để trả lời cho câu hỏi này và kết hợp việc sử dụng CTE (Biểu thức bảng chung). Tôi sẽ giới thiệu truy vấn sau đó giải thích lý do tại sao tôi làm những gì tôi đã làm.

Tôi muốn đổi họ của nhân viên 300 thành DAVIS nếu có nhân viên 300. Nếu không, tôi sẽ thêm một nhân viên mới.

Tên bảng: nhân viên Cột: id, First_name, last_name

Truy vấn là:

INSERT OR REPLACE INTO employees (employee_id, first_name, last_name)
WITH registered_employees AS ( --CTE for checking if the row exists or not
    SELECT --this is needed to ensure that the null row comes second
        *
    FROM (
        SELECT --an existing row
            *
        FROM
            employees
        WHERE
            employee_id = '300'

        UNION

        SELECT --a dummy row if the original cannot be found
            NULL AS employee_id,
            NULL AS first_name,
            NULL AS last_name
    )
    ORDER BY
        employee_id IS NULL --we want nulls to be last
    LIMIT 1 --we only want one row from this statement
)
SELECT --this is where you provide defaults for what you would like to insert
    registered_employees.employee_id, --if this is null the SQLite default will be used
    COALESCE(registered_employees.first_name, 'SALLY'),
    'DAVIS'
FROM
    registered_employees
;

Về cơ bản, tôi đã sử dụng CTE để giảm số lần sử dụng câu lệnh select để xác định giá trị mặc định. Vì đây là CTE, chúng tôi chỉ chọn các cột mà chúng tôi muốn từ bảng và câu lệnh INSERT sử dụng điều này.

Bây giờ bạn có thể quyết định mặc định nào bạn muốn sử dụng bằng cách thay thế null, trong hàm COALESCE bằng giá trị nào.


2

Theo Aristotle Pagaltzis và ý tưởng COALESCEtừ câu trả lời của Eric B , đây là một tùy chọn nâng cấp để chỉ cập nhật một vài cột hoặc chèn toàn bộ hàng nếu nó không tồn tại.

Trong trường hợp này, hãy tưởng tượng rằng tiêu đề và nội dung cần được cập nhật, giữ các giá trị cũ khác khi tồn tại và chèn các giá trị được cung cấp khi không tìm thấy tên:

LƯU Ý id bị buộc phải là NULL khi INSERTnó được coi là tự động. Nếu nó chỉ là một khóa chính được tạo thì COALESCEcũng có thể được sử dụng (xem bình luận của Aristotle Pagaltzis ).

WITH new (id, name, title, content, author)
     AS ( VALUES(100, 'about', 'About this site', 'Whatever new content here', 42) )
INSERT OR REPLACE INTO page (id, name, title, content, author)
SELECT
     old.id, COALESCE(old.name, new.name),
     new.title, new.content,
     COALESCE(old.author, new.author)
FROM new LEFT JOIN page AS old ON new.name = old.name;

Vì vậy, quy tắc chung sẽ là, nếu bạn muốn giữ các giá trị cũ, hãy sử dụng COALESCE, khi bạn muốn cập nhật giá trị, hãy sử dụngnew.fieldname


COALESCE(old.id, new.id)chắc chắn là sai với một khóa tự động. Và trong khi Lọ giữ hầu hết các hàng không thay đổi, ngoại trừ trường hợp thiếu giá trị âm thanh giống như trường hợp sử dụng mà thực tế ai đó có thể có, tôi không nghĩ đó là những gì mọi người đang tìm kiếm khi họ tìm cách thực hiện một UPSERT.
Aristotle Pagaltzis

@AristotlePagaltzis xin lỗi nếu tôi sai, tôi không sử dụng tự động. Điều tôi đang tìm kiếm với truy vấn này là chỉ cập nhật vài cols hoặc chèn toàn bộ hàng với các giá trị được cung cấp trong trường hợp nó không tồn tại . Tôi đã chơi với truy vấn của bạn và tôi không thể đạt được nó khi chèn: các cột được chọn từ oldbảng được gán NULL, không cho các giá trị được cung cấp trong new. Đây là lý do để sử dụng COALESCE. Tôi không phải là chuyên gia về sqlite, tôi đã thử nghiệm truy vấn này và dường như hoạt động trong trường hợp này, tôi rất cảm ơn bạn nếu bạn có thể chỉ cho tôi giải pháp với tính năng tự động
Miquel

2
Trong trường hợp khóa tự động tăng, bạn muốn chèn NULLlàm khóa, vì điều đó báo cho SQLite thay vì chèn giá trị khả dụng tiếp theo.
Aristotle Pagaltzis

Tôi không nói rằng bạn không nên làm những gì bạn đang làm (nếu bạn cần nó thì bạn cần nó), chỉ là nó không thực sự là một câu trả lời cho câu hỏi ở đây. UPSERT thường có nghĩa là bạn có một hàng mà bạn muốn được lưu trữ trong bảng và chỉ không biết liệu bạn đã có một hàng phù hợp để đặt các giá trị vào hay cần chèn chúng thành một hàng mới. Trường hợp sử dụng của bạn là nếu bạn đã có một hàng phù hợp, bạn muốn bỏ qua hầu hết các giá trị từ hàng mới . Điều đó tốt, đó không phải là câu hỏi được hỏi.
Aristotle Pagaltzis

1

Tôi nghĩ rằng đây có thể là những gì bạn đang tìm kiếm: ON mệnh đề CONFLICT .

Nếu bạn xác định bảng của mình như thế này:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    field1 TEXT 
); 

Bây giờ, nếu bạn thực hiện một INSERT với một id đã tồn tại, SQLite tự động thực hiện CẬP NHẬT thay vì INSERT.

Hth ...


6
Tôi không nghĩ rằng nó hoạt động, nó sẽ xóa sạch các cột bị thiếu trong câu lệnh chèn
Sam Saffron

3
@Mosor: -1 từ tôi, xin lỗi. Điều này giống như đưa ra một REPLACEtuyên bố.
Alix Axel

7
-1 bởi vì điều này thực hiện xóa và sau đó chèn nếu khóa chính đã tồn tại.
frast

1

Nếu bạn không phiền làm điều này trong hai hoạt động.

Các bước:

1) Thêm các mục mới với "CHERTN HOẶC IGNORE"

2) Cập nhật các mục hiện có với "CẬP NHẬT"

Đầu vào cho cả hai bước là cùng một bộ sưu tập các mục mới hoặc có thể cập nhật. Hoạt động tốt với các mục hiện có mà không cần thay đổi. Chúng sẽ được cập nhật, nhưng với cùng một dữ liệu và do đó kết quả ròng không có thay đổi.

Chắc chắn, chậm hơn, vv Không hiệu quả. Vâng.

Dễ dàng để viết sql và duy trì và hiểu nó? Chắc chắn rồi.

Đó là một sự đánh đổi để xem xét. Hoạt động tuyệt vời cho uperts nhỏ. Hoạt động tuyệt vời cho những người không ngại hy sinh hiệu quả cho khả năng duy trì mã.


Lưu ý cho tất cả mọi người: CHERTN HOẶC THAY THẾ KHÔNG phải là bài viết này. XÁC NHẬN HOẶC THAY THẾ sẽ tạo một hàng MỚI trong bảng của bạn, với một ID mới. Đó không phải là CẬP NHẬT.
sapbucket

-2

Vừa mới đọc bài viết này và thất vọng vì không dễ dàng gì với "UPSERT" này, tôi đã điều tra thêm ...

Bạn thực sự có thể làm điều này trực tiếp và dễ dàng trong SQLITE.

Thay vì sử dụng: INSERT INTO

Sử dụng: INSERT OR REPLACE INTO

Điều này làm chính xác những gì bạn muốn nó làm!


21
-1 INSERT OR REPLACEkhông phải là một UPSERT. Xem "câu trả lời" của gregschlom để biết lý do tại sao. Giải pháp của Eric B thực sự hoạt động và cần một số upvote.
Ngày

-4
SELECT COUNT(*) FROM table1 WHERE id = 1;

nếu COUNT(*) = 0

INSERT INTO table1(col1, col2, cole) VALUES(var1,var2,var3);

khác nếu COUNT(*) > 0

UPDATE table1 SET col1 = var4, col2 = var5, col3 = var6 WHERE id = 1;

Đây là cách quá phức tạp, SQL có thể xử lý việc này chỉ tốt trong một truy vấn
Robin Kanters
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.