2013-03-14 11 views
5

Używam mongoose.js do wykonywania zapytań do mongodb, ale myślę, że mój problem nie jest specyficzny dla mongoose.js.MongoDB: wybierz dopasowane elementy subcollection

Say mam tylko jeden rekord w kolekcji:

var album = new Album({ 
    tracks: [{ 
     title: 'track0', 
     language: 'en', 

    },{ 
     title: 'track1', 
     language: 'en', 
    },{ 
     title: 'track2', 
     language: 'es', 
    }] 
}) 

Chcę wybrać wszystkie utwory z zakresu języka równe 'en', więc próbowałem dwóch wariantach:

Album.find({'tracks.language':'en'}, {'tracks.$': 1}, function(err, albums){ 

i przywiązany do tego samego z projekcją $ elemMatch:

Album.find({}, {tracks: {$elemMatch: {'language': 'en'}}}, function(err, albums){ 

w obu przypadkach mam taki sam wynik:

{tracks:[{title: 'track0', language: 'en'}]} 

wybrane album.tracks zawierać tylko jeden element toru z tytułowym 'track0' (ale nie powinno być zarówno 'track0', 'track1'):

{tracks:[{title: 'track0', language: 'en'}, {title: 'track1', language: 'en'}]} 

Co robię źle?

+4

nie robisz nic złego, to po prostu sposób działają te pytania - obejmują one tylko pierwszy element pasujący. Myślę, że musisz użyć struktury agregacji, aby zrobić to, co chcesz. – JohnnyHK

+0

Dzięki, mógłbyś podać moją bardziej konkretną wskazówkę czy coś takiego? – WHITECOLOR

Odpowiedz

11

Jak już powiedziałeś @JohnnyHK, będziesz musiał użyć struktury agregacji, aby to osiągnąć, ponieważ zarówno $, jak i $elemMatch zwracają tylko pierwsze dopasowanie.

Oto jak:

db.Album.aggregate(
    // This is optional. It might make your query faster if you have 
    // many albums that don't have any English tracks. Take a larger 
    // collection and measure the difference. YMMV. 
    { $match: {tracks: {$elemMatch: {'language': 'en'}} } }, 

    // This will create an 'intermediate' document for each track 
    { $unwind : "$tracks" }, 

    // Now filter out the documents that don't contain an English track 
    // Note: at this point, documents' 'tracks' element is not an array 
    { $match: { "tracks.language" : "en" } }, 

    // Re-group so the output documents have the same structure, ie. 
    // make tracks a subdocument/array again 
    { $group : { _id : "$_id", tracks : { $addToSet : "$tracks" } }} 
); 

Można spróbować tej kwerendy zagregowane tylko pierwszego wyrazu, a następnie dodać linię wyrażenia przez kolejce, aby zobaczyć, jak wyjście jest zmieniona. Szczególnie ważne jest zrozumienie, w jaki sposób $unwind tworzy dokumenty pośrednie, które później są ponownie scalane przy użyciu $group i $addToSet.

Wyniki:

> db.Album.aggregate(
    { $match: {tracks: {$elemMatch: {'language': 'en'}} } }, 
    { $unwind : "$tracks" }, 
    { $match: { "tracks.language" : "en" } }, 
    { $group : { _id : "$_id", tracks : { $addToSet : "$tracks" } }} ); 
{ 
    "result" : [ 
      { 
        "_id" : ObjectId("514217b1c99766f4d210c20b"), 
        "tracks" : [ 
          { 
            "title" : "track1", 
            "language" : "en" 
          }, 
          { 
            "title" : "track0", 
            "language" : "en" 
          } 
        ] 
      } 
    ], 
    "ok" : 1 
} 
+0

Dziękuję bardzo! – WHITECOLOR

+0

Jeszcze jedno pokrewne pytanie, jeśli nie masz nic przeciwko temu, aby ograniczyć zaznaczone ścieżki do pola tytułu (nie ma innego pola jak język). – WHITECOLOR

+0

@mnemosyn Próbowałem tego rozwiązania na mój powiązany problem i zadziałało. Teraz muszę wrócić i zrozumieć to! – carbontax

Powiązane problemy