2013-12-09 8 views
7

Ho un documento con un array interno, in questo modo:

"userTags" : [ 
     "foo", 
     "foo", 
     "foo", 
     "foo", 
     "moo", 
     "bar" 
    ] 

Se effettuo db.products.update({criteriaToGetDocument}, {$push: {userTags: "foo"}}} posso inserire correttamente un'altra istanza foo nella matrice.

Tuttavia, se lo faccio db.products.update({criteriaToGetDocument}, {$pull: {userTags: "foo"}}} allora rimuove tutti istanze di foo dalla matrice, lasciandomi con:

"userTags" : [ 
     "moo", 
     "bar" 
    ] 

Così non va per niente, voglio solo tirare un elemento da la matrice piuttosto che tutti. Come posso modificare il comando in modo che venga rimosso solo uno foo? Esiste una sorta di metodo $pullOnce che può funzionare qui?

risposta

4

No, non c'è niente come questo al momento. Molte persone hanno già richiesto la funzione e la puoi rintracciare in mongodb Jira. Per quanto tu possa vedere, non è risolto e anche non programmato (il che significa che non hai fortuna nel prossimo futuro).

L'unica opzione è quella di utilizzare la logica dell'applicazione per raggiungere questo obiettivo potrebbe essere:

    elemento
  1. di ricerca desiderati e che ha usertags come foo
  2. iterare attraverso usertags e rimuovere una foo da esso
  3. aggiorna quell'elemento con un nuovo userTags

Ricorda che questa operazione rompe l'atomicità, ma poiché Mongo non ha fornito un metodo nativo per farlo, si romperà atomi città in alcun modo.

Ho spostato una soluzione alternativa alla nuova risposta, perché non risponde a questa domanda, ma rappresenta uno degli approcci per refactoring schema esistente. Inoltre è diventato così grande, che ha iniziato ad essere molto più grande della risposta originale.

+0

Questo è ... molto fastidioso. Suppongo che un altro modo sarebbe cambiare la mia progettazione dei dati in modo che 'userTags' sia un oggetto con' {tag: (numberofinstances) 'e semplicemente aumenti/diminuisca questo numero ogni volta che sia necessario, ma preferirei non farlo mentre fa casino con il sacco di altre cose! – Jascination

+0

Sono d'accordo con te. Sembra che questa funzione sia davvero utile e senza la documentazione di lettura si può interpretare male l'idea di $ pull. Ma le persone in mongo stanno facendo un buon lavoro aumentando le caratteristiche di mongo durante ogni rilascio. Quindi credo che un giorno vedrai questo fuori dagli schemi. Post scriptum - votare per la funzione nella JIRA. –

+0

Nice edit. Puoi suggerire qualsiasi comando che controlli il tag esistente quindi controlla se è maggiore di uno? O dovrei scoprire se il 'tag: {$ esiste: true}' in una ricerca quindi fare un aggiornamento con il nuovo numero di tag? – Jascination

1

Se si desidera veramente utilizzare questa funzione, è necessario prendere in considerazione la possibilità di modificare lo schema. Un modo per andare è fare qualcosa del genere:

"userTags" : { 
    "foo" : 4, 
    "moo": 1, 
    "bar": 1 
} 

Dove il numero è la quantità di tag. In questo modo puoi usare atomic $ inc per aggiungere o rimuovere il tag. Mentre aggiungere il nuovo tag può essere appropriato, mentre si eliminano i tag bisogna stare attenti. È necessario verificare che il tag esiste e che è> = 1. allora

ecco come si può fare:

db.a.insert({ 
    _id : 1, 
    "userTags" : { 
    "foo" : 4, 
    "moo": 1, 
    "bar": 1 
    } 
}) 

Per aggiungere un tag che devi fare proprio questo:

db.a.update({_id : 1}, {$inc : {'userTags.zoo' : 1}}) 

Se il tag non esisteva prima che creerà

per rimuovere il tag, è necessario fare un po 'di più:

db.a.update({ 
    _id : 1, 
    'userTags.moo' : {$gte : 1 } 
}, { 
    $inc : {'userTags.moo' : -1} 
}) 

Qui si controlla che l'elemento esista e sia più grande di 1 (non è necessario verificare l'esistenza perché $ gte sta facendo anche questo).

Ricordare che questo approccio ha uno svantaggio. Puoi avere alcuni tag che sono elencati come campi, ma non sono tag reali (hanno un valore di 0). Quindi, se si desidera trovare tutti gli elementi con tag pippo si deve ricordare questo e di fare qualcosa di simile:

db.a.find({'userTags.zoo' : { $gte : 1}}) 

Anche quando si uscita tutti i tag per un elemento, si potrebbe avere lo stesso problema. Quindi è necessario disinfettare l'output sul livello dell'applicazione.

0

Lo so che è una soluzione e non una vera e propria soluzione, ma, come mi sono imbattuto in questo stesso problema, ho scoperto che in PHP, almeno, si può fare qualcosa di simile:

// Remove up to two elements "bar" from an array called "foo" 
// in a document that matches the query {foo:"bar"} 

$un_bar_qty = 2; 
$un_bar_me = $db->foo->findOne(array("foo"=>"bar")); 
foreach($un_bar_me['bar'] as $foo => $bar) { 
    if($bar == 'bar') { 
     unset($un_bar_me['bar'][$foo]); 
     $un_bar_qty--; 
    } 
    if(!$un_bar_qty) break; 
} 
$db->foo->update(array('_id'=>$un_bar_me['_id']),$bar); 

documento prima:

{ 
    { foo: "bar" }, 
    { bar: [ "bar" , "bar" , "foo", "bar" ] } 
} 

documento dopo:

{ 
    { foo: "bar" }, 
    { baz: [ "foo", "bar" ] } 
} 

Non troppo male di un modo per farlo. Certamente sembrava meno complicato per me che andare in giro con tag e incrementi, YMMV.

Problemi correlati