Trong câu trả lời ngắn nhất, đó là cả "có" và "không".
Thực sự có một cách để khớp các phần tử mảng riêng lẻ và cập nhật chúng với các giá trị riêng biệt trong một câu lệnh duy nhất, vì trên thực tế, bạn có thể cung cấp "nhiều" arrayFilters
điều kiện và sử dụng các số nhận dạng đó trong tuyên bố cập nhật của bạn.
Vấn đề với mẫu cụ thể của bạn ở đây là một trong những mục nhập trong "tập hợp thay đổi" của bạn (mục cuối cùng) không thực sự khớp với bất kỳ thành viên mảng nào hiện có. Hành động "giả định" ở đây sẽ là $ push
thành viên mới chưa được so khớp đó vào mảng mà nó không được tìm thấy. Tuy nhiên, hành động cụ thể đó không thể được thực hiện trong một "thao tác đơn lẻ" , nhưng bạn có thể sử dụng BulWrite ()
để đưa ra các câu lệnh "nhiều" để giải quyết trường hợp đó.
Khớp các điều kiện mảng khác nhau
Giải thích điều đó bằng điểm, hãy xem xét hai mục đầu tiên trong "bộ thay đổi" của bạn. Bạn có thể áp dụng một "đơn" cập nhật câu lệnh với nhiều arrayFilters
như thế này:
db.avail_rates_copy.updateOne(
{ "_id": 12345 },
{
"$set": {
"rates.$[one]": {
"productId" : NumberInt(1234),
"rate" : 400.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201801)
},
"rates.$[two]": {
"productId" : NumberInt(1234),
"rate" : 500.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201802)
}
}
},
{
"arrayFilters": [
{
"one.productId": NumberInt(1234),
"one.rateCardId": NumberInt(1),
"one.month": NumberInt(201801)
},
{
"two.productId": NumberInt(1234),
"two.rateCardId": NumberInt(1),
"two.month": NumberInt(201802)
}
]
}
)
Nếu bạn chạy mà bạn sẽ thấy tài liệu được sửa đổi sẽ trở thành:
{
"_id" : 12345,
"_class" : "com.example.ProductRates",
"rates" : [
{ // Matched and changed this by one
"productId" : 1234,
"rate" : 400,
"rateCardId" : 1,
"month" : 201801
},
{ // And this as two
"productId" : 1234,
"rate" : 500,
"rateCardId" : 1,
"month" : 201802
},
{
"productId" : 1234,
"rate" : 400,
"rateCardId" : 2,
"month" : 201803
},
{
"productId" : 1235,
"rate" : 500,
"rateCardId" : 1,
"month" : 201801
},
{
"productId" : 1235,
"rate" : 234,
"rateCardId" : 2,
"month" : 201803
}
]
}
Lưu ý ở đây rằng bạn chỉ định từng "định danh" trong danh sách arrayFilters
với nhiều điều kiện để khớp với phần tử như sau:
{
"one.productId": NumberInt(1234),
"one.rateCardId": NumberInt(1),
"one.month": NumberInt(201801)
},
Vì vậy, mỗi "điều kiện" ánh xạ hiệu quả như:
<identifier>.<property>
Vì vậy, nó biết đang xem xét "giá"
mảng theo câu lệnh trong khối cập nhật bởi $ [
:
"rates.$[one]"
Và xem xét từng yếu tố của "rate"
để phù hợp với các điều kiện. Vì vậy, "một"
mã định danh sẽ khớp với các điều kiện có tiền tố là "one"
và tương tự như vậy đối với tập hợp các điều kiện khác có tiền tố là "hai"
, do đó, câu lệnh cập nhật thực tế chỉ áp dụng cho những câu lệnh phù hợp với các điều kiện được chỉ định cho số nhận dạng.
Nếu bạn chỉ muốn "giá"
thuộc tính trái ngược với toàn bộ đối tượng, khi đó bạn chỉ cần ghi chú là:
{ "$set": { "rates.$[one].rate": 400, "rates.$[two].rate": 500 } }
Thêm các đối tượng không phù hợp
Vì vậy, phần đầu tiên tương đối đơn giản để hiểu, nhưng như đã nói khi thực hiện $ push
đối với "phần tử không có ở đó" thì lại là một vấn đề khác, vì về cơ bản chúng ta cần một điều kiện truy vấn ở cấp "tài liệu" để xác định rằng một phần tử mảng bị "thiếu".
Về cơ bản, điều này có nghĩa là bạn cần phát hành bản cập nhật với mã <> $ push
tìm kiếm từng phần tử mảng để xem nó có tồn tại hay không. Khi nó không xuất hiện, thì tài liệu là một khớp và $ push
được thực hiện.
Đây là nơi BulWrite ()
phát huy tác dụng và bạn sử dụng nó bằng cách thêm bản cập nhật bổ sung vào hoạt động đầu tiên của chúng tôi ở trên cho mọi phần tử trong "tập hợp thay đổi":
db.avail_rates_copy.bulkWrite(
[
{ "updateOne": {
"filter": { "_id": 12345 },
"update": {
"$set": {
"rates.$[one]": {
"productId" : NumberInt(1234),
"rate" : 400.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201801)
},
"rates.$[two]": {
"productId" : NumberInt(1234),
"rate" : 500.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201802)
},
"rates.$[three]": {
"productId" : NumberInt(1235),
"rate" : 700.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201802)
}
}
},
"arrayFilters": [
{
"one.productId": NumberInt(1234),
"one.rateCardId": NumberInt(1),
"one.month": NumberInt(201801)
},
{
"two.productId": NumberInt(1234),
"two.rateCardId": NumberInt(1),
"two.month": NumberInt(201802)
},
{
"three.productId": NumberInt(1235),
"three.rateCardId": NumberInt(1),
"three.month": NumberInt(201802)
}
]
}},
{ "updateOne": {
"filter": {
"_id": 12345,
"rates": {
"$not": {
"$elemMatch": {
"productId" : NumberInt(1234),
"rateCardId": NumberInt(1),
"month" : NumberInt(201801)
}
}
}
},
"update": {
"$push": {
"rates": {
"productId" : NumberInt(1234),
"rate" : 400.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201801)
}
}
}
}},
{ "updateOne": {
"filter": {
"_id": 12345,
"rates": {
"$not": {
"$elemMatch": {
"productId" : NumberInt(1234),
"rateCardId": NumberInt(1),
"month" : NumberInt(201802)
}
}
}
},
"update": {
"$push": {
"rates": {
"productId" : NumberInt(1234),
"rate" : 500.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201802)
}
}
}
}},
{ "updateOne": {
"filter": {
"_id": 12345,
"rates": {
"$not": {
"$elemMatch": {
"productId" : NumberInt(1235),
"rateCardId": NumberInt(1),
"month" : NumberInt(201802)
}
}
}
},
"update": {
"$push": {
"rates": {
"productId" : NumberInt(1235),
"rate" : 700.0,
"rateCardId": NumberInt(1),
"month" : NumberInt(201802)
}
}
}
}}
],
{ "ordered": true }
)
Lưu ý $ elemMatch
sử dụng bộ lọc truy vấn, vì đây là yêu cầu để khớp với một phần tử mảng theo "nhiều điều kiện". Chúng tôi không cần điều đó trên arrayFilters
các mục nhập vì chúng chỉ xem xét từng mục mảng mà chúng đã được áp dụng rồi, nhưng dưới dạng "truy vấn", các điều kiện yêu cầu $ elemMatch
vì "ký hiệu dấu chấm" đơn giản sẽ trả về kết quả phù hợp không chính xác.
Cũng xem $ not
toán tử được sử dụng ở đây để "phủ định" $ elemMatch
, vì điều kiện thực sự của chúng tôi là chỉ khớp với tài liệu "không khớp với phần tử mảng" với các điều kiện được cung cấp và đó là điều biện minh cho việc lựa chọn để thêm một phần tử mới.
Và một tuyên bố duy nhất được phát hành cho máy chủ về cơ bản là cố gắng bốn cập nhật các hoạt động như một hoạt động để cố gắng cập nhật các phần tử mảng phù hợp và một hoạt động khác cho từng ba "thay đổi bộ" đang cố gắng $ push
nơi tài liệu được tìm thấy không khớp với các điều kiện cho phần tử mảng trong "tập hợp thay đổi".
Do đó, kết quả như mong đợi:
{
"_id" : 12345,
"_class" : "com.example.ProductRates",
"rates" : [
{ // matched and updated
"productId" : 1234,
"rate" : 400,
"rateCardId" : 1,
"month" : 201801
},
{ // matched and updated
"productId" : 1234,
"rate" : 500,
"rateCardId" : 1,
"month" : 201802
},
{
"productId" : 1234,
"rate" : 400,
"rateCardId" : 2,
"month" : 201803
},
{
"productId" : 1235,
"rate" : 500,
"rateCardId" : 1,
"month" : 201801
},
{
"productId" : 1235,
"rate" : 234,
"rateCardId" : 2,
"month" : 201803
},
{ // This was appended
"productId" : 1235,
"rate" : 700,
"rateCardId" : 1,
"month" : 201802
}
]
}
Tùy thuộc vào số lượng phần tử thực sự chưa khớp với BulWrite ()
phản hồi sẽ báo cáo về số lượng trong số các tuyên bố đó thực sự khớp và ảnh hưởng đến một tài liệu. Trong trường hợp này, đó là 2
khớp và sửa đổi, vì thao tác cập nhật "đầu tiên" khớp với các mục nhập mảng hiện có và cập nhật thay đổi "cuối cùng" khớp với tài liệu không chứa mục nhập mảng và thực hiện $push
để sửa đổi.
Kết luận
Vì vậy, bạn có phương pháp kết hợp, trong đó:
-
Phần đầu tiên của "cập nhật" trong câu hỏi của bạn rất dễ dàng và có thể được thực hiện trong một câu lệnh duy nhất , như được trình bày trong phần đầu tiên.
-
Phần thứ hai nơi có một phần tử mảng "hiện không tồn tại" trong mảng tài liệu hiện tại, điều này thực sự yêu cầu bạn sử dụng
BulWrite ()
để đưa ra các hoạt động "nhiều" trong một yêu cầu.
Do đó, hãy cập nhật , là "CÓ" đối với một thao tác duy nhất. Nhưng thêm sự khác biệt nghĩa là nhiều phép toán. Nhưng bạn có thể kết hợp hai cách tiếp cận như được trình bày ở đây.
Có nhiều cách "lạ mắt" để bạn có thể xây dựng các câu lệnh này dựa trên nội dung mảng "tập hợp thay đổi" với mã, vì vậy bạn không cần phải "mã hóa cứng" từng thành viên.
Là một trường hợp cơ bản cho JavaScript và tương thích với bản phát hành hiện tại của trình bao mongo (điều này hơi khó chịu không hỗ trợ các toán tử lây lan đối tượng):
db.getCollection('avail_rates_copy').drop();
db.getCollection('avail_rates_copy').insert(
{
"_id" : 12345,
"_class" : "com.example.ProductRates",
"rates" : [
{
"productId" : 1234,
"rate" : 100,
"rateCardId" : 1,
"month" : 201801
},
{
"productId" : 1234,
"rate" : 200,
"rateCardId" : 1,
"month" : 201802
},
{
"productId" : 1234,
"rate" : 400,
"rateCardId" : 2,
"month" : 201803
},
{
"productId" : 1235,
"rate" : 500,
"rateCardId" : 1,
"month" : 201801
},
{
"productId" : 1235,
"rate" : 234,
"rateCardId" : 2,
"month" : 201803
}
]
}
);
var changeSet = [
{
"productId" : 1234,
"rate" : 400.0,
"rateCardId": 1,
"month" : 201801
},
{
"productId" : 1234,
"rate" : 500.0,
"rateCardId": 1,
"month" : 201802
},
{
"productId" : 1235,
"rate" : 700.0,
"rateCardId": 1,
"month" : 201802
}
];
var arrayFilters = changeSet.map((obj,i) =>
Object.keys(obj).filter(k => k != 'rate' )
.reduce((o,k) => Object.assign(o, { [`u${i}.${k}`]: obj[k] }) ,{})
);
var $set = changeSet.reduce((o,r,i) =>
Object.assign(o, { [`rates.$[u${i}].rate`]: r.rate }), {});
var updates = [
{ "updateOne": {
"filter": { "_id": 12345 },
"update": { $set },
arrayFilters
}},
...changeSet.map(obj => (
{ "updateOne": {
"filter": {
"_id": 12345,
"rates": {
"$not": {
"$elemMatch": Object.keys(obj).filter(k => k != 'rate')
.reduce((o,k) => Object.assign(o, { [k]: obj[k] }),{})
}
}
},
"update": {
"$push": {
"rates": obj
}
}
}}
))
];
db.getCollection('avail_rates_copy').bulkWrite(updates,{ ordered: true });
Thao tác này sẽ tạo động một danh sách các thao tác cập nhật "Hàng loạt" trông giống như sau:
[
{
"updateOne": {
"filter": {
"_id": 12345
},
"update": {
"$set": {
"rates.$[u0].rate": 400,
"rates.$[u1].rate": 500,
"rates.$[u2].rate": 700
}
},
"arrayFilters": [
{
"u0.productId": 1234,
"u0.rateCardId": 1,
"u0.month": 201801
},
{
"u1.productId": 1234,
"u1.rateCardId": 1,
"u1.month": 201802
},
{
"u2.productId": 1235,
"u2.rateCardId": 1,
"u2.month": 201802
}
]
}
},
{
"updateOne": {
"filter": {
"_id": 12345,
"rates": {
"$not": {
"$elemMatch": {
"productId": 1234,
"rateCardId": 1,
"month": 201801
}
}
}
},
"update": {
"$push": {
"rates": {
"productId": 1234,
"rate": 400,
"rateCardId": 1,
"month": 201801
}
}
}
}
},
{
"updateOne": {
"filter": {
"_id": 12345,
"rates": {
"$not": {
"$elemMatch": {
"productId": 1234,
"rateCardId": 1,
"month": 201802
}
}
}
},
"update": {
"$push": {
"rates": {
"productId": 1234,
"rate": 500,
"rateCardId": 1,
"month": 201802
}
}
}
}
},
{
"updateOne": {
"filter": {
"_id": 12345,
"rates": {
"$not": {
"$elemMatch": {
"productId": 1235,
"rateCardId": 1,
"month": 201802
}
}
}
},
"update": {
"$push": {
"rates": {
"productId": 1235,
"rate": 700,
"rateCardId": 1,
"month": 201802
}
}
}
}
}
]
Giống như được mô tả trong "dạng dài" của câu trả lời chung, nhưng tất nhiên chỉ đơn giản là sử dụng nội dung "mảng" đầu vào để xây dựng tất cả các câu lệnh đó.
Bạn có thể thực hiện việc xây dựng đối tượng động như vậy bằng bất kỳ ngôn ngữ nào và tất cả các trình điều khiển MongoDB đều chấp nhận đầu vào của một số loại cấu trúc mà bạn được phép "thao tác", sau đó được chuyển đổi thành BSON trước khi nó thực sự được gửi đến máy chủ để thực thi.