2016-03-03 63 views
3

我有一個嵌入文檔的數組'pets': [{'fido': ['abc']}。當我給寵物添加寵物時,如何檢查寵物是否已經存在?例如,如果我再次添加fido ...我如何檢查是否只有fido存在,而不是添加它?我希望我可以使用$addToSet,但我只想檢查一部分(寵物名稱)。加入fido兩次

User.prototype.updatePetArray = function(userId, petName) { 
    userId = { _id: ObjectId(userId) }; 
    return this.collection.findOneAndUpdate(userId, 
    { $addToSet: { pets: { [petName]: [] } } }, 
    { returnOriginal: false, 
     maxTimeMS: QUERY_TIME }); 

結果:

{u'lastErrorObject': {u'updatedExisting': True, u'n': 1}, u'ok': 1, u'value': {u'username': u'bob123', u'_id': u'56d5fc8381c9c28b3056f794', u'location': u'AT', u'pets': [{u'fido': []}]}} 

{u'lastErrorObject': {u'updatedExisting': True, u'n': 1}, u'ok': 1, u'value': {u'username': u'bob123', u'_id': u'56d5fc8381c9c28b3056f794', u'location': u'AT', u'pets': [{u'fido': [u'abc']}, {u'fido': []}]}} 

回答

1

如果有總是將成爲"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"]}]} 

隨意更改爲不同的值,以瞭解如何應用不同的「集合」。

+0

謝謝!我最初有''寵物':[{「名字」:「fido」,「data」:[「abc」]}]'並且認爲它不是很好。我會改變這一點......謝謝你確認這一點。 – dman

+0

如果可能的話,我想避免'pets'數組上的額外索引,以節省內存。由於'pets'數組是User doc的嵌入式文檔,因此不會搜索索引的'_id:ObjectId()'就足夠了嗎?我仍然可以使用'「pets.fido」:{「$ exists」:false}'和'「pets.name」:{「$ ne」:petName}'沒有額外的索引和相同的性能? – dman

+1

@dman只要你在查詢中包含'_id',它就不會成爲問題,因爲文檔已經被該值所選中。如果它在結構上有所作爲,並且指數收益來自何處,那麼您希望對此來源的文檔進行任何類型的分析。命名鍵在分析中確實不能很好地工作,這是避免它們的強有力的理由。至於你的內存問題,這個get一次又一次提出,但真正的差別是微不足道的,甚至在有線老虎(內部哈希鍵)下更是如此。 –

1

請嘗試這一個與string template,這裏是一個例子蒙戈下運行外殼

> var name = 'fido'; 
> var t = `pets.${name}`; \\ string temple, could parse name variable 
> db.pets.find() 
    { "_id" : ObjectId("56d7b5019ed174b9eae2b9c5"), "pets" : [ { "fido" : [ "abc" ]} ] } 

用下面update命令,它會如果存在相同的寵物名稱,則不更新它。

> db.pets.update({[t]: {$exists: false}}, {$addToSet: {pets: {[name]: []}}}) 
    WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 }) 

如果pets文檔

> db.pets.find() 
{ "_id" : ObjectId("56d7b7149ed174b9eae2b9c6"), "pets" : [ { "fi" : [ "abc" ] } ] } 

更新後與

> db.pets.update({[t]: {$exists: false}}, {$addToSet: {pets: {[name]: []}}}) 
    WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) 

結果表明添加的寵物名字,如果它不存在。

> db.pets.find() 
    { "_id" : ObjectId("56d7b7149ed174b9eae2b9c6"), "pets" : [ { "fi" : [ "abc" ] }, { "fido" : [ ] } ] } 
+0

這不是有效的JavaScript。鍵「stringify」,因此它正在尋找't'並嘗試添加'[name]'作爲鍵而不是變量插值。也沒有那麼簡單 –

+0

@BlakesSeven,我的壞,我犯了以前的答案錯誤。我用一些測試代碼更新了我的答案。現在看起來好嗎? – zangw

+0

我還沒有嘗試過,因爲我可能會改變陣列結構。但這是一個好主意!我喜歡它也很乾淨。 – dman

相關問題