Удалить все поля, начинающиеся с имени «XX»

1

Пример документа из коллекции:

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

Мне интересно, есть ли лучший подход к этой проблеме.

Теги:
mongodb-query

1 ответ

0
Лучший ответ

Было бы "лучше" вместо этого заранее определить все возможные ключи, а затем выпустить одно "multi" обновление, чтобы удалить все ключи. В зависимости от имеющейся версии MongoDB были бы разные подходы.

MongoDB 3.4: $ objectToArray

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 для удаления этих полей из всех документов.

Предыдущие версии: mapReduce

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 МБ. Это довольно экстремально, но в зависимости от реальных данных это возможно.

Поэтому существуют по существу "два расширения", которые, естественно, применимы к методам:

  1. Используйте "курсор" с .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 МБ в качестве запроса.

  2. Используйте временную коллекцию с 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 })

Что совершенно верно, потому что все остальные заявления делают, это то, что эти имена должны быть для вас.

  • 0
    В моей ситуации общее количество записей велико, и уже известно, что имена команд никогда не будут одинаковыми. Если мы сохраним имена в самом начале, это заняло бы много места. Есть ли другой выбор?
  • 0
    @ xis Я не понимаю, что ты говоришь. Я только что дал вам три метода работы с «отличным» выбором имени поля на основе данных, представленных в вашем вопросе. На самом деле делать точно так, как просили, и не повторять каждый документ в процессе, потому что вам это не нужно. Ты не понимаешь? Внимательно прочитайте и объясните, если вы думаете, что нужно сделать что-то другое.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню