W MongoDB mapreduce jak spłaścić obiekt values?

Próbuję użyć MongoDB do analizy plików dziennika Apache. Stworzyłem receipts kolekcję z dzienników dostępu Apache. Oto skrócone podsumowanie tego, jak wyglądają moje modele:

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

Napisałem funkcję MapReduce, która grupuje wszystkie dane według pola issued_at date. Podsumowuje całkowitą liczbę żądań i przedstawia podział liczby żądań dla każdej unikalnej ścieżki. Oto przykład jak wygląda wyjście:

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
            }
        }
    }
}

Jak mogę wykonać wyjście wygląda tak:

{
    "_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
        }
    }
}
Author: nelstrom, 2011-08-31

7 answers

Obecnie nie jest to możliwe, ale proponuję zagłosować za tą sprawą: https://jira.mongodb.org/browse/SERVER-2517 .

 13
Author: mstearn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-31 14:29:45

Biorąc najlepsze z poprzednich odpowiedzi i komentarzy:

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

Od http://docs.mongodb.org/manual/core/update/#replace-existing-document-with-new-document
"Jeśli argument update zawiera tylko pary pola i wartości, Metoda update() zastępuje istniejący dokument dokumentem w argumencie update, z wyjątkiem pola _id."

Więc nie musisz ani $unset value, ani wypisywać każdego pola.

Od https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/#cursor-snapshot "W niektórych sytuacjach Kursory MongoDB mogą zwrócić ten sam dokument więcej niż jeden raz. ... użyj unikalnego indeksu na tym polu lub tych polach, aby zapytanie zwróciło każdy dokument nie więcej niż jeden raz. Zapytanie za pomocą metody hint (), aby jawnie wymusić użycie tego indeksu."

 7
Author: Denis Ryzhkov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-28 23:10:19

AFAIK, według projektu Mongo 's Map reduce będzie wypluwał wyniki w "krotkach wartości" i nie widziałem nic, co skonfigurowałoby ten "format wyjściowy". Może można użyć metody finalize ().

Możesz spróbować uruchomić proces końcowy, który zmieni kształt danych za pomocą

results.find({}).forEach( function(result) {
  results.update({_id: result._id}, {count: result.value.count, paths: result.value.paths})
});
Tak, to wygląda brzydko. Wiem.
 5
Author: Ijonas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-31 14:23:42

Możesz zrobić Kod Dana z odniesieniem do kolekcji:

    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
Author: Craig Lebowitz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-31 05:27:22

Podobne podejście do @ljonas, ale bez potrzeby twardego kodowania pól dokumentów:

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
Author: Dan Tenenbaum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-03 00:12:53

Wszystkie proponowane rozwiązania są dalekie od optymalnych. Najszybsze jak do tej pory to coś w stylu:

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();
};

To nazwij to: flattenMRCollection("MyDB","MyMRCollection")

Jest to o wiele szybsze niż wykonywanie sekwencyjnych aktualizacji.
 3
Author: Vincent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-09 20:44:47

Eksperymentując z odpowiedzią Vincenta, znalazłem kilka problemów. Zasadniczo, jeśli wykonasz aktualizacje w pętli foreach, przeniesie to dokument na koniec kolekcji, a kursor ponownie dotrze do tego dokumentu ( przykład ). Można to obejść, jeśli użyto $snapshot. Dlatego poniżej podaję przykład Javy.

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);
}

Uwaga: wykonanie tego fragmentu zajmuje średnio 7.4 sekund na moim komputerze z rekordami 100k i atrybutami 14 (IMDB dataset). Bez dozowania zajmuje to średnio 25,2 sekundy.

 0
Author: Dhruv Gairola,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-04 23:07:52