在MongoDB mapreduce中,如何展平值对象?

问题描述 投票:34回答:7

我正在尝试使用MongoDB来分析Apache日志文件。我从Apache访问日志中创建了一个receipts集合。以下是我的模型外观的精简摘要:

db.receipts.findOne()
{
    "_id" : ObjectId("4e57908c7a044a30dc03a888"),
    "path" : "/videos/1/show_invisibles.m4v",
    "issued_at" : ISODate("2011-04-08T00:00:00Z"),
    "status" : "200"
}

我写了一个MapReduce function,它按照issued_at日期字段对所有数据进行分组。它总结了请求的总数,并提供了每个唯一路径的请求数的细分。以下是输出结果的示例:

db.daily_hits_by_path.findOne()
{
    "_id" : ISODate("2011-04-08T00:00:00Z"),
    "value" : {
        "count" : 6,
        "paths" : {
            "/videos/1/show_invisibles.m4v" : {
                "count" : 2
            },
            "/videos/1/show_invisibles.ogv" : {
                "count" : 3
            },
            "/videos/6/buffers_listed_and_hidden.ogv" : {
                "count" : 1
            }
        }
    }
}

如何使输出看起来像这样:

{
    "_id" : ISODate("2011-04-08T00:00:00Z"),
    "count" : 6,
    "paths" : {
        "/videos/1/show_invisibles.m4v" : {
            "count" : 2
        },
        "/videos/1/show_invisibles.ogv" : {
            "count" : 3
        },
        "/videos/6/buffers_listed_and_hidden.ogv" : {
            "count" : 1
        }
    }
}
mongodb mapreduce
7个回答
14
投票

目前还不可能,但我建议投票支持这个案例:https://jira.mongodb.org/browse/SERVER-2517


7
投票

从以前的答案和评论中获取最佳效果:

db.items.find().hint({_id: 1}).forEach(function(item) {
    db.items.update({_id: item._id}, item.value);
});

来自http://docs.mongodb.org/manual/core/update/#replace-existing-document-with-new-document “如果update参数仅包含字段和值对,则update()方法将现有文档替换为update参数中的文档,但_id字段除外。”

所以你既不需要$unset value,也不需要列出每个字段。

来自https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/#cursor-snapshot“在某些情况下,MongoDB游标可以多次返回同一文档。...在此字段或这些字段上使用唯一索引,以便查询将返回每个文档不超过一次。使用提示()查询强制查询使用该索引。“


5
投票

AFAIK,按照设计Mongo的map reduce会在“值元组”中吐出结果,而我没有看到任何可以配置“输出格式”的东西。也许可以使用finalize()方法。

您可以尝试运行将使用重新整形数据的后处理

results.find({}).forEach( function(result) {
  results.update({_id: result._id}, {count: result.value.count, paths: result.value.paths})
});

是的,看起来很难看。我知道。


4
投票

您可以使用集合参考来执行Dan的代码:

    function clean(collection) { 
      collection.find().forEach( function(result) {
      var value = result.value;
      delete value._id;     
      collection.update({_id: result._id}, value);     
      collection.update({_id: result.id}, {$unset: {value: 1}} ) } )};

3
投票

与@ljonas类似的方法,但不需要硬编码文档字段:

db.results.find().forEach( function(result) {
    var value = result.value;
    delete value._id;
    db.results.update({_id: result._id}, value);
    db.results.update({_id: result.id}, {$unset: {value: 1}} )
} );

3
投票

所有提出的解决方案都远非最佳。到目前为止,你能做到的最快的是:

var flattenMRCollection=function(dbName,collectionName) {
    var collection=db.getSiblingDB(dbName)[collectionName];

    var i=0;
    var bulk=collection.initializeUnorderedBulkOp();
    collection.find({ value: { $exists: true } }).addOption(16).forEach(function(result) {
        print((++i));
        //collection.update({_id: result._id},result.value);

        bulk.find({_id: result._id}).replaceOne(result.value);

        if(i%1000==0)
        {
            print("Executing bulk...");
            bulk.execute();
            bulk=collection.initializeUnorderedBulkOp();
        }
    });
    bulk.execute();
};

然后称之为:flattenMRCollection("MyDB","MyMRCollection")

这比顺序更新更快。


0
投票

在尝试Vincent的回答时,我发现了一些问题。基本上,如果在foreach循环中执行更新,则会将文档移动到集合的末尾,并且光标将再次到达该文档(example)。如果使用$snapshot,可以规避这一点。因此,我在下面提供一个Java示例。

final List<WriteModel<Document>> bulkUpdate = new ArrayList<>();

// You should enable $snapshot if performing updates within foreach
collection.find(new Document().append("$query", new Document()).append("$snapshot", true)).forEach(new Block<Document>() {
    @Override
    public void apply(final Document document) {
        // Note that I used incrementing long values for '_id'. Change to String if
        // you used string '_id's
        long docId = document.getLong("_id");
        Document subDoc = (Document)document.get("value");
        WriteModel<Document> m = new ReplaceOneModel<>(new Document().append("_id", docId), subDoc);
        bulkUpdate.add(m);

        // If you used non-incrementing '_id's, then you need to use a final object with a counter.
        if(docId % 1000 == 0 && !bulkUpdate.isEmpty()) {
            collection.bulkWrite(bulkUpdate);
            bulkUpdate.removeAll(bulkUpdate);
        }
    }
});
// Fixing bug related to Vincent's answer.
if(!bulkUpdate.isEmpty()) {
    collection.bulkWrite(bulkUpdate);
    bulkUpdate.removeAll(bulkUpdate);
}

注意:此代码段平均需要7.4秒才能在我的计算机上执行,包含100k条记录和14个属性(IMDB数据集)。没有批处理,平均需要25.2秒。

© www.soinside.com 2019 - 2024. All rights reserved.