sql >> Databasteknik >  >> NoSQL >> MongoDB

Aggregationsfilter efter $lookup

Frågan här handlar faktiskt om något annat och behöver inte $lookup alls. Men för alla som kommer hit enbart från titeln "filtrering efter $lookup" så är dessa tekniker för dig:

MongoDB 3.6 - Sub-pipeline

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

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

Om du frågar varför skulle du $unwind i motsats till att använda $filter på arrayen, läs sedan Aggregate $lookup Den totala storleken på dokument i matchande pipeline överstiger den maximala dokumentstorleken för alla detaljer om varför detta generellt är nödvändigt och mycket mer optimalt.

För utgåvor av MongoDB 3.6 och senare är den mer uttrycksfulla "sub-pipeline" i allmänhet vad du vill "filtrera" resultaten av den utländska samlingen innan någonting alls returneras till arrayen.

Tillbaka till svaret dock som faktiskt beskriver varför den ställda frågan behöver "ingen join" alls....

Original

Använder $lookup som att detta inte är det mest "effektiva" sättet att göra vad du vill här. Men mer om detta senare.

Som ett grundläggande koncept, använd bara $filter på den resulterande arrayen:

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

Eller använd $redact istället:

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

Båda får samma resultat:

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

Slutsatsen är att $lookup själv kan inte "ännu" fråga för att bara välja vissa data. Så all "filtrering" måste ske efter $lookup

Men egentligen för den här typen av "self join" är det bättre att du inte använder $lookup överhuvudtaget och undvika omkostnader för ytterligare en läsning och "hash-merge" helt. Hämta bara de relaterade objekten och $group istället:

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

Vilket bara blir lite annorlunda eftersom jag medvetet tagit bort de främmande fälten. Lägg till dem själv om du verkligen vill:

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

Så det enda verkliga problemet här är att "filtrera" någon null resultat från arrayen, skapad när det aktuella dokumentet var parent vid bearbetning av objekt till $push .

Det du också verkar sakna här är att resultatet du letar efter inte alls behöver aggregering eller "sub-queries". Strukturen som du har kommit fram till eller möjligen hittat någon annanstans är "designad" så att du kan få en "nod" och alla dess "barn" i en enda frågeförfrågan.

Det betyder att bara "frågan" är allt som verkligen behövs, och datainsamlingen (vilket är allt som händer eftersom inget innehåll verkligen "minskas") är bara en funktion av att iterera markörresultatet:

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

Detta gör exakt samma sak:

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

Och fungerar som ett bevis på att allt du verkligen behöver göra här är att skicka en "enda" fråga för att välja både förälder och barn. Den returnerade informationen är precis densamma, och allt du gör på antingen servern eller klienten är att "massera" till ett annat insamlat format.

Detta är ett av de fall där du kan bli "fångad" av att tänka på hur du gjorde saker i en "relationell" databas, och inte inse att eftersom hur data lagras har "förändrats" behöver du inte längre använda samma tillvägagångssätt.

Det är precis vad poängen med dokumentationsexemplet "Modellträdstrukturer med underordnade referenser" i sin struktur, där det gör det enkelt att välja föräldrar och barn inom en fråga.




  1. MongoDB - Begränsa resultaten av en fråga

  2. Mongoose:vad är skillnaderna mellan Model.create och Collection.insert

  3. Filtrera undermatrisen för en matris efter vissa kriterier

  4. Dumpa Mongo Collection till JSON-format