MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Filtr agregacji po $lookup

To pytanie dotyczy w rzeczywistości czegoś innego i nie wymaga $lookup w ogóle. Ale dla każdego, kto przybywa tutaj wyłącznie z tytułu „filtrowanie po $lookup”, oto techniki dla ciebie:

MongoDB 3.6 — podpotok

db.test.aggregate([
    { "$match": { "id": 100 } },
    { "$lookup": {
      "from": "test",
      "let": { "id": "$id" },
      "pipeline": [
        { "$match": {
          "value": "1",
          "$expr": { "$in": [ "$$id", "$contain" ] }
        }}
      ],
      "as": "childs"
    }}
])

Wcześniej – $wyszukaj + $odpręż + $dopasuj połączenie

db.test.aggregate([
    { "$match": { "id": 100 } },
    { "$lookup": {
        "from": "test",
        "localField": "id",
        "foreignField": "contain",
        "as": "childs"
    }},
    { "$unwind": "$childs" },
    { "$match": { "childs.value": "1" } },
    { "$group": {
        "_id": "$_id",
        "id": { "$first": "$id" },
        "value": { "$first": "$value" },
        "contain": { "$first": "$contain" },
        "childs": { "$push": "$childs" }
     }}
])

Jeśli zastanawiasz się, dlaczego miałbyś $unwind w przeciwieństwie do używania $filter na tablicy, a następnie przeczytaj Aggregate $lookup Całkowity rozmiar dokumentów w dopasowanym potoku przekracza maksymalny rozmiar dokumentu, aby poznać wszystkie szczegóły, dlaczego jest to ogólnie konieczne i znacznie bardziej optymalne.

W przypadku wydań MongoDB 3.6 i późniejszych, bardziej wyrazisty „podpotok” jest zazwyczaj tym, co chcesz „filtrować” wyniki obcego zbioru, zanim cokolwiek w ogóle zostanie zwrócone do tablicy.

Wracając do odpowiedzi, która właściwie opisuje, dlaczego zadane pytanie w ogóle nie wymaga dołączania....

Oryginał

Korzystanie z $lookup w ten sposób nie jest to najbardziej „efektywny” sposób robienia tego, co chcesz tutaj. Ale o tym później.

Jako podstawową koncepcję, po prostu użyj $filter na wynikowej 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" ] }
           }
        }
    }}
]);

Lub użyj $redact zamiast tego:

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"
        }
    }}
]);

Oba uzyskują 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 ]
    }
  ]
}

Najważniejsze jest to, że $lookup samo nie może "jeszcze" wysyłać zapytań, aby wybrać tylko określone dane. Tak więc całe "filtrowanie" musi nastąpić po $lookup

Ale tak naprawdę dla tego typu „samodzielnego dołączenia” lepiej nie używać $lookup w ogóle i unikając narzutów związanych z dodatkowym odczytem i „scalaniem skrótów”. Po prostu pobierz powiązane elementy i $group zamiast tego:

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 ] }
      }
    }
  }}
])

Co wychodzi tylko trochę inaczej, ponieważ celowo usunąłem obce pola. Dodaj je w sobie, 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 tutaj "filtrowanie" dowolnych null wynik z tablicy utworzonej, gdy bieżący dokument był parent w przetwarzaniu elementów do $push .

Wydaje się również, że brakuje Ci tutaj tego, że wynik, którego szukasz, w ogóle nie wymaga agregacji ani „podzapytań”. Struktura, którą zawarłeś lub prawdopodobnie znalazłeś gdzie indziej, jest „zaprojektowana” tak, abyś mógł uzyskać „węzeł” i wszystkie jego „dzieci” w jednym żądaniu zapytania.

Oznacza to, że tylko „zapytanie” jest wszystkim, co jest naprawdę potrzebne, a zbieranie danych (czyli wszystko, co się dzieje, ponieważ żadna zawartość nie jest tak naprawdę „redukowana”) jest tylko funkcją iteracji 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ę musisz zrobić, to wydać „pojedyncze” zapytanie, aby wybrać zarówno rodzica, jak i dzieci. Zwrócone dane są takie same, a wszystko, co robisz na serwerze lub kliencie, to "masowanie" do innego zebranego formatu.

Jest to jeden z tych przypadków, w których możesz „przyłapać się” na myśleniu o tym, jak robiłeś rzeczy w „relacyjnej” bazie danych i nie zdawać sobie sprawy, że ponieważ sposób przechowywania danych „zmienił się”, nie musisz już używać to samo podejście.

Dokładnie o to chodzi w przykładzie dokumentacji "Modelowe struktury drzew z odniesieniami podrzędnymi" w swojej strukturze, która ułatwia wybór rodziców i dzieci w ramach jednego zapytania.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Nie można znaleźć modułu '../build/Release/bson'] kod:'MODULE_NOT_FOUND' } js-bson:Nie udało się załadować rozszerzenia bson c++, używając czystej wersji JS

  2. MongoDB $indexOfArray

  3. Definiowanie schematu Mongoose na bieżąco z „opisu” w formacie JSON

  4. Aktualizacja/zmiana o manguście?

  5. Redis lub Mongo do określania, czy liczba mieści się w zakresach?