MongoDB查询中的嵌套条件

时间:2015-07-03 02:30:45

标签: mongodb mongodb-query

我的文档结构如下:

{
   _id : <number>
   timestamp : <datetime>
   ...
}

我正在尝试对这些文档的集合进行查询,其中给出了新的id和关联的timestamp,我执行以下操作:

  • 检查集合中是否已存在新的id
  • 如果id 不存在,则创建新文档并添加包含ID和时间戳的新文档
  • 如果id 确实存在,那么对于匹配的文档,请检查timestamp
  • 如果timestamp比新版本更早/更小,则将旧timestamp替换为新版本。否则,什么也不做。

目前我所拥有的是:

db.test.update(

  {
    "_id" : newID,  // QUERY
    "timestamp" : { "$lt" : newTimestamp }
  },
  { 
    "$set" : { "timestamp" : newTimestamp } // UPDATE
  },
  { upsert : true })

这适用于所有情况,但id已存在且newTimestamp早于已存储的timestamp的情况除外。在这种情况下,我得到E11000 duplicate key error

如何修复查询?

2 个答案:

答案 0 :(得分:2)

虽然这可能没有得到很好的解释或者很容易被注意到,但MongoDB shell中的.update()等方法(以及为所有驱动程序提供相同的方法)实际上使用{{3操作API“引擎盖下”。

这是对之前存在的“遗留”操作的重大更新,不仅是因为允许方法的“批处理”,而且还提供了“写入”提交方式和给出的响应数据。 / p>

作为一个简单的例子:

> var date = new Date();
> db.testme.update({ "_id": 1 },{ "$set": { "updated": date } },{ "upsert": true })
WriteResult({ "nMatched" : 0, "nUpserted" : 1, "nModified" : 0, "_id" : 1 })

返回一个"Bulk"对象,显示发生的事件的统计信息。在这种情况下,“upsert”包含计数器值和文档的“_id”。

在具有相同date值的第二次执行中,您将得到:

> db.testme.update({ "_id": 1 }, { "$set": { "updated": date } },{ "upsert": true })
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 0 })

请注意,“nMatched”为1但“nModified”为0。这是因为API具有“智能”,可以意识到实际上没有数据被“更改”,因此没有进行任何修改。

当然,如果将日期更改为其他值,则会进行修改:

> var date = new Date();
> db.testme.update({ "_id": 1 },{ "$set": { "updated": date } },{ "upsert": true })
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

扩展这一原则,在这种情况下,“批量”操作是您的朋友,您的逻辑是:

  

“仅修改新”日期“大于”存在的数据“的数据,并仅在_id值不存在的情况下插入新记录。”

逻辑不能很好地融合到单个语句中,但是您可以使用“批量”操作在“同一时间”将两个操作发送到服务器。

  • 在条件
  • 下尝试修改的一项操作
  • 如果文档不存在,则尝试进行upsert的另一个操作

所以它形成如下:

    var bulk = db.testme.initializeOrderedBulkOp();
    bulk.find({ "_id": 2, "created": { "$lt": date } })
        .updateOne({ "$set": { "created": date } });
    bulk.find({ "_id": 2 }).upsert().updateOne(
    { "$setOnInsert": { "created": date } });
    bulk.execute();

WriteResult提供了额外的帮助,“只会”在“插入”时修改匹配的文档,而不是“匹配”。

首次执行将是:

var date = new Date("2015-07-03");

BulkWriteResult({
    "writeErrors" : [ ],
    "writeConcernErrors" : [ ],
    "nInserted" : 0,
    "nUpserted" : 1,
    "nMatched" : 0,
    "nModified" : 0,
    "nRemoved" : 0,
    "upserted" : [
            {
                    "index" : 0,
                    "_id" : 2
            }
    ]
})

具有相同日期的第二次迭代:

BulkWriteResult({
    "writeErrors" : [ ],
    "writeConcernErrors" : [ ],
    "nInserted" : 0,
    "nUpserted" : 0,
    "nMatched" : 1,
    "nModified" : 0,
    "nRemoved" : 0,
    "upserted" : [ ]
})

表示第二个操作只有一个“匹配”,但没有修改或up​​sert。

向后更改日期:

var date = new Date("2015-06-03");

BulkWriteResult({
    "writeErrors" : [ ],
    "writeConcernErrors" : [ ],
    "nInserted" : 0,
    "nUpserted" : 0,
    "nMatched" : 1,
    "nModified" : 0,
    "nRemoved" : 0,
    "upserted" : [ ]
})

同样只有第二个语句匹配但没有修改或up​​sert。

更改日期:

var date = new Date("2015-08-03");

BulkWriteResult({
    "writeErrors" : [ ],
    "writeConcernErrors" : [ ],
    "nInserted" : 0,
    "nUpserted" : 0,
    "nMatched" : 2,
    "nModified" : 1,
    "nRemoved" : 0,
    "upserted" : [ ]
})

显示两个操作都匹配,但是只有“一个”修改来自第一个操作,因为$setOnInsert没有执行_id值存在的任何操作。

您需要的是“两个”语句来强制执行您的逻辑,但Bulk Operations API允许您同时向服务器发送“both”并收到单个响应。

答案 1 :(得分:0)

对于那些偶然发现并尝试在ReactiveMongo中进行类似查询的人(就像我一样),基于@ BlakesSeven的excellent answer,我做了以下操作以在Scala中成功运行查询:

val cmd = BSONDocument(
      "update" -> collectionName,
      "updates" -> BSONArray(

        BSONDocument(
          "q" -> BSONDocument(
            "_id" -> newId),
          "u" -> BSONDocument("$setOnInsert" -> BSONDocument("data" -> newData)),
          "upsert" -> true),
          BSONDocument(
          "q" -> BSONDocument(
            "_id" -> newId,
            "timestamp" -> BSONDocument("$lt" -> newTimestamp)),
          "u" -> BSONDocument(
            "$set" -> BSONDocument("data" -> newData)))
      )
    )

   db.command(RawCommand(cmd)) map (_ => // whatever)