2011-08-31 10 views
32

Sto cercando di utilizzare MongoDB per analizzare i file di registro di Apache. Ho creato una collezione receipts dai log di accesso di Apache. Ecco una sintesi ridotta di quello che i miei modelli assomigliano:In maprouce MongoDB, come posso appiattire l'oggetto valori?

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

Ho scritto una MapReduce function che raggruppa tutti i dati in base al campo issued_at data. Riepiloga il numero totale di richieste e fornisce una suddivisione del numero di richieste per ciascun percorso univoco. Ecco un esempio di ciò che l'output appare come:

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

Come posso fare l'aspetto output come questo, invece:

{ 
    "_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 
     } 
    } 
} 

risposta

12

Attualmente non è possibile, ma suggerirei di votare per questo caso: https://jira.mongodb.org/browse/SERVER-2517.

+0

Questa è la risposta giusta, quindi votare per questo caso e quindi utilizzare http://stackoverflow.com/a/18124090/1402121 come soluzione – dmo

4

per quanto ne so, in base alla progettazione mappa di Mongo ridurre sputerà risultati in "tuple di valore "e non ho visto nulla che configuri quel" formato di output ". Forse può essere usato il metodo finalize().

Si potrebbe provare ad eseguire un post-processo che rimodellare i dati utilizzando

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

Yep, che sembra brutto. Lo so.

+0

Non c'è un modo per modificare direttamente l'oggetto/documento 'risultato'? –

3

Un approccio simile a quello di @ljonas ma c'è bisogno di codificare i campi del documento:

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

Si può fare il codice di Dan con un riferimento collezione:

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

Prendendo il meglio dai precedenti risposte e commenti:

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

Da http://docs.mongodb.org/manual/core/update/#replace-existing-document-with-new-document
"Se l'argomento update contiene solo coppie di campi e valori, il metodo update() sostituisce il documento esistente con il documento nell'argomento update, ad eccezione del campo _id".

Quindi non è necessario né a $unset value né elencare ciascun campo.

Da https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/#cursor-snapshot "cursori MongoDB può restituire lo stesso documento più di una volta in alcune situazioni. ... utilizzare un indice univoco su questo campo o di questi campi in modo che la query restituirà ogni documento non più di una volta. Query con hint() per forzare esplicitamente la query a utilizzare quell'indice. "

+0

Ciò porta a condizioni di gara gravi. –

+0

@DerekBrown, grazie, l'ho corretto ora –

+0

no non l'hai fatto ... –

3

Tutte le soluzioni proposte sono tutt'altro che ottimali. Il più veloce è possibile fare finora è qualcosa di simile:

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

Poi lo chiamano: flattenMRCollection("MyDB","MyMRCollection")

questo è il modo più veloce di fare aggiornamenti sequenziali.

+3

NB: questo è nuovo in MongoDB 2.6 – Vincent

0

Mentre sperimentavo la risposta di Vincent, ho trovato un paio di problemi.Fondamentalmente, se si eseguono gli aggiornamenti all'interno di un ciclo foreach, questo sposta il documento alla fine della raccolta e il cursore raggiungerà nuovamente quel documento (example). Questo può essere aggirato se viene utilizzato $snapshot. Quindi, sto fornendo un esempio Java di seguito.

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

Nota: Questo frammento prende una media di 7,4 secondi per l'esecuzione sulla mia macchina con 100k record e 14 attributi (IMDB dataset). Senza batch, ci vogliono in media 25,2 secondi.

Problemi correlati