sql >> Database teknologi >  >> NoSQL >> MongoDB

Aggregationsfilter efter $lookup

Spørgsmålet her handler faktisk om noget andet og behøver ikke $lookup overhovedet. Men for alle, der ankommer her udelukkende fra titlen "filtrering efter $lookup", så er disse teknikker for 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"
    }}
])

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

Hvis du stiller spørgsmål, hvorfor ville du $unwind i modsætning til at bruge $filter på arrayet, og læs derefter Aggregate $lookup Den samlede størrelse af dokumenter i matchende pipeline overstiger den maksimale dokumentstørrelse for alle detaljer om, hvorfor dette generelt er nødvendigt og langt mere optimalt.

For udgivelser af MongoDB 3.6 og frem, så er den mere udtryksfulde "sub-pipeline" generelt det, du vil "filtrere" resultaterne af den udenlandske samling, før noget overhovedet bliver returneret til arrayet.

Tilbage til svaret, som faktisk beskriver, hvorfor det stillede spørgsmål overhovedet ikke behøver at deltage...

Original

Brug af $lookup som om dette ikke er den mest "effektive" måde at gøre, hvad du vil her. Men mere om dette senere.

Som et grundlæggende koncept skal du blot bruge $filter på det resulterende array:

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 brug $redact i stedet:

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

Begge får det samme 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 ]
    }
  ]
}

Bundlinjen er, at $lookup selv kan ikke "endnu" forespørge om kun at vælge visse data. Så al "filtrering" skal ske efter $lookup

Men for denne type "selv joinforbindelse" er du bedre stillet til ikke at bruge $lookup overhovedet og undgå overhead af en ekstra læsning og "hash-fletning" helt. Bare hent de relaterede elementer og $group i stedet:

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

Hvilket kun kommer lidt anderledes ud, fordi jeg bevidst fjernede de uvedkommende felter. Tilføj dem selv, hvis du virkelig vil:

{
  "_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 eneste rigtige problem her er at "filtrere" enhver null resultat fra arrayet, der blev oprettet, da det aktuelle dokument var parent i behandling af elementer til $push .

Det du også ser ud til at mangle her er, at det resultat du leder efter slet ikke behøver aggregering eller "underforespørgsler". Den struktur, som du har indgået eller muligvis fundet andre steder, er "designet", så du kan få en "node" og alle dens "børn" i en enkelt forespørgselsanmodning.

Det betyder, at kun "forespørgslen" er alt, der virkelig er brug for, og dataindsamlingen (hvilket er alt, der sker, da intet indhold virkelig bliver "reduceret") er blot en funktion af iteration af 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);

Dette gør nøjagtig det samme:

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

Og tjener som bevis på, at alt, hvad du virkelig skal gøre her, er at udsende den "enlige" forespørgsel for at vælge både forælder og børn. De returnerede data er præcis de samme, og alt hvad du gør på enten server eller klient er at "massere" til et andet indsamlet format.

Dette er et af de tilfælde, hvor du kan blive "fanget" i at tænke på, hvordan du gjorde tingene i en "relationel" database, og ikke indse, at siden måden dataene er lagret på har "ændret", behøver du ikke længere bruge samme tilgang.

Det er præcis det, der er meningen med dokumentationseksemplet "Modeltræstrukturer med underordnede referencer" i sin struktur, hvor det gør det nemt at vælge forældre og børn inden for én forespørgsel.




  1. PostgreSQL vs. MongoDB

  2. mongoDB/mongoose:unik, hvis ikke null

  3. Find største dokumentstørrelse i MongoDB

  4. Hvad er navnekonventioner for MongoDB?