MongoDB:使用同一文档中的数据更新文档

78

我有一个文档列表,每个文档都有纬度和经度属性(以及其他属性)。

{ 'lat': 1, 'lon': 2, someotherdata [...] } 
{ 'lat': 4, 'lon': 1, someotherdata [...] }
[...]

我想修改它,使其看起来像这样:

{ 'coords': {'lat': 1, 'lon': 2}, someotherdata [...]} 
{ 'coords': {'lat': 4, 'lon': 1}, someotherdata [...]}
[...]
到目前为止,我已经写出了这个代码:
db.events.update({}, {$set : {'coords': {'lat': db.events.lat, 'lon': db.events.lon}}}, false, true)

但它将 db.events.latdb.events.lon 视为字符串。我该如何引用文档的属性?

Cheers.


4
这里有一个功能请求:https://jira.mongodb.org/browse/SERVER-458 - Thilo
6个回答

214

更新: 如果您只需要更改文档的结构而不更改值,请参见gipset的答案中的一个很好的解决方案。


根据 Update文档页面上 (现在已无法使用)的评论,您不能从update()内部引用当前文档的属性。

您将不得不遍历所有文档并像这样更新它们:

db.events.find().snapshot().forEach(
  function (e) {
    // update document, using its own properties
    e.coords = { lat: e.lat, lon: e.lon };

    // remove old properties
    delete e.lat;
    delete e.lon;

    // save the updated document
    db.events.save(e);
  }
)

根据您的需求,这样的函数也可以在Map-Reduce作业或服务器端db.eval()作业中使用。


7
根据类似问题的评论,我发现如果不对查找结果进行snapshot()处理,则会在某些情况下使游标进入无限循环。尝试使用以下代码:`db.events.find().snapshot().forEach(` `// ..function goes here` `)`以另一个字段的值更新字段。 - Ashley Raiteri
@AshleyRaiteri 谢谢,我已经在答案中包含了对 snapshot() 的调用。很好的发现 :) - Niels van der Rest
3
针对Mongo 4.0版本,使用.hint({_id: 1})来替代.snapshot() - twksos
漂亮的回答。在 updateMany 中查询和这个有什么性能上的区别? - Eduardo Pignatelli
自Mongo 3.6起已被弃用 http://mongodb.github.io/mongo-java-driver/3.8/javadoc/deprecated-list.html - Elisabeth Shevtsova
显示剩余3条评论

57
$rename 运算符(在此问题发布一个月后推出)使得执行这种类型的操作变得非常简单,您无需修改值即可完成。 插入一些测试文档
db.events.insert({ 'lat': 1, 'lon': 2, someotherdata: [] })
db.events.insert({ 'lat': 4, 'lon': 1, someotherdata: [] })

使用$rename运算符

db.events.update({}, {$rename: {'lat': 'coords.lat', 'lon': 'coords.lon'}}, false, true)

结果

db.events.find()
{
    "_id" : ObjectId("5113c82dd28c4e8b79971add"),
    "coords" : {
        "lat" : 1,
        "lon" : 2
    },
    "someotherdata" : [ ]
}
{
    "_id" : ObjectId("5113c82ed28c4e8b79971ade"),
    "coords" : {
        "lat" : 4,
        "lon" : 1
    },
    "someotherdata" : [ ]
}

6

尼尔的答案。只是想让人们知道,如果你像Robomongo一样在远程shell上运行它,你不能在大型数据库上运行它。你需要ssh进入您实际服务器的mongo shell。此外,如果您更愿意进行更新,也可以这样做。

db.Collection.find({***/ possible query /***}).toArray().forEach(
  function(obj){
    obj.item = obj.copiedItem;
    obj.otherItem = obj.copiedItem;
    obj.thirdItem = true;
    obj.fourthItem = "string";
    db.Collection.update({_id: obj._id}, obj);
  }
);

这只是对Niels van der Rest发布的答案的评论吗? - Vince Bowdren
这是一条评论,但我想发布一个比您可以在添加评论部分中留下的更详细的答案。我还想发布我所使用的代码,基于他的答案,以便如果有人懒得去查找那些链接,这里有一个可工作的模型。当然,它只适用于服务器或者如果您只需要更新<101个文档,则在远程shell中运行。有些人可能不认为“保存”是最好的操作。 - mjwrazor

3
我们可以使用Mongo脚本动态操纵数据。这对我很管用!我使用这个脚本来修正我的地址数据。当前地址示例:"No.12, FIFTH AVENUE,"。我想要删除最后一个多余的逗号,期望得到的新地址为"No.12, FIFTH AVENUE"。
var cursor = db.myCollection.find().limit(100);

while (cursor.hasNext()) {
  var currentDocument = cursor.next();

  var address = currentDocument['address'];
  var lastPosition = address.length - 1;

  var lastChar = address.charAt(lastPosition);

  if (lastChar == ",") {

    var newAddress = address.slice(0, lastPosition);


    currentDocument['address'] = newAddress;

    db.localbizs.update({_id: currentDocument._id}, currentDocument);

  }
}

希望这可以帮上忙!

3
只要你可以创建数据副本,聚合框架就可以作为替代方案使用。您也可以选择使用其他运算符对数据进行更多操作,但您唯一需要的是$project。这在空间上有些浪费,但在某些情况下可能更快,更适合使用。为了说明,我将首先向foo集合插入一些示例数据:
db.foo.insert({ 'lat': 1, 'lon': 2, someotherdata : [1, 2, 3] })
db.foo.insert({ 'lat': 4, 'lon': 1, someotherdata : [4, 5, 6] })

现在,我们只需使用$project重新处理latlon字段,然后将它们发送到newfoo集合:

db.foo.aggregate([
    {$project : {_id : "$_id", "coords.lat" : "$lat", "coords.lon" : "$lon", "someotherdata" : "$someotherdata" }},
    { $out : "newfoo" }
])

然后检查newfoo以获取我们修改过的数据:

db.newfoo.find()
{ "_id" : ObjectId("544548a71b5cf91c4893eb9a"), "someotherdata" : [ 1, 2, 3 ], "coords" : { "lat" : 1, "lon" : 2 } }
{ "_id" : ObjectId("544548a81b5cf91c4893eb9b"), "someotherdata" : [ 4, 5, 6 ], "coords" : { "lat" : 4, "lon" : 1 } }

一旦您对新数据满意,您可以使用renameCollection()命令删除旧数据,并在旧名称下使用新数据:

> db.newfoo.renameCollection("foo", true)
{ "ok" : 1 }
> db.foo.find()
{ "_id" : ObjectId("544548a71b5cf91c4893eb9a"), "someotherdata" : [ 1, 2, 3 ], "coords" : { "lat" : 1, "lon" : 2 } }
{ "_id" : ObjectId("544548a81b5cf91c4893eb9b"), "someotherdata" : [ 4, 5, 6 ], "coords" : { "lat" : 4, "lon" : 1 } }

最后一点注意事项 - 在 SERVER-7944 完成之前,您不能像这个答案中建议的那样通过提示 _id 索引来执行快照操作,因此如果其他地方的活动导致文档移动,则可能会多次访问文档。 由于在此示例中插入了 _id 字段,任何这种情况都会导致唯一键冲突,因此您不会得到重复项,但可能会有一个“旧”版本的文档。 始终在删除数据之前仔细检查数据,并最好备份。


0

从CLI?我认为您首先必须提取值并将该值分配给变量。然后运行更新命令。

或者(我没有尝试过)从字符串中删除“db”。events.latevents.lon如果可以的话,您仍将拥有多个值,“lat”和“lon”的旧值以及您创建的新数组。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接