В MongoDB mapreduce как сгладить объект значений? - PullRequest
34 голосов
/ 31 августа 2011

Я пытаюсь использовать MongoDB для анализа файлов журнала Apache. Я создал коллекцию receipts из журналов доступа Apache. Вот краткое описание того, как выглядят мои модели:

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

Я написал MapReduce функцию , которая группирует все данные по полю 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 ]

14 голосов
/ 31 августа 2011

В настоящее время это невозможно, но я бы предложил проголосовать за этот случай: https://jira.mongodb.org/browse/SERVER-2517.

7 голосов
/ 08 августа 2013

Взяв лучшее из предыдущих ответов и комментариев:

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 могут возвращать один и тот же документ более одного раза в некоторых ситуациях. ... использовать уникальный индекс для этого поля или этих полей, чтобы запрос возвращал каждый документ не более одного раза. Запрос с помощью hint ()явно заставить запрос использовать этот индекс. "

5 голосов
/ 31 августа 2011

AFAIK, по замыслу уменьшение карты Монго будет выводить результаты в «кортежи значений», и я не видел ничего, что настраивало бы этот «формат вывода».Возможно, можно использовать метод finalize ().

Вы можете попытаться запустить пост-процесс, который преобразует данные, используя

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

Да, что выглядит ужасно.Я знаю.

4 голосов
/ 31 января 2013

Вы можете сделать код Дэна со ссылкой на коллекцию:

    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 голосов
/ 09 августа 2014

Все предложенные решения далеки от оптимальных. Самое быстрое, что вы можете сделать, это что-то вроде:

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")

Это намного быстрее, чем последовательные обновления.

3 голосов
/ 03 января 2013

Подход похож на @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}} )
} );
0 голосов
/ 11 октября 2015

Экспериментируя с ответом Винсента, я обнаружил пару проблем.В основном, если вы выполняете обновления в цикле foreach, это переместит документ в конец коллекции, и курсор снова достигнет этого документа ( пример ).Это можно обойти, если использовать $ 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 секунды для выполнения на моей машине с 100 тыс. Записей и 14 атрибутами (набор данных IMDB).Без пакетирования это занимает в среднем 25,2 секунды.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...