2016-04-06 14 views
14

Jak mogę dodać filtr po sprawdzeniu $ lub czy jest jakaś inna metoda, aby to zrobić?

Moja próba zbierania danych jest:

{ "_id" : ObjectId("570557d4094a4514fc1291d6"), "id" : 100, "value" : "0", "contain" : [ ] } 
{ "_id" : ObjectId("570557d4094a4514fc1291d7"), "id" : 110, "value" : "1", "contain" : [ 100 ] } 
{ "_id" : ObjectId("570557d4094a4514fc1291d8"), "id" : 120, "value" : "1", "contain" : [ 100 ] } 
{ "_id" : ObjectId("570557d4094a4514fc1291d9"), "id" : 121, "value" : "2", "contain" : [ 100, 120 ] } 

wybiorę id 100 i agregują Childs:

db.test.aggregate([ { 
    $match : { 
    id: 100 
    } 
}, { 
    $lookup : { 
    from : "test", 
    localField : "id", 
    foreignField : "contain", 
    as : "childs" 
    } 
}]); 

wrócę:

{ 
    "_id":ObjectId("570557d4094a4514fc1291d6"), 
    "id":100, 
    "value":"0", 
    "contain":[ ], 
    "childs":[ { 
     "_id":ObjectId("570557d4094a4514fc1291d7"), 
     "id":110, 
     "value":"1", 
     "contain":[ 100 ] 
    }, 
    { 
     "_id":ObjectId("570557d4094a4514fc1291d8"), 
     "id":120, 
     "value":"1", 
     "contain":[ 100 ] 
    }, 
    { 
     "_id":ObjectId("570557d4094a4514fc1291d9"), 
     "id":121, 
     "value":"2", 
     "contain":[ 100, 120 ] 
    } 
    ] 
} 

ale chcę Childs tylko że dopasowanie z "wartością: 1"

Pod koniec Spodziewam ten wynik:

{ 
    "_id":ObjectId("570557d4094a4514fc1291d6"), 
    "id":100, 
    "value":"0", 
    "contain":[ ], 
    "childs":[ { 
     "_id":ObjectId("570557d4094a4514fc1291d7"), 
     "id":110, 
     "value":"1", 
     "contain":[ 100 ] 
    }, 
    { 
     "_id":ObjectId("570557d4094a4514fc1291d8"), 
     "id":120, 
     "value":"1", 
     "contain":[ 100 ] 
    } 
    ] 
} 

Odpowiedz

31

Korzystanie $lookup tak nie jest najbardziej „skuteczny” sposób zrobić to, co chcesz tutaj. Ale o tym później.

W podstawowej koncepcji, wystarczy użyć $filter na wynikającym z tablicy:

db.test.aggregate([ 
    { "$match": { "id": 100 } }, 
    { "$lookup": { 
     "from": "test", 
     "localField": "id", 
     "foreignField": "contain", 
     "as": "childs" 
    }}, 
    { "$project": { 
     "id": 1, 
     "value": 1, 
     "contain": 1, 
     "childs": { 
      "$filter": { 
       "input": "$childs", 
       "as": "child", 
       "cond": { "$eq": [ "$$child.value", "1" ] } 
      } 
     } 
    }} 
]); 

Albo użyć $redact zamiast:

db.test.aggregate([ 
    { "$match": { "id": 100 } }, 
    { "$lookup": { 
     "from": "test", 
     "localField": "id", 
     "foreignField": "contain", 
     "as": "childs" 
    }}, 
    { "$redact": { 
     "$cond": { 
      "if": { 
       "$or": [ 
       { "$eq": [ "$value", "0" ] }, 
       { "$eq": [ "$value", "1" ] } 
       ] 
      }, 
      "then": "$$DESCEND", 
      "else": "$$PRUNE" 
     } 
    }} 
]); 

Zarówno uzyskać ten sam wynik:

{ 
    "_id":ObjectId("570557d4094a4514fc1291d6"), 
    "id":100, 
    "value":"0", 
    "contain":[ ], 
    "childs":[ { 
     "_id":ObjectId("570557d4094a4514fc1291d7"), 
     "id":110, 
     "value":"1", 
     "contain":[ 100 ] 
    }, 
    { 
     "_id":ObjectId("570557d4094a4514fc1291d8"), 
     "id":120, 
     "value":"1", 
     "contain":[ 100 ] 
    } 
    ] 
} 

Dół linia jest taka, że ​​sama $lookup nie może "jeszcze" zapytać o wybierz niektóre dane. Więc wszystkie "filtrowanie" musi nastąpić po $lookup.

Ale tak naprawdę dla tego typu "samodzielnego łączenia" w ogóle lepiej nie używać $lookup w ogóle i całkowicie uniknąć dodatkowych kosztów odczytu i "mieszania". Wystarczy pobrać związane z nimi przedmioty i $group zamiast:

db.test.aggregate([ 
    { "$match": { 
    "$or": [ 
     { "id": 100 }, 
     { "contain.0": 100, "value": "1" } 
    ] 
    }}, 
    { "$group": { 
    "_id": { 
     "$cond": { 
     "if": { "$eq": [ "$value", "0" ] }, 
     "then": "$id", 
     "else": { "$arrayElemAt": [ "$contain", 0 ] } 
     } 
    }, 
    "value": { "$first": { "$literal": "0"} }, 
    "childs": { 
     "$push": { 
     "$cond": { 
      "if": { "$ne": [ "$value", "0" ] }, 
      "then": "$$ROOT", 
      "else": null 
     } 
     } 
    } 
    }}, 
    { "$project": { 
    "value": 1, 
    "childs": { 
     "$filter": { 
     "input": "$childs", 
     "as": "child", 
     "cond": { "$ne": [ "$$child", null ] } 
     } 
    } 
    }} 
]) 

Które tylko wychodzi trochę inaczej, bo celowo usunięte zbędne pola. Dodaj je do siebie, jeśli naprawdę chcesz:

{ 
    "_id" : 100, 
    "value" : "0", 
    "childs" : [ 
    { 
     "_id" : ObjectId("570557d4094a4514fc1291d7"), 
     "id" : 110, 
     "value" : "1", 
     "contain" : [ 100 ] 
    }, 
    { 
     "_id" : ObjectId("570557d4094a4514fc1291d8"), 
     "id" : 120, 
     "value" : "1", 
     "contain" : [ 100 ] 
    } 
    ] 
} 

Więc jedynym prawdziwym problemem jest tu „filtrowanie” wszelkie null wynik z tablicy stworzonej kiedy był obecny dokument z parent w punktach przerobu do $push.


Czego tu również brakuje, to to, że szukany wynik nie wymaga w ogóle agregacji lub "sub-zapytań". Struktura, którą zawarłeś lub ewentualnie znalazłeś w innym miejscu, została "zaprojektowana", abyś mógł otrzymać "węzeł" i wszystkie jego "dzieci" w jednym zapytaniu.

Oznacza to, że tylko "zapytanie" jest wszystkim, co jest naprawdę potrzebne, a zbieranie danych (co jest wszystkim, co dzieje się, ponieważ żadna zawartość nie jest naprawdę "zmniejszana") jest tylko funkcją iterowania wyniku kursora:

var result = {}; 

db.test.find({ 
    "$or": [ 
    { "id": 100 }, 
    { "contain.0": 100, "value": "1" } 
    ] 
}).sort({ "contain.0": 1 }).forEach(function(doc) { 
    if (doc.id == 100) { 
    result = doc; 
    result.childs = [] 
    } else { 
    result.childs.push(doc) 
    } 
}) 

printjson(result); 

To robi dokładnie to samo:

{ 
    "_id" : ObjectId("570557d4094a4514fc1291d6"), 
    "id" : 100, 
    "value" : "0", 
    "contain" : [ ], 
    "childs" : [ 
    { 
     "_id" : ObjectId("570557d4094a4514fc1291d7"), 
     "id" : 110, 
     "value" : "1", 
     "contain" : [ 
       100 
     ] 
    }, 
    { 
     "_id" : ObjectId("570557d4094a4514fc1291d8"), 
     "id" : 120, 
     "value" : "1", 
     "contain" : [ 
       100 
     ] 
    } 
    ] 
} 

i służy jako dowód, że wszystko, co naprawdę trzeba zrobić tutaj jest problem, który „single” query wybrać zarówno rodziców i dzieci. Zwrócone dane są takie same, a wszystko, co robisz na serwerze lub kliencie, "masuje" do innego zebranego formatu.

Jest to jeden z tych przypadków, w których można "złapać" myślenie o tym, jak robiłeś rzeczy w "relacyjnej" bazie danych, i nie zdawać sobie sprawy, że skoro sposób przechowywania danych "zmienił się", nie musisz dłużej trzeba korzystać z tego samego podejścia.

To jest dokładnie to, co jest punktem przykładu dokumentacji "Model Tree Structures with Child References" w jego strukturze, gdzie ułatwia wybór rodziców i dzieci w ramach jednego zapytania.

+0

Dzięki, ale kiedy patrzymy na przedstawienie. Czy zatem lepiej jest wykonać 2 pytania, jeden do pobrania dokumentu, a drugi do dziecka? –

+2

@PhillipBartschinski Jeśli chodzi o "wydajność", to '$ lookup' skutecznie wykonuje" dwa "zapytania, ale na" serwerze ". "Klient" może wykonywać kwerendy "dwa", ale oczywiście wiąże się z nim obciążenie sieci i reakcji, które spowolni proces. Stąd dlaczego robisz '$ lookup'. Ale mówię też, że twoja sprawa tego nie potrzebuje. Wszystkie elementy są już w tej samej kolekcji, więc po prostu wybierz je i '$ group' odpowiednio. Pod względem "wydajności" jest zdecydowanie lepszą opcją. –

+1

@PhillipBartschinski Myślę, że nadal tkwisz w "myśleniu relacyjnym", które sprawia, że ​​myślisz "sub-zapytanie" lub "samodzielne dołączanie", kiedy w rzeczywistości nie potrzebujesz. Dodano tu czystą metodę "bez agregacji", aby zademonstrować punkt. –