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

Вземете филтриран брой елементи в масива от $lookup заедно с целия документ

Анотация за тези, които търсят - Чуждестранна графа

Малко по-добре от първоначалния отговор е действително да се използва по-новата форма на $lookup от MongoDB 3.6. Това всъщност може да извърши „броенето“ в рамките на израза „подтръбопровод“ за разлика от връщането на „масив“ за последващо филтриране и преброяване или дори използване на $unwind

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "let": { "id": "$_id" },
    "pipeline": [
      { "$match": {
        "originalLink": "",
        "$expr": { "$eq": [ "$$id", "$_id" ] }
      }},
      { "$count": "count" }
    ],
    "as": "linkCount"    
  }},
  { "$addFields": {
    "linkCount": { "$sum": "$linkCount.count" }
  }}
])

Не това, което първоначалният въпрос искаше, а част от отговора по-долу в сега най-оптималната форма, като разбира се резултат от $lookup се редуцира до само броя на съвпаденията вместо „всички съвпадащи документи“.

Оригинал

Правилният начин да направите това е да добавите "linkCount" към $group етап, както и $first във всички допълнителни полета на основния документ, за да получите формата "единично число", както беше състоянието "преди" $unwind беше обработен в масива, който беше резултат от $lookup :

Всички подробности

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$unwind": "$link" },
  { "$match": { "link.originalLink": "" } },
  { "$group": {
    "_id": "$_id",
    "partId": { "$first": "$partId" },
    "link": { "$push": "$link" },
    "linkCount": {
      "$sum": {
        "$size": {
          "$ifNull": [ "$link.linkHistory", [] ]
        } 
      }   
    }
  }}
])

Произвежда:

{
    "_id" : ObjectId("594a6c47f51e075db713ccb6"),
    "partId" : "f56c7c71eb14a20e6129a667872f9c4f",
    "link" : [ 
        {
            "_id" : ObjectId("594b96d6f51e075db67c44c9"),
            "originalLink" : "",
            "emailGroupId" : ObjectId("594a6c47f51e075db713ccb6"),
            "linkHistory" : [ 
                {
                    "_id" : ObjectId("594b96f5f51e075db713ccdf")
                }, 
                {
                    "_id" : ObjectId("594b971bf51e075db67c44ca")
                }
            ]
        }
    ],
    "linkCount" : 2
}

Групиране по partId

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$unwind": "$link" },
  { "$match": { "link.originalLink": "" } },
  { "$group": {
    "_id": "$partId",
    "linkCount": {
      "$sum": {
        "$size": {
          "$ifNull": [ "$link.linkHistory", [] ]
        } 
      }   
    }
  }}
])

Произвежда

{
    "_id" : "f56c7c71eb14a20e6129a667872f9c4f",
    "linkCount" : 2
}

Причината да го направите по този начин с $unwind и след това $match се дължи на начина, по който MongoDB всъщност обработва тръбопровода, когато е издаден в този ред. Ето какво се случва с $lookup както е демонстрирано с "explain" изход от операцията:

    {
        "$lookup" : {
            "from" : "link",
            "as" : "link",
            "localField" : "_id",
            "foreignField" : "emailGroupId",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "originalLink" : {
                    "$eq" : ""
                }
            }
        }
    }, 
    {
        "$group" : {

Напускам частта с $group в този изход, за да демонстрира, че другите два етапа на тръбопровода „изчезват“. Това е така, защото те са били „събрани“ в $lookup етап на тръбопровода, както е показано. Това всъщност е начинът, по който MongoDB се справя с възможността лимитът на BSON да бъде надвишен от резултата от „присъединяването“ на резултатите от $lookup в масив от основния документ.

Можете алтернативно да напишете операцията по следния начин:

Всички подробности

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$addFields": {
    "link": {
      "$filter": {
        "input": "$link",
        "as": "l",
        "cond": { "$eq": [ "$$l.originalLink", "" ] }    
      }
    },
    "linkCount": {
      "$sum": {
        "$map": {
          "input": {
            "$filter": {
              "input": "$link",
              "as": "l",
              "cond": { "$eq": [ "$$l.originalLink", "" ] }
            }
          },
          "as": "l",
          "in": { "$size": { "$ifNull": [ "$$l.linkHistory", [] ] } }
        }     
      }
    }    
  }}
])

Групиране по partId

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$addFields": {
    "link": {
      "$filter": {
        "input": "$link",
        "as": "l",
        "cond": { "$eq": [ "$$l.originalLink", "" ] }    
      }
    },
    "linkCount": {
      "$sum": {
        "$map": {
          "input": {
            "$filter": {
              "input": "$link",
              "as": "l",
              "cond": { "$eq": [ "$$l.originalLink", "" ] }
            }
          },
          "as": "l",
          "in": { "$size": { "$ifNull": [ "$$l.linkHistory", [] ] } }
        }     
      }
    }    
  }},
  { "$unwind": "$link" },
  { "$group": {
    "_id": "$partId",
    "linkCount": { "$sum": "$linkCount" } 
  }}
])

Което има същия изход, но се „различава“ от първата заявка по това, че $filter тук се прилага "след" ВСИЧКИ резултати от $lookup се връщат в новия масив на родителския документ.

Така че от гледна точка на производителността всъщност е по-ефективно да го направите по първия начин, както и да бъдете преносими към възможни големи набори от резултати „преди филтриране“, което иначе би нарушило ограничението от 16MB BSON.

Като странична бележка за тези, които се интересуват, в бъдещи издания на MongoDB (вероятно 3.6 и по-нови) можете да използвате $replaceRoot вместо $addFields с използването на новия $mergeObjects оператор на тръбопровод. Предимството на това е, че като "блок", можем да декларираме "филтриран" съдържание като променлива чрез $let , което означава, че не е необходимо да пишете същия $filter "два пъти":

db.emailGroup.aggregate([
  { "$lookup": {
    "from": "link",
    "localField": "_id",
    "foreignField": "emailGroupId",
    "as": "link"    
  }},
  { "$replaceRoot": {
    "newRoot": {
      "$mergeObjects": [
        "$$ROOT",
        { "$let": {
          "vars": {
            "filtered": {
              "$filter": {
                "input": "$link",
                "as": "l",
                "cond": { "$eq": [ "$$l.originalLink", "" ] }    
              }
            }
          },
          "in": {
            "link": "$$filtered",
            "linkCount": {
              "$sum": {
                "$map": {
                  "input": "$$filtered.linkHistory",
                  "as": "lh",
                  "in": { "$size": { "$ifNull": [ "$$lh", [] ] } } 
                }   
              } 
            }  
          }
        }}
      ]
    }
  }}
])

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




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. използва за времето за създаване на mongodb ObjectId

  2. Виртуално попълване с Mongoose

  3. Преобразуване на върнат обект от mongodb в речник

  4. MongoDB Java API:Разлика между com.mongodb.DBCollection.Save() и com.mongodb.DBCollection.Insert()?

  5. Как да пагинирам с Mongoose в Node.js?