Как в mongoDb удалить элемент массива по его индексу?

97

В следующем примере предположим, что документ находится в коллекции db.people .

Как удалить 3-й элемент массива интересов по его индексу ?

{
  "_id" : ObjectId("4d1cb5de451600000000497a"),           
  "name" : "dannie",  
  "interests" : [  
    "guitar",  
    "programming",           
    "gadgets",  
    "reading"  
  ]   
}

Это мое текущее решение:

var interests = db.people.findOne({"name":"dannie"}).interests;  
interests.splice(2,1)  
db.people.update({"name":"dannie"}, {"$set" : {"interests" : interests}});

Есть более прямой путь?

dannie.f
источник

Ответы:

138

Нет прямого способа извлечения / удаления по индексу массива. Фактически, это открытый вопрос http://jira.mongodb.org/browse/SERVER-1014 , вы можете проголосовать за него.

Обходной путь заключается в использовании $ unset, а затем $ pull:

db.lists.update({}, {$unset : {"interests.3" : 1 }}) 
db.lists.update({}, {$pull : {"interests" : null}})

Обновление: как упоминалось в некоторых комментариях, этот подход не является атомарным и может вызвать некоторые состояния гонки, если другие клиенты читают и / или записывают между двумя операциями. Если нам нужно, чтобы операция была атомарной, мы могли бы:

  • Прочитать документ из базы данных
  • Обновите документ и удалите элемент в массиве
  • Заменить документ в базе данных. Чтобы гарантировать, что документ не изменился с тех пор, как мы его прочитали, мы можем использовать обновление, если текущий шаблон описан в документации mongo
Хавьер Ферреро
источник
33
Прошло полтора года? Так обстоит дело с mongodb?
Abe
Следующий ответ более прямой и сработал для меня. Хотя это не удаление по индексу, а удаление по значению.
vish
1
@Javier - разве это решение не оставит вас открытым для проблем, если db изменится между временем, когда вы подсчитали позицию в индексе, и временем, когда вы сделали сброс?
UpTheCreek
Это не атомарно, поэтому может вызвать непонятные условия гонки, если у вас есть какой-либо код, который не готов справиться с нулевой записью в массиве.
Glenn Maynard
3
8 лет, а этот билет до сих пор не реализован ...
Олли Джон
19

Вы можете использовать $pullмодификатор updateоперации для удаления определенного элемента в массиве. Если вы предоставили запрос, он будет выглядеть так:

db.people.update({"name":"dannie"}, {'$pull': {"interests": "guitar"}})

Кроме того, вы можете рассмотреть возможность использования $pullAllдля удаления всех вхождений. Подробнее об этом на странице официальной документации - http://www.mongodb.org/display/DOCS/Updating#Updating-%24pull

Это не использует индекс в качестве критерия для удаления элемента, но все же может помочь в случаях, подобных вашему. ИМО, использование индексов для адресации элементов внутри массива не очень надежно, поскольку mongodb не согласован с порядком элементов так быстро, как я знаю.

Sunseeker
источник
6
Его элемент - это массив в вопросе. В этом случае Mongo соответствует порядку. Фактически, все документы представляют собой упорядоченные коллекции, но многие драйверы не обрабатывают их таким образом. Еще больше усложняет дело то, что если документ должен быть перемещен после операции обновления (из-за роста сверх коэффициента заполнения), порядок ключей внезапно становится алфавитным (под обложками, я думаю, они отсортированы по их двоичному значению, это подозрение основываясь на моих знаниях, массивы - это в значительной степени стандартные документы с ключами, которые являются последовательными целыми числами, а не строками).
marr75
Это позволяет избежать проблем типа unset + pull, но требует, чтобы ваш словарь был строго упорядочен. Словари на большинстве языков неупорядочены, поэтому это может быть проблемой.
Glenn Maynard
@ marr75: У вас есть ссылка? Документы Mongo всегда должны сохранять порядок, и если он когда-либо изменит порядок вложенных документов, многие вещи сломаются.
Glenn Maynard
У меня нет ссылки. Я знаю это по личному опыту работы с 2.2, когда были исправлены ошибки, возникающие при обновлении и перемещении документов. За последние несколько месяцев я мало работал с монго, поэтому не могу говорить о более актуальных версиях.
marr75
Что касается согласованности элементов, мой желаемый вариант использования. Клиент запрашивает json у mongo, а затем, если клиент решает сказать «удалить» элемент из массива json, он передает индекс массива на сервер для удаления. если бы позиция элементов массива сместилась, это было бы странно, но объяснило бы, почему они не могут реализовать эту функцию
meffect
4

Вместо того, чтобы использовать unset (как в принятом ответе), я решаю эту проблему, устанавливая для поля уникальное значение (т.е. не NULL), а затем сразу же извлекая это значение. Немного безопаснее с точки зрения асинхронности. Вот код:

    var update = {};
    var key = "ToBePulled_"+ new Date().toString();
    update['feedback.'+index] = key;
    Venues.update(venueId, {$set: update});
    return Venues.update(venueId, {$pull: {feedback: key}});

Надеюсь, mongo решит эту проблему, возможно, расширив модификатор $ position для поддержки $ pull, а также $ push.

Стивен Орр
источник
3

Я бы рекомендовал использовать поле GUID (я предпочитаю использовать ObjectID) или поле с автоинкрементом для каждого субдокумента в массиве.

С этим GUID легко выполнить запрос $ pull и быть уверенным, что будет извлечен правильный идентификатор. То же самое и с другими операциями с массивами.

Климакс
источник
2

Начиная Mongo 4.4с$function оператор агрегирования позволяет применять настраиваемую функцию javascript для реализации поведения, не поддерживаемого языком запросов MongoDB.

Например, чтобы обновить массив, удалив элемент по заданному индексу:

// { "name": "dannie", "interests": ["guitar", "programming", "gadgets", "reading"] }
db.collection.update(
  { "name": "dannie" },
  [{ $set:
    { "interests":
      { $function: {
          body: function(interests) { interests.splice(2, 1); return interests; },
          args: ["$interests"],
          lang: "js"
      }}
    }
  }]
)
// { "name": "dannie", "interests": ["guitar", "programming", "reading"] }

$function принимает 3 параметра:

  • body, которая представляет собой функцию, которую нужно применить, параметр которой является изменяемым массивом. Функция здесь просто заключается в использовании splice для удаления 1 элемента с индексом 2.
  • args, который содержит поля из записи, которые bodyфункция принимает в качестве параметра. В нашем случае "$interests".
  • lang, который является языком, на котором bodyнаписана функция. Доступно только jsв настоящее время.
Ксавье Гихот
источник
2

в Mongodb 4.2 вы можете сделать это:

db.example.update({}, [
     {$set: {field: {
           $concatArrays: [ 
                  {$slice: ["$field", P]}, 
                  {$slice: ["$field", {$add: [1, P]}, {$size: "$field"}]}
           ]
     }}}
]);

P - это индекс элемента, который вы хотите удалить из массива.

Если вы хотите удалить из P до конца:

db.example.update({}, [
     {$set: {field: {$slice: ["$field", P]}}
]);
Али
источник
0

Для людей, которые ищут ответ, используя мангуст с nodejs. Вот как я это делаю.

exports.deletePregunta = function (req, res) {
let codTest = req.params.tCodigo;
let indexPregunta = req.body.pregunta; // the index that come from frontend
let inPregunta = `tPreguntas.0.pregunta.${indexPregunta}`; // my field in my db
let inOpciones = `tPreguntas.0.opciones.${indexPregunta}`; // my other field in my db
let inTipo = `tPreguntas.0.tipo.${indexPregunta}`; // my  other field in my db

Test.findOneAndUpdate({ tCodigo: codTest },
    {
        '$unset': {
            [inPregunta]: 1, // put the field with [] 
            [inOpciones]: 1,
            [inTipo]: 1
        }
    }).then(()=>{ 
    Test.findOneAndUpdate({ tCodigo: codTest }, {
        '$pull': {
            'tPreguntas.0.pregunta': null,
            'tPreguntas.0.opciones': null,
            'tPreguntas.0.tipo': null
        }
    }).then(testModificado => {
        if (!testModificado) {
            res.status(404).send({ accion: 'deletePregunta', message: 'No se ha podido borrar esa pregunta ' });
        } else {
            res.status(200).send({ accion: 'deletePregunta', message: 'Pregunta borrada correctamente' });
        }
    })}).catch(err => { res.status(500).send({ accion: 'deletePregunta', message: 'error en la base de datos ' + err }); });
 }

Я могу переписать этот ответ, если он не очень хорошо понимает, но я думаю, что это нормально.

Надеюсь, это поможет вам, я потерял много времени, столкнувшись с этой проблемой.

Schwarz54
источник
-3

Вместо использования $ pull мы можем использовать $ pop для удаления элементов в массиве по его индексу. Но вы должны вычесть 1 из позиции индекса для удаления на основе индекса.

Например, если вы хотите удалить элемент в индексе 0, вы должны использовать -1, для индекса 1 вы должны использовать 0 и так далее ...

Запрос на удаление 3-го элемента (гаджеты):

db.people.update({"name":"dannie"}, {'$pop': {"interests": 1}})

для справки: https://docs.mongodb.com/manual/reference/operator/update/pop/

Мохан Кришнан
источник
4
Согласно связанной документации, $popможно удалить только первый или последний элемент из массива. Запрос в этом ответе не удаляет третий элемент.
Трэвис Г.