Пример документа из коллекции:
{ "teamAlpha": { }, "teamBeta": { }, "leader_name": "leader" }
Для такого документа я хотел бы удалить все поля, начинающиеся с "team"
. Таким образом, ожидаемый результат
{leader_name: "leader"}
В настоящее время я использую функцию:
db.teamList.find().forEach(
function(document) {
for(var k in document) {
if (k.startsWith('team')) {
delete document[k];
}
}
db.teamList.save(document);
}
);
Мне интересно, есть ли лучший подход к этой проблеме.
Было бы "лучше" вместо этого заранее определить все возможные ключи, а затем выпустить одно "multi"
обновление, чтобы удалить все ключи. В зависимости от имеющейся версии MongoDB были бы разные подходы.
let fields = db.teamList.aggregate([
{ "$project": {
"_id": 0,
"fields": {
"$map": {
"input": {
"$filter": {
"input": { "$objectToArray": "$$ROOT" },
"as": "d",
"cond": { "$eq": [{ "$substrCP": [ "$$d.k", 0, 4 ] }, "team" ] }
}
},
"as": "f",
"in": "$$f.k"
}
}
}},
{ "$unwind": "$fields" },
{ "$group": { "_id": "$fields" } }
])
.map( d => ({ [d._id]: "" }))
.reduce((acc,curr) => Object.assign(acc,curr),{})
db.teamList.updateMany({},{ "$unset": fields });
Оператор .aggregate()
превращает поля в документ в массив через $objectToArray
а затем применяет $filter
чтобы возвращать только те, где первые четыре буквы "ключа" соответствуют строке "team"
. Затем он обрабатывается с помощью $unwind
и $group
для создания "уникального списка" соответствующих полей.
Последующие инструкции просто обрабатывают этот список, возвращаемый в курсор, в один объект, например:
{
"teamBeta" : "",
"teamAlpha" : ""
}
Затем передается значение $unset
для удаления этих полей из всех документов.
var fields = db.teamList.mapReduce(
function() {
Object.keys(this).filter( k => /^team/.test(k) )
.forEach( k => emit(k,1) );
},
function() {},
{ "out": { "inline": 1 } }
)
.results.map( d => ({ [d._id]: "" }))
.reduce((acc,curr) => Object.assign(acc,curr),{})
db.teamList.update({},{ "$unset": fields },{ "multi": true });
Та же самая основная вещь, где продемонстрирована единственная разница: где .updateMany()
не существует как метод, который мы просто вызываем .update()
используя параметр "multi"
применимый ко всем согласованным документам. На самом деле это новый вызов API.
Разумеется, нецелесообразно перебирать все документы просто для удаления полей, и поэтому любой из указанных выше будет "предпочтительным" подходом. Единственным возможным сбоем является то, что построение "особого списка" ключей фактически превышает предел BSON 16 МБ. Это довольно экстремально, но в зависимости от реальных данных это возможно.
Поэтому существуют по существу "два расширения", которые, естественно, применимы к методам:
Используйте "курсор" с .aggregate()
var fields = [];
db.teamList.aggregate([
{ "$project": {
"_id": 0,
"fields": {
"$map": {
"input": {
"$filter": {
"input": { "$objectToArray": "$$ROOT" },
"as": "d",
"cond": { "$eq": [{ "$substrCP": [ "$$d.k", 0, 4 ] }, "team" ] }
}
},
"as": "f",
"in": "$$f.k"
}
}
}},
{ "$unwind": "$fields" },
{ "$group": { "_id": "$fields" } }
]).forEach( d => {
fields.push(d._id);
if ( fields.length >= 2000 ) {
db.teamList.updateMany({},
{ "$unset":
fields.reduce((acc,curr) => Object.assign(acc,{ [curr]: "" }),{})
}
);
}
});
if ( fields.length > 0 ) {
db.teamList.updateMany({},
{ "$unset":
fields.reduce((acc,curr) => Object.assign(acc,{ [curr]: "" }),{})
}
);
}
Там, где это по существу "запустило" количество полей, обработанных "курсором", на много 2000, что "должно" оставаться в соответствии с лимитом BSON 16 МБ в качестве запроса.
Используйте временную коллекцию с mapReduce()
db.teamList.mapReduce(
function() {
Object.keys(this).filter( k => /^team/.test(k) )
.forEach( k => emit(k,1) );
},
function() {},
{ "out": { "replace": "tempoutput" } }
);
db.tempoutput.find({},{ "_id": 1 }).forEach(d => {
fields.push(d._id);
if ( fields.length >= 2000 ) {
db.teamList.update({},
{ "$unset":
fields.reduce((acc,curr) => Object.assign(acc,{ [curr]: "" }),{})
},
{ "multi": true }
);
}
});
if ( fields.length > 0 ) {
db.teamList.update({},
{ "$unset":
fields.reduce((acc,curr) => Object.assign(acc,{ [curr]: "" }),{})
},
{ "multi": true }
);
}
Если это опять же тот же самый процесс, за исключением того, что mapReduce
не может выводить на "курсор", вам нужно вывести во временную коллекцию, состоящую только из "отдельных имен полей", а затем переместите курсор из этой коллекции, чтобы обработать тот же "пакетный" способ.
Подобно аналогичным первоначальным подходам, это гораздо более эффективные варианты, чем повторение всей коллекции и внесение корректировок в каждый документ отдельно. Как правило, это не обязательно, поскольку вероятность того, что любой "отдельный список" фактически приведет к тому, что один запрос на обновление превысит 16 МБ, действительно будет экстремальным. Но это снова будет "предпочтительным" способом справиться с таким экстремальным случаем.
Конечно, если вы просто знаете все имена полей и не должны их обрабатывать, изучая коллекцию, просто напишите инструкцию с известными именами:
db.teamList.update({},{ "$unset": { "teamBeta": "", "teamAlpha": "" } },{ "multi": true })
Что совершенно верно, потому что все остальные заявления делают, это то, что эти имена должны быть для вас.