$ addToSet基于Object键存在

时间:2016-03-03 02:41:41

标签: node.js mongodb mongodb-query

我有一个嵌入文档的数组$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 }); 而不添加它?我希望我可以使用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'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': []}]}}

两次添加public class Robot { ... int x; int y; ... } 的结果:

java.awt.Point

2 个答案:

答案 0 :(得分:1)

请使用string template试试这个,这是在mongo shell下运行的一个示例

> 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" : [ ] } ] }

答案 1 :(得分: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(),当然会有更多的开销,因为这是后来的"两个"对您需要等待响应的服务器的实际调用,而不是只有" one"的批量方法。

但是如果您需要返回的文档(选项是驱动程序中的默认选项),那么要么执行此操作,要么类似地等待来自.bulkWrite()的Promise解析,然后在完成后通过.findOne()获取文档。虽然在修改之后通过.findOne()进行操作并不是真正的" atomic"并且可能会在"之后返回文件"进行了另一次类似的修改,而不仅仅是在特定变化的状态。

N.B还假设除了"pets"中的子文档的键作为" set"您对阵列的另一个意图是添加到#34; set"以及通过提供给函数的附加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"]}]}

随意更改为不同的值,以查看不同的"设置"适用。