ejemplos - En MongoDB mapreduce, ¿cómo puedo aplanar el objeto de valores?
mongodb agregacion (7)
AFAIK, por diseño, el mapa de Mongo reduce los resultados en "tuplas de valor" y no he visto nada que configure ese "formato de salida". Tal vez el método finalize () puede ser utilizado.
Podría intentar ejecutar un proceso posterior que remodelará los datos usando
results.find({}).forEach( function(result) {
results.update({_id: result._id}, {count: result.value.count, paths: result.value.paths})
});
Sí, eso se ve feo. Lo sé.
Intento usar MongoDB para analizar los archivos de registro de Apache. Creé una colección de receipts
de los registros de acceso de Apache. Aquí hay un resumen abreviado de cómo se ven mis modelos:
db.receipts.findOne()
{
"_id" : ObjectId("4e57908c7a044a30dc03a888"),
"path" : "/videos/1/show_invisibles.m4v",
"issued_at" : ISODate("2011-04-08T00:00:00Z"),
"status" : "200"
}
issued_at
una función de MapReduce que agrupa todos los datos por el campo de fecha issued_at
. Resume el número total de solicitudes y proporciona un desglose del número de solicitudes para cada ruta única. Aquí hay un ejemplo de cómo se ve el resultado:
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
}
}
}
}
¿Cómo puedo hacer que el resultado se vea así en su lugar?
{
"_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
}
}
}
Actualmente no es posible, pero sugiero votar para este caso: https://jira.mongodb.org/browse/SERVER-2517 .
Al experimentar con la respuesta de Vincent, encontré un par de problemas. Básicamente, si realiza actualizaciones dentro de un bucle foreach, esto moverá el documento al final de la colección y el cursor alcanzará ese documento nuevamente ( example ). Esto se puede eludir si se usa $snapshot . Por lo tanto, estoy proporcionando un ejemplo de Java a continuación.
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: Este fragmento demora un promedio de 7.4 segundos para ejecutarse en mi máquina con 100k registros y 14 atributos (conjunto de datos IMDB). Sin procesamiento por lotes, toma un promedio de 25.2 segundos.
Puedes hacer el código de Dan con una referencia de colección:
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}} ) } )};
Todas las soluciones propuestas están lejos de ser óptimas. Lo más rápido que puedes hacer hasta ahora es algo así como:
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();
};
Luego llámalo: flattenMRCollection("MyDB","MyMRCollection")
Esto es mucho más rápido que hacer actualizaciones secuenciales.
Tomando lo mejor de respuestas y comentarios anteriores:
db.items.find().hint({_id: 1}).forEach(function(item) {
db.items.update({_id: item._id}, item.value);
});
Desde http://docs.mongodb.org/manual/core/update/#replace-existing-document-with-new-document
"Si el argumento de update
contiene solo pares de campo y valor, el método update()
reemplaza el documento existente con el documento en el argumento de update
, excepto para el campo _id
".
Por lo tanto, no es necesario $unset value
ni listar cada campo.
Desde https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/#cursor-snapshot "Los cursores MongoDB pueden devolver el mismo documento más de una vez en algunas situaciones ... use un índice único en este campo o estos campos para que la consulta devuelva cada documento no más de una vez. Consulta con hint () para forzar explícitamente a la consulta a usar ese índice ".
Un enfoque similar al de @ljonas, pero no es necesario codificar los campos 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}} )
} );