如果有總是將成爲"pets"
陣列(即petName作爲密鑰)的每個成員中「可變的」內容然後$addToSet
是不適合你。至少不是在您希望應用它的陣列級別。
相反,你基本上需要對文檔的「鑰匙」的$exists
測試被包含在陣列中,那麼無論$addToSet
爲「載」與positional $
運營商匹配鍵的陣列,或其中「鍵「直接與$push
不匹配的」寵物「陣列,新的內部content
直接作爲唯一的陣列成員。
因此,如果您可以忍受未返回修改過的文檔,那麼「批量」操作適合您。在現代車手與bulkWrite()
:
User.prototype.updatePetArray = function(userId, petName, content) {
var filter1 = { "_id": ObjectId(userId) },
filter2 = { "_id": ObjectId(userId) },
update1 = { "$addToSet": {} },
update2 = { "$push": { "pets": {} } };
filter1["pets." + petName] = { "$exists": true };
filter2["pets." + petName] = { "$exists": false };
var setter1 = {};
setter1["pets.$." + petName] = content;
update1["$addToSet"] = setter1;
var setter2 = {};
setter2[petName] = [content];
update2["$push"]["pets"] = setter2;
// Return the promise that yields the BulkWriteResult of both calls
return this.collection.bulkWrite([
{ "updateOne": {
"filter": filter1,
"update": update1
}},
{ "updateOne": {
"filter": filter2,
"update": update2
}}
]);
};
如果必須退回修改的文件,那麼你將需要解決每個調用和返回實際匹配的東西之一:
User.prototype.updatePetArray = function(userId, petName, content) {
var filter1 = { "_id": ObjectId(userId) },
filter2 = { "_id": ObjectId(userId) },
update1 = { "$addToSet": {} },
update2 = { "$push": { "pets": {} } };
filter1["pets." + petName] = { "$exists": true };
filter2["pets." + petName] = { "$exists": false };
var setter1 = {};
setter1["pets.$." + petName] = content;
update1["$addToSet"] = setter1;
var setter2 = {};
setter2[petName] = [content];
update2["$push"]["pets"] = setter2;
// Return the promise that returns the result that matched and modified
return new Promise(function(resolve,reject) {
var operations = [
this.collection.findOneAndUpdate(filter1,update1,{ "returnOriginal": false}),
this.collection.findOneAndUpdate(filter2,update2,{ "returnOriginal": false})
];
// Promise.all runs both, and discard the null document
Promise.all(operations).then(function(result) {
resolve(result.filter(function(el) { return el.value != null })[0].value);
},reject);
});
};
在這兩種情況下,這需要「兩次」更新嘗試,其中只有「一個」實際上會成功並修改文檔,因爲只有其中一個$exists
測試成立。
{
"_id": ObjectId("56d7b759e955e2812c6c8c1b"),
"pets.fido": { "$exists": true }
},
{ "$addToSet": { "pets.$.fido": "ccc" } }
而第二個更新爲:
{
"_id": ObjectId("56d7b759e955e2812c6c8c1b"),
"pets.fido": { "$exists": false }
},
{ "$push": { "pets": { "fido": ["ccc"] } } }
的考慮變量下
所以作爲第一種情況下,「查詢」和「更新」的一個例子插值後解決:
userId = "56d7b759e955e2812c6c8c1b",
petName = "fido",
content = "ccc";
就我個人而言,我不會像這樣命名鍵,而是將結構更改爲:
{
"_id": ObjectId("56d7b759e955e2812c6c8c1b"),
"pets": [{ "name": "fido", "data": ["abc"] }]
}
這使得更新語句更容易,而且不需要對鍵名進行可變內插。例如:
{
"_id": ObjectId(userId),
"pets.name": petName
},
{ "$addToSet": { "pets.$.data": content } }
和:
{
"_id": ObjectId(userId),
"pets.name": { "$ne": petName }
},
{ "$push": { "pets": { "name": petName, "data": [content] } } }
這感覺了一大堆清潔,可以實際使用的「指數」進行匹配,這當然$exists
根本無法。
如果使用.findOneAndUpdate()
,當然會有更多的開銷,因爲這對於需要等待響應的服務器來說是最後的「兩次」實際調用,而Bulk方法只是「一個」。
但是如果你需要返回的文檔(選項反正在驅動程序的默認),那麼無論做或類似的等待無極從.bulkWrite()
解決,然後完成後通過.findOne()
提取文檔。雖然修改後通過.findOne()
修改後不會真的是「原子的」,並且可能會在「之後」返回文檔進行另一個類似的修改,而不僅僅是該特定更改的狀態。
NB還假設除了子文檔的鍵在"pets"
爲「設置」您的其他意圖用於陣列包含被添加到「設置」,以及經由提供給函數的附加content
。如果您只是想覆蓋一個值,那麼只需應用$set
而不是$addToSet
,並將其類似地換成數組。
但是,前者就是你所問的,這聽起來很合理。
順便說一句。請清理被可怕的設置代碼在這個例子中的查詢和更新對象實際代碼:)
作爲一個自包含上市證明:
var async = require('async'),
mongodb = require('mongodb'),
MongoClient = mongodb.MongoClient;
MongoClient.connect('mongodb://localhost/test',function(err,db) {
var coll = db.collection('pettest');
var petName = "fido",
content = "bbb";
var filter1 = { "_id": 1 },
filter2 = { "_id": 1 },
update1 = { "$addToSet": {} },
update2 = { "$push": { "pets": {} } };
filter1["pets." + petName] = { "$exists": true };
filter2["pets." + petName] = { "$exists": false };
var setter1 = {};
setter1["pets.$." + petName] = content;
update1["$addToSet"] = setter1;
var setter2 = {};
setter2[petName] = [content];
update2["$push"]["pets"] = setter2;
console.log(JSON.stringify(update1,undefined,2));
console.log(JSON.stringify(update2,undefined,2));
function CleanInsert(callback) {
async.series(
[
// Clean data
function(callback) {
coll.deleteMany({},callback);
},
// Insert sample
function(callback) {
coll.insert({ "_id": 1, "pets": [{ "fido": ["abc"] }] },callback);
}
],
callback
);
}
async.series(
[
CleanInsert,
// Modify Bulk
function(callback) {
coll.bulkWrite([
{ "updateOne": {
"filter": filter1,
"update": update1
}},
{ "updateOne": {
"filter": filter2,
"update": update2
}}
]).then(function(res) {
console.log(JSON.stringify(res,undefined,2));
coll.findOne({ "_id": 1 }).then(function(res) {
console.log(JSON.stringify(res,undefined,2));
callback();
});
},callback);
},
CleanInsert,
// Modify Promise all
function(callback) {
var operations = [
coll.findOneAndUpdate(filter1,update1,{ "returnOriginal": false }),
coll.findOneAndUpdate(filter2,update2,{ "returnOriginal": false })
];
Promise.all(operations).then(function(res) {
//console.log(JSON.stringify(res,undefined,2));
console.log(
JSON.stringify(
res.filter(function(el) { return el.value != null })[0].value
)
);
callback();
},callback);
}
],
function(err) {
if (err) throw err;
db.close();
}
);
});
和輸出:
{
"$addToSet": {
"pets.$.fido": "bbb"
}
}
{
"$push": {
"pets": {
"fido": [
"bbb"
]
}
}
}
{
"ok": 1,
"writeErrors": [],
"writeConcernErrors": [],
"insertedIds": [],
"nInserted": 0,
"nUpserted": 0,
"nMatched": 1,
"nModified": 1,
"nRemoved": 0,
"upserted": []
}
{
"_id": 1,
"pets": [
{
"fido": [
"abc",
"bbb"
]
}
]
}
{"_id":1,"pets":[{"fido":["abc","bbb"]}]}
隨意更改爲不同的值,以瞭解如何應用不同的「集合」。
謝謝!我最初有''寵物':[{「名字」:「fido」,「data」:[「abc」]}]'並且認爲它不是很好。我會改變這一點......謝謝你確認這一點。 – dman
如果可能的話,我想避免'pets'數組上的額外索引,以節省內存。由於'pets'數組是User doc的嵌入式文檔,因此不會搜索索引的'_id:ObjectId()'就足夠了嗎?我仍然可以使用'「pets.fido」:{「$ exists」:false}'和'「pets.name」:{「$ ne」:petName}'沒有額外的索引和相同的性能? – dman
@dman只要你在查詢中包含'_id',它就不會成爲問題,因爲文檔已經被該值所選中。如果它在結構上有所作爲,並且指數收益來自何處,那麼您希望對此來源的文檔進行任何類型的分析。命名鍵在分析中確實不能很好地工作,這是避免它們的強有力的理由。至於你的內存問題,這個get一次又一次提出,但真正的差別是微不足道的,甚至在有線老虎(內部哈希鍵)下更是如此。 –