2016-03-15 24 views
10

Potrzebuję pobrać całą hierarchię pojedynczego obiektu z bazy danych jako JSON. W rzeczywistości propozycja dotycząca jakiegokolwiek innego rozwiązania pozwalającego osiągnąć ten wynik byłaby wysoce aprobowana. Postanowiłem użyć MongoDB z obsługą $ lookup.Wyszukiwanie zagnieżdżone MongoDB z 3 poziomami

Więc mam trzy kolekcje:

firm

{ "_id" : "2", "name" : "party2" } 
{ "_id" : "5", "name" : "party5" } 
{ "_id" : "4", "name" : "party4" } 
{ "_id" : "1", "name" : "party1" } 
{ "_id" : "3", "name" : "party3" }  

adres

{ "_id" : "a3", "street" : "Address3", "party_id" : "2" } 
{ "_id" : "a6", "street" : "Address6", "party_id" : "5" } 
{ "_id" : "a1", "street" : "Address1", "party_id" : "1" } 
{ "_id" : "a5", "street" : "Address5", "party_id" : "5" } 
{ "_id" : "a2", "street" : "Address2", "party_id" : "1" } 
{ "_id" : "a4", "street" : "Address4", "party_id" : "3" } 

addressComment

{ "_id" : "ac2", "address_id" : "a1", "comment" : "Comment2" } 
{ "_id" : "ac1", "address_id" : "a1", "comment" : "Comment1" } 
{ "_id" : "ac5", "address_id" : "a5", "comment" : "Comment6" } 
{ "_id" : "ac4", "address_id" : "a3", "comment" : "Comment4" } 
{ "_id" : "ac3", "address_id" : "a2", "comment" : "Comment3" } 

Muszę pobrać wszystkie strony z odpowiednimi adresami i komentarzami adresowymi jako częścią rekordu. Moja agregacja:

db.party.aggregate([{ 
    $lookup: { 
     from: "address", 
     localField: "_id", 
     foreignField: "party_id", 
     as: "address" 
    } 
}, 
{ 
    $unwind: "$address" 
}, 
{ 
    $lookup: { 
     from: "addressComment", 
     localField: "address._id", 
     foreignField: "address_id", 
     as: "address.addressComment" 
    } 
}]) 

Rezultat jest dość dziwny. Niektóre zapisy są w porządku. Brakuje Party z _id 4 (nie ma adresu). Ponadto w zestawie wyników są dwie osoby: 1, ale z innymi adresami:

{ 
    "_id": "1", 
    "name": "party1", 
    "address": { 
     "_id": "2", 
     "street": "Address2", 
     "party_id": "1", 
     "addressComment": [{ 
      "_id": "3", 
      "address_id": "2", 
      "comment": "Comment3" 
     }] 
    } 
}{ 
    "_id": "1", 
    "name": "party1", 
    "address": { 
     "_id": "1", 
     "street": "Address1", 
     "party_id": "1", 
     "addressComment": [{ 
      "_id": "1", 
      "address_id": "1", 
      "comment": "Comment1" 
     }, 
     { 
      "_id": "2", 
      "address_id": "1", 
      "comment": "Comment2" 
     }] 
    } 
}{ 
    "_id": "3", 
    "name": "party3", 
    "address": { 
     "_id": "4", 
     "street": "Address4", 
     "party_id": "3", 
     "addressComment": [] 
    } 
}{ 
    "_id": "5", 
    "name": "party5", 
    "address": { 
     "_id": "5", 
     "street": "Address5", 
     "party_id": "5", 
     "addressComment": [{ 
      "_id": "5", 
      "address_id": "5", 
      "comment": "Comment5" 
     }] 
    } 
}{ 
    "_id": "2", 
    "name": "party2", 
    "address": { 
     "_id": "3", 
     "street": "Address3", 
     "party_id": "2", 
     "addressComment": [{ 
      "_id": "4", 
      "address_id": "3", 
      "comment": "Comment4" 
     }] 
    } 
} 

Proszę, pomóżcie mi z tym. Jestem nowicjuszem w MongoDB, ale czuję, że może zrobić to, czego potrzebuję.

Odpowiedz

19

Przyczyną twoich "kłopotów" jest drugi etap agregacji - { $unwind: "$address" }. Usuwa rekord dla strony z _id: 4 (ponieważ jego tablica adresów jest pusta, jak wspomniałeś) i tworzy dwa rekordy dla stron _id: 1 i _id: 5 (ponieważ każdy z nich ma dwa adresy).

  • Aby zapobiec usuwaniu stron bez adresów należy ustawić preserveNullAndEmptyArrays opcję $unwind etapie true.

  • Aby zapobiec powielaniu stron dla różnych adresów, do procesu należy dodać etap agregacji $group. Ponadto należy użyć etapu $project z operatorem $filter, aby wykluczyć puste pozycje adresu w wynikach.

db.party.aggregate([{ 
    $lookup: { 
    from: "address", 
    localField: "_id", 
    foreignField: "party_id", 
    as: "address" 
    } 
}, { 
    $unwind: { 
    path: "$address", 
    preserveNullAndEmptyArrays: true 
    } 
}, { 
    $lookup: { 
    from: "addressComment", 
    localField: "address._id", 
    foreignField: "address_id", 
    as: "address.addressComment", 
    } 
}, { 
    $group: { 
    _id : "$_id", 
    name: { $first: "$name" }, 
    address: { $push: "$address" } 
    } 
}, { 
    $project: { 
    _id: 1, 
    name: 1, 
    address: { 
     $filter: { input: "$address", as: "a", cond: { $ifNull: ["$$a._id", false] } } 
    } 
    } 
}]); 
+0

Tank ty Shad! Jest jeden mały problem z zapisem 4 jednak: '{ \t "_id": "4", \t "name": "Imprezka! 4" \t "adres": [{ \t \t "addressComment": [] \t}] } ' }' } Jak widać - adres powinien być pusty, ale zamiast tego jest pusty ... Czy możemy pominąć adres, jeśli jego adres jest pusty? W innym przypadku ten adres będzie uznawany za rekord. – Yuriy

+1

W rzeczywistości widzę, że dostarczone rozwiązanie działa zgodnie z oczekiwaniami, zgodnie z opisem nowego pola "preserveNullAndEmptyArrays" dla operacji $ unwind (od wersji 3.2). Teraz możemy pominąć krok "$ project" i określić ten "$ unwind" zamiast prostego: '$ unwind: {path:" $ address ", zachowajNullAndEmptyArrays: true}'.Przyjmuję twoją odpowiedź, dziękuję za szybką i jasną odpowiedź! – Yuriy

+0

@Shad Mam dość podobne pytanie. Tutaj kod OP ma tylko jedną właściwość o nazwie 'name' w kolekcji' party' i stąd użyłeś '$ first', aby pobrać ją w' $ group'. Przypuśćmy, że mam 10 lub więcej właściwości, czy jest jakiś sposób, aby automatycznie uzyskać wszystkie właściwości, nie wspominając o każdej z nich indywidualnie? – Xyroid