MongoDB nguyên tử “findOrCreate”: findOne, chèn nếu không tồn tại, nhưng không cập nhật


114

như tiêu đề cho biết, tôi muốn thực hiện tìm (một) cho một tài liệu, bằng _id, và nếu không tồn tại, hãy tạo nó, sau đó cho dù nó được tìm thấy hay đã được tạo, nó có được trả về trong lệnh gọi lại hay không.

Tôi không muốn cập nhật nó nếu nó tồn tại, như tôi đã đọc findAndModify. Tôi đã thấy nhiều câu hỏi khác trên Stackoverflow liên quan đến điều này nhưng một lần nữa, tôi không muốn cập nhật bất cứ điều gì.

Tôi không chắc liệu bằng cách tạo (không tồn tại), ĐÓ thực sự là bản cập nhật mà mọi người đang nói đến, tất cả đều khó hiểu :(

Câu trả lời:


192

Bắt đầu với MongoDB 2.4, không còn cần thiết phải dựa vào một chỉ mục duy nhất (hoặc bất kỳ cách giải quyết nào khác) cho các findOrCreatehoạt động như nguyên tử .

Đây là nhờ vào các $setOnInsertnhà khai thác mới đến 2.4, cho phép bạn xác định thông tin cập nhật mà chỉ nên xảy ra khi chèn văn bản.

Điều này, kết hợp với upserttùy chọn, có nghĩa là bạn có thể sử dụng findAndModifyđể đạt được một findOrCreatehoạt động giống như nguyên tử .

db.collection.findAndModify({
  query: { _id: "some potentially existing id" },
  update: {
    $setOnInsert: { foo: "bar" }
  },
  new: true,   // return new doc if one is upserted
  upsert: true // insert the document if it does not exist
})

$setOnInsertchỉ ảnh hưởng đến tài liệu được chèn vào, nếu tài liệu hiện có được tìm thấy, sẽ không có sửa đổi nào xảy ra. Nếu không có tài liệu nào tồn tại, nó sẽ bổ sung một tài liệu với _id được chỉ định, sau đó thực hiện bộ chỉ chèn. Trong cả hai trường hợp, tài liệu được trả lại.


3
@Gank nếu bạn đang sử dụng trình điều khiển gốc mongodb cho nút thì cú pháp sẽ giống hơn collection.findAndModify({_id:'theId'}, <your sort opts>, {$setOnInsert:{foo: 'bar'}}, {new:true, upsert:true}, callback). Xem tài liệu
số 1311407

7
Có cách nào để biết tài liệu đã được cập nhật hoặc chèn thêm hay không?
diệt vong

3
Nếu bạn muốn kiểm tra xem truy vấn trên ( db.collection.findAndModify({query: {_id: "some potentially existing id"}, update: {$setOnInsert: {foo: "bar"}}, new: true, upsert: true})) chèn (nâng cấp) có chỉnh sửa tài liệu hay không, bạn nên cân nhắc sử dụng db.collection.updateOne({_id: "some potentially existing id"}, {$setOnInsert: {foo: "bar"}}, {upsert: true}). Nó trả về {"acknowledged": true, "matchedCount": 0, "modifiedCount": 0, "upsertedId": ObjectId("for newly inserted one")}nếu tài liệu được chèn vào, {"acknowledged": true, "matchedCount": 1, "modifiedCount": 0}nếu tài liệu đã tồn tại.
Константин Ван

8
dường như được tán thành trong hương vị của findOneAndUpdate, findOneAndReplacehoặcfindOneAndDelete
Ravshan Samandarov

5
Tuy nhiên, người ta cần phải cẩn thận ở đây. Điều này chỉ hoạt động nếu bộ chọn của findAndModify / findOneAndUpdate / updateOne xác định duy nhất một tài liệu bằng _id. Nếu không, phần bổ sung được chia nhỏ trên máy chủ thành một truy vấn và một bản cập nhật / chèn. Bản cập nhật sẽ vẫn là nguyên tử. Nhưng truy vấn và cập nhật cùng nhau sẽ không được thực thi nguyên tử.
Anon

14

Phiên bản trình điều khiển> 2

Bằng cách sử dụng trình điều khiển mới nhất (> phiên bản 2) , bạn sẽ sử dụng findOneAndUpdate như findAndModifykhông được dùng nữa. Phương pháp mới mất 3 đối số, các filter, các updateđối tượng (trong đó có chứa các thuộc tính mặc định của bạn, cần được chèn vào cho một đối tượng mới), và optionsnơi bạn có để xác định các hoạt động upsert.

Sử dụng cú pháp lời hứa, nó trông giống như sau:

const result = await collection.findOneAndUpdate(
  { _id: new ObjectId(id) },
  {
    $setOnInsert: { foo: "bar" },
  },
  {
    returnOriginal: false,
    upsert: true,
  }
);

const newOrUpdatedDocument = result.value;

Cảm ơn, returnOriginalnên returnNewDocumenttôi nghĩ - docs.mongodb.com/manual/reference/method/...
Dominic

Đừng bận tâm, trình điều khiển nút đã triển khai nó theo cách khác và câu trả lời của bạn là đúng
Dominic

6

Nó hơi bẩn, nhưng bạn có thể chèn nó vào.

Đảm bảo rằng khóa có một chỉ mục duy nhất trên đó (nếu bạn sử dụng _id thì không sao, nó đã là duy nhất).

Bằng cách này, nếu phần tử đã có mặt, nó sẽ trả về một ngoại lệ mà bạn có thể bắt được.

Nếu nó không có, tài liệu mới sẽ được chèn.

Đã cập nhật: giải thích chi tiết về kỹ thuật này trên Tài liệu MongoDB


ok, đó là một ý kiến ​​hay, nhưng đối với giá trị tồn tại từ trước, nó sẽ trả về một lỗi chứ KHÔNG phải chính giá trị đó, phải không?
Discipol

3
Đây thực sự là một trong những giải pháp được đề nghị cho chuỗi cô lập hoạt động (tìm sau đó tạo ra nếu không tìm thấy, có lẽ) docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations
numbers1311407

@Discipol nếu bạn muốn thực hiện một tập hợp các hoạt động nguyên tử, trước tiên bạn nên khóa Tài liệu, sau đó sửa đổi nó và cuối cùng thì giải phóng nó. Điều này sẽ yêu cầu nhiều truy vấn hơn, nhưng bạn có thể tối ưu hóa cho trường hợp tốt nhất và chỉ thực hiện 1-2 truy vấn hầu hết các lần. xem: docs.mongodb.org/manual/tutorial/perform-two-phase-commits
Madarco

1

Đây là những gì tôi đã làm (trình điều khiển Ruby MongoDB):

$db[:tags].update_one({:tag => 'flat'}, {'$set' => {:tag => 'earth' }}, { :upsert => true })}

Nó sẽ cập nhật nó nếu nó tồn tại và chèn nó nếu nó khô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.