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

时间:2011-08-31 13:55:47

标签: mongodb mapreduce

我正在尝试使用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
        }
    }
}

7 个答案:

答案 0 :(得分:14)

目前无法实现,但我建议投票支持此案例:https://jira.mongodb.org/browse/SERVER-2517

答案 1 :(得分: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游标可以多次返回同一文档。...在此字段或这些字段上使用唯一索引,以便查询将返回每个文档不超过一次。使用提示()查询以明确强制查询以使用该索引。“

答案 2 :(得分:5)

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

您可以尝试运行使用

重塑数据的后期处理
results.find({}).forEach( function(result) {
  results.update({_id: result._id}, {count: result.value.count, paths: result.value.paths})
});
是的,这看起来很难看。我知道。

答案 3 :(得分: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}} ) } )};

答案 4 :(得分: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}} )
} );

答案 5 :(得分: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")

这比顺序更新更快。

答案 6 :(得分: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秒。