W mongoDb, jak usunąć element tablicy według jego indeksu?

97

W poniższym przykładzie załóżmy, że dokument znajduje się w kolekcji db.people .

Jak usunąć trzeci element tablicy zainteresowań według jego indeksu ?

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

Oto moje obecne rozwiązanie:

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

Czy jest bardziej bezpośredni sposób?

dannie.f
źródło

Odpowiedzi:

138

Nie ma prostego sposobu pobierania / usuwania przez indeks tablicy. W rzeczywistości jest to sprawa otwarta http://jira.mongodb.org/browse/SERVER-1014 , możesz na nią głosować.

Sposób obejścia problemu polega na użyciu $ unset, a następnie $ pull:

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

Aktualizacja: jak wspomniano w niektórych komentarzach, to podejście nie jest atomowe i może powodować pewne sytuacje wyścigu, jeśli inni klienci czytają i / lub zapisują między dwiema operacjami. Jeśli potrzebujemy atomowej operacji, możemy:

  • Przeczytaj dokument z bazy danych
  • Zaktualizuj dokument i usuń element z tablicy
  • Zastąp dokument w bazie danych. Aby upewnić się, że dokument nie zmienił się od czasu jego przeczytania, możemy użyć aktualizacji, jeśli aktualny wzorzec jest opisany w dokumentacji mongo
Javier Ferrero
źródło
33
Minęło półtora roku? Czy taki stan rzeczy nadal dotyczy mongodb?
Abe,
Następna odpowiedź jest bardziej bezpośrednia i zadziałała dla mnie. Chociaż nie jest to usuwanie według indeksu, ale raczej usuwanie według wartości.
vish
1
@Javier - czy to rozwiązanie nie pozostawiłoby Cię otwartym na problemy, gdyby db zmienił się między momentem zliczenia pozycji w indeksie, a chwilą, gdy nie ustawiłeś?
UpTheCreek,
To nie jest atomowe, więc prawdopodobnie spowoduje niejasne warunki wyścigu, jeśli masz kod, który nie jest przygotowany do obsługi pustego wpisu w tablicy.
Glenn Maynard,
3
8 lat, a ten bilet nadal nie został zrealizowany ...
Olly John
19

Możesz użyć $pullmodyfikatora updateoperacji do usunięcia określonego elementu z tablicy. W przypadku, gdy podane zapytanie będzie wyglądać następująco:

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

Możesz również rozważyć użycie $pullAlldo usunięcia wszystkich wystąpień. Więcej na ten temat na oficjalnej stronie dokumentacji - http://www.mongodb.org/display/DOCS/Updating#Updating-%24pull

Nie używa to indeksu jako kryterium usuwania elementu, ale nadal może pomóc w przypadkach podobnych do Twojego. IMO, używanie indeksów do adresowania elementów wewnątrz tablicy nie jest zbyt niezawodne, ponieważ mongodb nie jest spójny w kolejności elementów tak fas, jak wiem.

Sunseeker
źródło
6
Jego elementem jest tablica w pytaniu. W tym przypadku Mongo jest zgodne z zamówieniem. W rzeczywistości wszystkie dokumenty są w rzeczywistości zamówionymi zbiorami, ale wielu kierowców nie obsługuje ich w ten sposób. Co jeszcze bardziej komplikuje sprawę, jeśli dokument ma zostać przeniesiony po operacji aktualizacji (z powodu wzrostu poza współczynnik wypełnienia), kolejność kluczy nagle staje się alfabetyczna (pod okładkami, myślę, że są sortowane według wartości binarnej, to podejrzenie jest opierając się na mojej wiedzy, że tablice są w zasadzie standardowymi dokumentami z kluczami będącymi kolejnymi liczbami całkowitymi zamiast łańcuchami).
marr75
Pozwala to uniknąć problemów związanych z unset + pull, ale wymaga ścisłego uporządkowania słownika. Słowniki w większości języków są nieuporządkowane, więc może to być trudne.
Glenn Maynard
@ marr75: Czy masz referencje? Dokumenty Mongo mają zawsze zachowywać porządek, a jeśli kiedykolwiek zmieniłby kolejność subdokumentów, wiele rzeczy by się zepsuło.
Glenn Maynard,
Nie mam odniesienia. Wiem to z własnego doświadczenia w 2.2, po naprawieniu błędów wprowadzonych przez zaktualizowane i przeniesione dokumenty. Nie robiłem zbyt wiele pracy z mongo w ciągu ostatnich kilku miesięcy, więc nie mogę mówić do bardziej aktualnych wersji.
marr75
Jeśli chodzi o spójność elementów, moim pożądanym przypadkiem użycia jest. Klient żąda json od mongo, a następnie, jeśli zdecyduje się powiedzieć „usuń” element z tablicy json, przekaże indeks tablicy do serwera, który ma zostać usunięty. gdyby pozycja elementów tablicy została przesunięta, byłoby to dziwne, ale wyjaśniałoby, dlaczego nie mogą zaimplementować tej funkcji
meffect
4

Zamiast używać nieustawionego (jak w zaakceptowanej odpowiedzi), rozwiązuję to, ustawiając pole na unikalną wartość (tj. Nie NULL), a następnie natychmiast wyciągając tę ​​wartość. Trochę bezpieczniejsze z asynchronicznego punktu widzenia. Oto kod:

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

Miejmy nadzieję, że mongo rozwiąże ten problem, być może poprzez rozszerzenie modyfikatora $ position tak, aby obsługiwał zarówno $ pull, jak i $ push.

Stephen Orr
źródło
3

Zalecałbym użycie pola GUID (zwykle używam ObjectID) lub pola automatycznego zwiększania wartości dla każdego dokumentu podrzędnego w tablicy.

Dzięki temu identyfikatorowi GUID łatwo jest wydać polecenie $ pull i upewnić się, że zostanie wyciągnięty właściwy. To samo dotyczy innych operacji tablicowych.

Punkt kulminacyjny
źródło
2

Począwszy Mongo 4.4od$function operator agregacji umożliwia zastosowanie niestandardowej funkcji javascript w celu zaimplementowania zachowania nieobsługiwanego przez język zapytań MongoDB.

Na przykład, aby zaktualizować tablicę poprzez usunięcie elementu o podanym indeksie:

// { "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 przyjmuje 3 parametry:

  • body, która jest funkcją do zastosowania, której parametrem jest tablica do zmodyfikowania. Funkcja tutaj polega po prostu na użyciu splotu do usunięcia 1 elementu o indeksie 2.
  • args, który zawiera pola z rekordu, które bodyfunkcja przyjmuje jako parametr. W naszym przypadku "$interests".
  • lang, czyli język, w którym bodynapisana jest funkcja. Tylko jsjest obecnie dostępne.
Xavier Guihot
źródło
2

w Mongodb 4.2 możesz to zrobić:

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

P to indeks elementu, który chcesz usunąć z tablicy.

Jeśli chcesz usunąć z P do końca:

db.example.update({}, [
     {$set: {field: {$slice: ["$field", P]}}
]);
Ali
źródło
0

Dla osób, które szukają odpowiedzi używając mangusty z nodejs. Tak to robię.

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

Mogę przepisać tę odpowiedź, jeśli nie rozumiem zbyt dobrze, ale myślę, że jest w porządku.

Mam nadzieję, że ci to pomoże, straciłem dużo czasu na zmierzenie się z tym problemem.

Schwarz54
źródło
-3

Zamiast używać $ pull, możemy użyć $ pop do usuwania elementów z tablicy według jej indeksu. Ale należy odjąć 1 od pozycji indeksu w celu usunięcia na podstawie indeksu.

Na przykład, jeśli chcesz usunąć element z indeksu 0, powinieneś użyć -1, dla indeksu 1 powinieneś użyć 0 i tak dalej ...

Zapytanie o usunięcie trzeciego elementu (gadżetów):

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

dla odniesienia: https://docs.mongodb.com/manual/reference/operator/update/pop/

Mohan Krishnan
źródło
4
Zgodnie z dołączoną dokumentacją $popmoże usunąć tylko pierwszy lub ostatni element z tablicy. Zapytanie w tej odpowiedzi nie usuwa trzeciego elementu.
Travis G.,