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

Uzyskaj przefiltrowaną liczbę elementów w tablicy z $lookup wraz z całym dokumentem

Adnotacja dla tych, którzy szukają – liczba zagraniczna

Nieco lepiej niż pierwotnie udzielono odpowiedzi, jest użycie nowszej formy $wyszukaj z MongoDB 3.6. Może to faktycznie wykonać „liczenie” w wyrażeniu „podpotoku”, w przeciwieństwie do zwracania „tablicy” do późniejszego filtrowania i liczenia, a nawet przy użyciu $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" }
  }}
])

Nie to, o co prosiło pierwotne pytanie, ale część poniższej odpowiedzi w obecnie najbardziej optymalnej formie, co jest oczywiście wynikiem $lookup jest zredukowana do „dopasowanej liczby” tylko zamiast "wszystkie pasujące dokumenty".

Oryginał

Właściwym sposobem na zrobienie tego byłoby dodanie "linkCount" do $group etap oraz $first na dowolnych dodatkowych polach dokumentu nadrzędnego w celu uzyskania formy „w liczbie pojedynczej”, takiej jak stan „przed” $unwind zostało przetworzone na tablicy, która była wynikiem $lookup :

Wszystkie szczegóły

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

Produkuje:

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

Grupuj według 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", [] ]
        } 
      }   
    }
  }}
])

Produkuje

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

Powód, dla którego robisz to w ten sposób z $unwind a następnie $match Wynika to z tego, jak MongoDB faktycznie obsługuje potok, gdy jest wydawany w tej kolejności. Tak dzieje się z $lookup jak pokazano w "wyjaśnij" wyjście z operacji:

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

Zostawiam część z $group w tym wyniku, aby wykazać, że pozostałe dwa etapy rurociągu „znikają”. Dzieje się tak, ponieważ zostały one „zwinięte” w $oglądaj etap rurociągu, jak pokazano. W ten sposób MongoDB radzi sobie z możliwością przekroczenia limitu BSON w wyniku „łączenia” wyników $lookup do tablicy dokumentu nadrzędnego.

Możesz naprzemiennie napisać operację w ten sposób:

Wszystkie szczegóły

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

Grupuj według 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" } 
  }}
])

Który ma to samo wyjście, ale „różni się” od pierwszego zapytania tym, że $filtr tutaj jest stosowane „po” WSZYSTKO wyniki $lookup są zwracane do nowej tablicy dokumentu nadrzędnego.

Tak więc pod względem wydajności, bardziej efektywne jest zrobienie tego w pierwszy sposób, jak również możliwość przenoszenia do możliwych dużych zestawów wyników „przed filtrowaniem”, które w przeciwnym razie przekroczyłoby limit 16 MB BSON.

Na marginesie dla zainteresowanych, w przyszłych wydaniach MongoDB (przypuszczalnie 3.6 i nowszych) możesz użyć $replaceRoot zamiast $addFields z użyciem nowego $mergeObjects operator rurociągu. Zaletą tego jest "blok", możemy zadeklarować "filtrowane" treść jako zmienną za pośrednictwem $let , co oznacza, że ​​nie musisz pisać tego samego $filter "dwa razy":

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

Niemniej jednak najlepszy sposób na zrobienie takich „przefiltrowanych” $lookup operacje są w tej chwili „wciąż” przy użyciu $unwind następnie $match wzorca, do czasu, gdy będziesz mógł dostarczyć argumenty zapytania do $ wyszukiwanie bezpośrednio.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Korzystanie z aspektów w C# agregacji Framework z wieloma aspektami, Unwind i sortByCount

  2. Czym jest tryb ścisły MongoDB i czy warto go używać?

  3. Czy można mongodumpować ostatnie x rekordów z kolekcji?

  4. Którą bazę danych (Cassandra, MongoDB, ?) wybrać do przechowywania i odpytywania danych o zdarzeniach / dziennikach / metrykach?

  5. Haszowanie hasła Mongoose