2013-02-27 32 views
49

Ich habe Array in Subdokument wie dieseWie Filter Array in Subdokument mit MongoDB

{ 
    "_id" : ObjectId("512e28984815cbfcb21646a7"), 
    "list" : [ 
     { 
      "a" : 1 
     }, 
     { 
      "a" : 2 
     }, 
     { 
      "a" : 3 
     }, 
     { 
      "a" : 4 
     }, 
     { 
      "a" : 5 
     } 
    ] 
} 

Kann ich Subdokument für eine> 3

Mein Filterergebnis erwarten unter

{ 
    "_id" : ObjectId("512e28984815cbfcb21646a7"), 
    "list" : [ 
     { 
      "a" : 4 
     }, 
     { 
      "a" : 5 
     } 
    ] 
} 

Ich versuche, zu verwenden $elemMatch aber gibt das erste übereinstimmende Element im Array zurück

Meine Frage:

db.test.find({ _id" : ObjectId("512e28984815cbfcb21646a7") }, { 
    list: { 
     $elemMatch: 
      { a: { $gt:3 } 
      } 
    } 
}) 

Das Ergebnis Rückkehr ein Element in Array

{ "_id" : ObjectId("512e28984815cbfcb21646a7"), "list" : [ { "a" : 4 } ] } 

und ich versuche mit $match Aggregat zu verwenden, aber nicht

db.test.aggregate({$match:{_id:ObjectId("512e28984815cbfcb21646a7"), 'list.a':{$gte:5} }}) 

arbeiten Es ist das Rück alle Element in Array

{ 
    "_id" : ObjectId("512e28984815cbfcb21646a7"), 
    "list" : [ 
     { 
      "a" : 1 
     }, 
     { 
      "a" : 2 
     }, 
     { 
      "a" : 3 
     }, 
     { 
      "a" : 4 
     }, 
     { 
      "a" : 5 
     } 
    ] 
} 

Kann ich Element in Array filtern, um das Ergebnis als Ergebnis zu erhalten?

Antwort

77

aggregate Verwendung ist der richtige Ansatz, aber Sie müssen die list Array $unwind vor dem $match so anwenden, dass Sie einzelne Elemente auswählen, und dann $group verwenden, um sie wieder zusammen:

db.test.aggregate(
    { $match: {_id: ObjectId("512e28984815cbfcb21646a7")}}, 
    { $unwind: '$list'}, 
    { $match: {'list.a': {$gt: 3}}}, 
    { $group: {_id: '$_id', list: {$push: '$list.a'}}}) 

Ausgänge :

{ 
    "result": [ 
    { 
     "_id": ObjectId("512e28984815cbfcb21646a7"), 
     "list": [ 
     4, 
     5 
     ] 
    } 
    ], 
    "ok": 1 
} 

MongoDB 3.2 Update

Beginnend mit der Version 3.2, können Sie die neue $filter Aggregationsoperator diese effizienter die list Elemente nur zu tun, auch Sie während eines $project wollen:

db.test.aggregate([ 
    { $match: {_id: ObjectId("512e28984815cbfcb21646a7")}}, 
    { $project: { 
     list: {$filter: { 
      input: '$list', 
      as: 'item', 
      cond: {$gt: ['$$item.a', 3]} 
     }} 
    }} 
]) 
+0

Ist die Aggregation Betrieb das Dokument ändern oder ist es nur eine Auswahl durchführen? – Cherif

+2

@Cherif 'aggregate' wirkt sich nicht auf das Dokument selbst aus. – JohnnyHK

+0

Wie würden Sie vorgehen, wenn Sie das Ergebnis dieser Abfrage auf dem Client veröffentlichen möchten? – samson

8

Above Lösung funktioniert am besten, wenn mehrere passende Unter Dokumente sind erforderlich. $elemMatch kommt auch in sehr verwenden, wenn einzelne Anpassungsunter Dokument als Ausgabe erforderlich ist

db.test.find({list: {$elemMatch: {a: 1}}}, {'list.$': 1}) 

Ergebnis:

{ 
    "_id": ObjectId("..."), 
    "list": [{a: 1}] 
} 
+0

Das war die beste Lösung für mich. Aber das zweite Argument funktioniert nur mit primitiven Typen. Da ich ein Objekt im Array hatte, war meine Lösung: 'ShoppingCartModel.findOne ({" _ id ": ctx.params.idCart," active ": true, Produkte: {$ elemMatch: {" products.active ": true} }}) ' (Ich filter nur die active: true-Wagen und Produkte) –

2

Verwenden $filter aggregation

eine Untergruppe des Arrays Wählt auf Rückgabe basierend der spezifizierte Zustand. Gibt ein Array mit nur den Elementen zurück, die der Bedingung entsprechen. Die zurückgegebenen Elemente sind in der ursprünglichen Reihenfolge.

db.test.aggregate([ 
    {$match: {"list.a": {$gt:3}}}, // <-- match only the document which have a matching element 
    {$project: { 
     list: {$filter: { 
      input: "$list", 
      as: "list", 
      cond: {$gt: ["$$list.a", 3]} //<-- filter sub-array based on condition 
     }} 
    }} 
]); 
Verwandte Themen