sql >> Databáze >  >> NoSQL >> MongoDB

Agregační filtr po $lookup

Tato otázka je ve skutečnosti o něčem jiném a nepotřebuje $lookup vůbec. Ale pro každého, kdo sem přichází čistě z názvu „filtrování po $lookup“, pak jsou tyto techniky pro vás:

MongoDB 3.6 – dílčí potrubí

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

Dříve – $lookup + $unwind + $match koalescence

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

Pokud se ptáte, proč byste $unwind na rozdíl od použití $filter na poli a poté si přečtěte Aggregate $lookup Celková velikost dokumentů v odpovídajícím kanálu překračuje maximální velikost dokumentu pro všechny podrobnosti o tom, proč je to obecně nutné a mnohem optimálnější.

U verzí MongoDB 3.6 a novějších je pak expresívnější „sub-pipeline“ obecně tím, co chcete „filtrovat“ výsledky cizí kolekce, než se vůbec něco vrátí do pole.

Zpět k odpovědi, která ve skutečnosti popisuje, proč položená otázka vůbec nepotřebuje "žádné připojení"...

Původní

Pomocí $lookup takhle to není ten "nejefektivnější" způsob, jak tady dělat to, co chcete. Ale o tom později.

Jako základní koncept stačí použít $filter na výsledném poli:

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

Nebo použijte $redact místo toho:

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

Oba mají stejný výsledek:

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

Sečteno a podtrženo, $lookup sám se nemůže "zatím" dotazovat, aby vybral pouze určitá data. Takže veškeré "filtrování" musí proběhnout po $lookup

Ale opravdu pro tento typ "vlastního připojení" je lepší nepoužívat $lookup vůbec a vyhnout se režii dalšího čtení a "hash-sloučení" úplně. Stačí načíst související položky a $group místo toho:

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

Což vychází trochu jinak, protože jsem záměrně odstranil cizí pole. Přidejte je do sebe, pokud to opravdu chcete:

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

Jediným skutečným problémem je tedy „filtrování“ jakékoli null výsledek z pole vytvořeného, ​​když byl aktuální dokument parent při zpracování položek na $push .

Zdá se, že vám zde také chybí, že hledaný výsledek vůbec nepotřebuje agregaci nebo „poddotazy“. Struktura, kterou jste uzavřeli nebo kterou jste možná našli jinde, je „navržena“ tak, abyste mohli získat „uzel“ a všechny jeho „děti“ v jediném dotazu.

To znamená, že pouze „dotaz“ je vše, co je skutečně potřeba, a sběr dat (což je vše, co se děje, protože ve skutečnosti není „redukován“ žádný obsah) je pouze funkcí opakování výsledku kurzoru:

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

To dělá přesně to samé:

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

A slouží jako důkaz, že vše, co zde skutečně potřebujete, je zadat „jediný“ dotaz pro výběr rodiče i potomka. Vrácená data jsou úplně stejná a vše, co děláte na serveru nebo klientovi, je „masírování“ do jiného shromážděného formátu.

Toto je jeden z těch případů, kdy se můžete „přichytit“ při přemýšlení o tom, jak jste dělali věci v „relační“ databázi, a neuvědomovat si, že protože se způsob ukládání dat „změnil“, již nemusíte používat stejný přístup.

Přesně to je smyslem příkladu dokumentace "Modelové stromové struktury s podřízenými referencemi" v jeho struktuře, kde usnadňuje výběr rodičů a dětí v rámci jednoho dotazu.




  1. Sestavení indexu MongoDB – Zabránění uživatelům ve spouštění nových sestavení

  2. Problémy se spouštěním příkladů v Meteoru

  3. Oblasti HBase se sloučí

  4. vyloučit pole v agregaci $lookup