Ответ 1
В настоящее время это невозможно, но я бы предложил голосовать за этот случай: https://jira.mongodb.org/browse/SERVER-2517.
Я пытаюсь использовать 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
}
}
}
В настоящее время это невозможно, но я бы предложил голосовать за этот случай: https://jira.mongodb.org/browse/SERVER-2517.
Взяв лучшее из предыдущих ответов:
db.items.find().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
, ни список каждого поля.
AFAIK, по дизайну карта Mongo уменьшит выплевывает результаты в "кортежах значений", и я не видел ничего, что будет настраивать этот "выходной формат". Может быть использован метод finalize().
Вы можете попробовать выполнить пост-процесс, который изменит данные с помощью
results.find({}).forEach( function(result) {
results.update({_id: result._id}, {count: result.value.count, paths: result.value.paths})
});
Да, это выглядит уродливо. Я знаю.
Вы можете сделать код Дэн с помощью ссылки коллекции:
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}} ) } )};
Аналогичный подход к @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}} )
} );
Все предлагаемые решения далеки от оптимальных. Самое быстрое, что вы можете сделать до сих пор, это что-то вроде:
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")
Это ПУТЬ быстрее, чем выполнение последовательных обновлений.
Во время эксперимента с ответом Винсента я обнаружил пару проблем. В основном, если вы выполняете обновления в цикле 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 answer.
if(!bulkUpdate.isEmpty()) {
collection.bulkWrite(bulkUpdate);
bulkUpdate.removeAll(bulkUpdate);
}
Примечание. Этот фрагмент занимает в среднем 7,4 секунды, чтобы выполнить на моей машине 100 тыс. записей и 14 атрибутов (набор данных IMDB). Без пакетной обработки это занимает в среднем 25,2 секунды.