我的文档结构如下:
{
_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
。
如何修复查询?
答案 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值不存在的情况下插入新记录。”
逻辑不能很好地融合到单个语句中,但是您可以使用“批量”操作在“同一时间”将两个操作发送到服务器。
所以它形成如下:
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" : [ ]
})
表示第二个操作只有一个“匹配”,但没有修改或upsert。
向后更改日期:
var date = new Date("2015-06-03");
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 0,
"nUpserted" : 0,
"nMatched" : 1,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
同样只有第二个语句匹配但没有修改或upsert。
更改日期:
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)