sql >> Database >  >> NoSQL >> MongoDB

Aggregatiefilter na $lookup

De vraag hier gaat eigenlijk over iets anders en heeft geen $lookup nodig helemaal niet. Maar voor iedereen die hier puur vanuit de titel "filteren na $lookup" komt, dan zijn dit de technieken voor jou:

MongoDB 3.6 - Subpijplijn

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

Eerder - $lookup + $unwind + $match coalescentie

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

Als je je afvraagt ​​waarom zou je $unwind in tegenstelling tot het gebruik van $filter op de array, lees dan Aggregate $lookup Totale grootte van documenten in overeenkomende pijplijn overschrijdt de maximale documentgrootte voor alle details over waarom dit over het algemeen nodig is en veel optimaler.

Voor releases van MongoDB 3.6 en hoger is de meer expressieve "subpijplijn" meestal wat u wilt "filteren" op de resultaten van de buitenlandse verzameling voordat er überhaupt iets in de array wordt teruggestuurd.

Terug naar het antwoord dat eigenlijk beschrijft waarom de gestelde vraag helemaal geen "join" nodig heeft....

Origineel

$lookup gebruiken alsof dit niet de meest "efficiënte" manier is om hier te doen wat je wilt. Maar hierover later meer.

Gebruik als basisconcept gewoon $filter op de 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" ] }
           }
        }
    }}
]);

Of gebruik $redact in plaats daarvan:

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

Beide krijgen hetzelfde resultaat:

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

Waar het op neer komt is dat $lookup kan zelf "nog" niet opvragen om alleen bepaalde gegevens te selecteren. Dus al het "filteren" moet gebeuren na de $lookup

Maar echt voor dit soort "self join" kun je beter $lookup niet gebruiken helemaal niet en de overhead van een extra lees- en "hash-merge" geheel vermijden. Haal gewoon de gerelateerde items op en $group in plaats daarvan:

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

Wat er alleen een beetje anders uitziet omdat ik bewust de vreemde velden heb verwijderd. Voeg ze zelf toe als je dat echt wilt:

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

Dus het enige echte probleem hier is het "filteren" van een null resultaat van de array, gemaakt toen het huidige document de parent was bij het verwerken van items naar $push .

Wat je hier ook lijkt te missen, is dat het resultaat dat je zoekt helemaal geen aggregatie of "subquery's" nodig heeft. De structuur die je hebt gesloten of die je mogelijk ergens anders hebt gevonden, is "ontworpen" zodat je een "knooppunt" en al zijn "kinderen" in één vraagverzoek kunt krijgen.

Dat betekent dat alleen de "query" alles is wat echt nodig is, en dat het verzamelen van gegevens (wat het enige is dat gebeurt omdat er geen inhoud echt wordt "gereduceerd") slechts een functie is van het herhalen van het cursorresultaat:

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

Dit doet precies hetzelfde:

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

En dient als bewijs dat het enige dat u hier echt hoeft te doen, de "enkele" query is om zowel de ouder als de kinderen te selecteren. De geretourneerde gegevens zijn hetzelfde en het enige dat u op de server of de client doet, is "masseren" in een ander verzameld formaat.

Dit is een van die gevallen waarin u "verstrikt" kunt raken in het denken over hoe u de dingen in een "relationele" database hebt gedaan, en u niet realiseert dat u, aangezien de manier waarop de gegevens worden opgeslagen is "gewijzigd", u niet langer hoeft te gebruiken dezelfde aanpak.

Dat is precies het punt van het documentatievoorbeeld "Modelboomstructuren met onderliggende verwijzingen" in zijn structuur, waar het gemakkelijk is om ouders en kinderen te selecteren binnen één zoekopdracht.




  1. Wat is de beste MongoDB GUI? — Update 2019

  2. MongoDB $setIsSubset

  3. Laravel 4 :Oproep naar ongedefinieerde methode Redis::connection()

  4. Hoe nulls te negeren terwijl u een MongoDB-document ongedaan maakt?