MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Филтър за агрегиране след $lookup

Въпросът тук всъщност е за нещо различно и не се нуждае от $lookup изобщо. Но за всеки, който пристига тук само от заглавието на „филтриране след $lookup“, това са техниките за вас:

MongoDB 3.6 – Под-тръбопровод

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

По-рано – $lookup + $unwind + $match coalescence

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

Ако се питате защо бихте $unwind за разлика от използването на $filter в масива, след което прочетете Aggregate $lookup Общият размер на документите в съответстващия тръбопровод надвишава максималния размер на документа за всички подробности относно това защо това обикновено е необходимо и далеч по-оптимално.

За версии на MongoDB 3.6 и по-нови, тогава по-изразителният „под-тръбопровод“ обикновено е това, което искате да „филтрирате“ резултатите от чуждата колекция, преди изобщо нещо да бъде върнато в масива.

Обратно към отговора, който всъщност описва защо зададеният въпрос изобщо не се нуждае от „без присъединяване“...

Оригинал

Използване на $lookup като това не е най-ефикасният начин да правите това, което искате тук. Но повече за това по-късно.

Като основна концепция просто използвайте $filter върху получения масив:

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

Или използвайте $redact вместо това:

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

И двете получават един и същ резултат:

{  
  "_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 ]
    }
  ]
}

В крайна сметка $lookup самата не може "все още" да прави заявка, за да избере само определени данни. Така че цялото "филтриране" трябва да се случи след $lookup

Но наистина за този тип "само присъединяване" е по-добре да не използвате $lookup изобщо и избягване на допълнителните разходи за допълнително четене и "хеш-сливане" изцяло. Просто извлечете свързаните елементи и $group вместо това:

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

Което излиза малко по-различно, защото нарочно премахнах външните полета. Добавете ги в себе си, ако наистина искате:

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

Така че единственият реален проблем тук е "филтрирането" на всеки null резултат от масива, създаден, когато текущият документ е бил parent при обработка на елементи към $push .

Това, което изглежда също ви липсва тук, е, че резултатът, който търсите, изобщо не се нуждае от агрегиране или „подзаявки“. Структурата, която сте сключили или евентуално сте намерили другаде, е „проектирана“ така, че да можете да получите „възел“ и всички негови „деца“ в една заявка за заявка.

Това означава, че само „заявката“ е всичко, което наистина е необходимо, а събирането на данни (което е всичко, което се случва, тъй като никакво съдържание не се „намалява“) е просто функция на повторение на резултата от курсора:

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);

Това прави точно същото нещо:

{
  "_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
      ]
    }
  ]
}

И служи като доказателство, че всичко, което наистина трябва да направите тук, е да издадете "единичната" заявка, за да изберете както родител, така и деца. Върнатите данни са едни и същи и всичко, което правите на сървър или клиент, е „масажиране“ в друг събран формат.

Това е един от онези случаи, в които можете да се „хванете“ да мислите как сте правили нещата в „релационна“ база данни и да не осъзнаете, че тъй като начинът, по който се съхраняват данните, се е „променил“, вече не е необходимо да използвате същия подход.

Точно това е смисълът на примера за документация „Моделни дървовидни структури с препратки към деца“ в неговата структура, където улеснява избора на родители и деца в рамките на една заявка.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. ScaleGrid обявява споделен хостинг на MongoDB на Amazon AWS

  2. Съвети за дистанционно управление на MongoDB

  3. Golang + MongoDB вграден тип (вграждане на структура в друга структура)

  4. Поставете Pandas Dataframe в mongodb с помощта на PyMongo

  5. db.collection не е функция, когато използвате MongoClient v3.0