У 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}});

Чи є більш прямий шлях?


Відповіді:


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

Наступна відповідь є більш прямою і працювала для мене. Хоча це не видалення за індексом, а швидше за значенням.
Віш

1
@Javier - хіба це рішення не залишить вас відкритими для питань, якщо db змінюється між часом, коли ви підрахували позицію в індексі, і часом, коли ви зробили скасування?
UpTheCreek

Це не атомно, тому, ймовірно, це спричинить незрозумілі умови перегонів, якщо у вас є будь-який код, який не готовий впоратися з нульовим записом у масиві.
Гленн Мейнард,

3
8 років, і цей квиток досі не реалізований ...
Оллі Джон,

19

Ви можете використовувати $pullмодифікатор updateоперації для видалення певного елемента з масиву. Якщо ви надали запит, це виглядатиме так:

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

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

Це не використовує індекс як критерій для видалення елемента, але все одно може допомогти у випадках, подібних до вашого. IMO, використання індексів для адресації елементів всередині масиву не дуже надійне, оскільки mongodb не узгоджується з порядком елементів настільки швидко, наскільки я знаю.


6
Його елемент - масив у питанні. У цьому випадку Монго відповідає порядку. Насправді всі документи насправді є замовленими колекціями, але багато водіїв не обробляють їх таким чином. Ще більше ускладнюючи справу, якщо документ потрібно перемістити після операції оновлення (через зростання, що перевищує коефіцієнт заповнення), порядок клавіш раптом стає алфавітним (під обкладинками, я думаю, вони відсортовані за двійковим значенням, ця підозра базуючись на моїх знаннях, що масиви - це майже стандартні документи з ключами, які є послідовними цілими числами замість рядків).
marr75

Це дозволяє уникнути проблем unset + pull, але вимагає суворого впорядкування вашого словника. Словники більшості мов не впорядковані, тому це може спричинити біль.
Гленн Мейнард,

@ marr75: У вас є довідка? Документи Монго завжди повинні зберігати порядок, і якщо вони коли-небудь змінюватимуть піддокументи, багато речей порушуватиметься.
Гленн Мейнард,

У мене немає довідки. Я знаю це з особистого досвіду роботи з 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 легко здійснити витяг із $ і переконатися, що буде витягнуто правильний. Те саме стосується інших операцій з масивом.


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, яка є функцією, яку слід застосувати, параметром якої є масив для модифікації. Функція тут просто полягає у використанні сплайсингу для видалення 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

Для людей, які шукають відповідь, використовуючи mongoose з 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 }); });
 }

Я можу переписати цю відповідь, якщо вона не дуже добре розуміє, але, на мою думку, це нормально.

Сподіваюся, це допоможе вам, я втратив багато часу, стикаючись з цією проблемою.


-3

Замість використання $ pull ми можемо використовувати $ pop для видалення елементів у масиві за його індексом. Але слід відняти 1 з позиції індексу для видалення на основі індексу.

Наприклад, якщо ви хочете видалити елемент з індексу 0, ви повинні використовувати -1, для індексу 1 - 0 і так далі ...

Запит на видалення третього елемента (гаджети):

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

для довідки: https://docs.mongodb.com/manual/reference/operator/update/pop/


3
Відповідно до пов'язаної документації, $popможна видалити лише перший або останній елемент із масиву. Запит у цій відповіді не видаляє третій елемент.
Тревіс Г.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.