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

Vraťte pouze odpovídající prvky dílčího dokumentu v rámci vnořeného pole

Takže dotaz, který máte, ve skutečnosti vybere "dokument" přesně tak, jak by měl. Ale co hledáte, je "filtrovat obsažená pole" tak, aby vrácené prvky odpovídaly pouze podmínce dotazu.

Skutečnou odpovědí je samozřejmě to, že pokud skutečně neušetříte spoustu šířky pásma odfiltrováním takových detailů, neměli byste to ani zkoušet, nebo alespoň po první poziční shodu.

MongoDB má poziční $ operátor, který vrátí prvek pole na odpovídajícím indexu z podmínky dotazu. Toto však vrátí pouze „první“ odpovídající index „vnějšího“ prvku pole nejvíce.

db.getCollection('retailers').find(
    { 'stores.offers.size': 'L'},
    { 'stores.$': 1 }
)

V tomto případě to znamená "stores" pouze pozice pole. Pokud by tedy existovalo více položek „obchody“, bude vrácen pouze „jeden“ z prvků, které obsahovaly vaši shodu. Ale , který nedělá nic pro vnitřní pole "offers" a jako taková každá "nabídka" v rámci odpovídajících "stores" pole by bylo stále vráceno.

MongoDB nemá žádný způsob, jak to "filtrovat" ve standardním dotazu, takže následující nefunguje:

db.getCollection('retailers').find(
    { 'stores.offers.size': 'L'},
    { 'stores.$.offers.$': 1 }
)

Jedinými nástroji, které MongoDB skutečně má k provedení této úrovně manipulace, je agregační rámec. Ale analýza by vám měla ukázat, proč byste to „pravděpodobně“ neměli dělat a místo toho pole filtrovat v kódu.

V pořadí, jak toho můžete dosáhnout podle verze.

Nejprve s MongoDB 3.2.x pomocí $filter operace:

db.getCollection('retailers').aggregate([
  { "$match": { "stores.offers.size": "L" } },
  { "$project": {
    "stores": {
      "$filter": {
        "input": {
          "$map": {
            "input": "$stores",
            "as": "store",
            "in": {
              "_id": "$$store._id",
              "offers": {
                "$filter": {
                  "input": "$$store.offers",
                  "as": "offer",
                  "cond": {
                    "$setIsSubset":  [ ["L"], "$$offer.size" ]
                  }
                }
              }
            }
          }
        },
        "as": "store",
        "cond": { "$ne": [ "$$store.offers", [] ]}
      }
    }
  }}
])

Poté pomocí MongoDB 2.6.x a výše pomocí $map a $setDifference :

db.getCollection('retailers').aggregate([
  { "$match": { "stores.offers.size": "L" } },
  { "$project": {
    "stores": {
      "$setDifference": [
        { "$map": {
          "input": {
            "$map": {
              "input": "$stores",
              "as": "store",
              "in": {
                "_id": "$$store._id",
                "offers": {
                  "$setDifference": [
                    { "$map": {
                      "input": "$$store.offers",
                      "as": "offer",
                      "in": {
                        "$cond": {
                          "if": { "$setIsSubset": [ ["L"], "$$offer.size" ] },
                          "then": "$$offer",
                          "else": false
                        }
                      }
                    }},
                    [false]
                  ]
                }
              }
            }
          },
          "as": "store",
          "in": {
            "$cond": {
              "if": { "$ne": [ "$$store.offers", [] ] },
              "then": "$$store",
              "else": false
            }
          }
        }},
        [false]
      ]
    }
  }}
])

A konečně v jakékoli verzi výše MongoDB 2.2.x kde byl představen agregační rámec.

db.getCollection('retailers').aggregate([
  { "$match": { "stores.offers.size": "L" } },
  { "$unwind": "$stores" },
  { "$unwind": "$stores.offers" },
  { "$match": { "stores.offers.size": "L" } },
  { "$group": {
    "_id": {
      "_id": "$_id",
      "storeId": "$stores._id",
    },
    "offers": { "$push": "$stores.offers" }
  }},
  { "$group": {
    "_id": "$_id._id",
    "stores": {
      "$push": {
        "_id": "$_id.storeId",
        "offers": "$offers"
      }
    }
  }}
])

Pojďme si vysvětlit vysvětlení.

MongoDB 3.2.xa vyšší

Obecně řečeno, $filter je způsob, jak jít sem, protože je navržen s ohledem na účel. Protože existuje více úrovní pole, musíte to použít na každé úrovni. Nejprve se tedy ponoříte do každé "offers" v "stores" prozkoumat a $filter ten obsah.

Zde je jednoduché srovnání "Vyhovuje "size" pole obsahuje prvek, který hledám" . V tomto logickém kontextu stačí použít $setIsSubset operace pro porovnání pole ("set") ["L"] do cílového pole. Kde je tato podmínka true ( obsahuje "L" ), pak prvek pole pro "offers" je zachován a vrácen ve výsledku.

Ve vyšší úrovni $filter , pak se díváte, zda výsledek z předchozího $filter vrátil prázdné pole [] pro "offers" . Pokud není prázdný, je prvek vrácen nebo je jinak odstraněn.

MongoDB 2.6.x

Toto je velmi podobné modernímu procesu s tím rozdílem, že neexistuje žádný $filter v této verzi můžete použít $map prozkoumejte každý prvek a poté použijte $setDifference odfiltrovat všechny prvky, které byly vráceny jako false .

Takže $map vrátí celé pole, ale $cond operace pouze rozhodne, zda vrátí prvek nebo místo toho false hodnota. Ve srovnání $setDifference na jeden prvek "set" [false] všechny false prvky ve vráceném poli by byly odstraněny.

Ve všech ostatních ohledech je logika stejná jako výše.

MongoDB 2.2.xa vyšší

Takže pod MongoDB 2.6 je jediným nástrojem pro práci s poli $unwind a pouze pro tento účel byste neměli použijte agregační rámec „jen“ pro tento účel.

Proces se skutečně zdá být jednoduchý, jednoduše „rozebrat“ každé pole, odfiltrovat věci, které nepotřebujete, a pak je dát zpět dohromady. Hlavní péče je ve "dvojce" $group fáze, přičemž „první“ přebuduje vnitřní pole a další přestaví vnější pole. Existují různé _id hodnoty na všech úrovních, takže je stačí zahrnout na každé úrovni seskupení.

Problém je ale v tom, že $unwind je velmi nákladné . Ačkoli to má stále svůj účel, hlavním účelem použití není provádět tento druh filtrování na dokument. Ve skutečnosti by se v moderních verzích mělo používat pouze tehdy, když se prvek pole (polí) musí stát součástí samotného "klíče seskupení".

Závěr

Není tedy jednoduchý proces získat shody na více úrovních pole, jako je tento, a ve skutečnosti to může být extrémně nákladné při nesprávné implementaci.

K tomuto účelu by se měly vždy používat pouze dva moderní výpisy, protože kromě „dotazu“ $match využívají „jedinou“ fázi potrubí. za účelem provedení „filtrace“. Výsledný efekt je o něco vyšší než u standardních forem .find() .

Obecně však platí, že tyto výpisy mají stále určitou složitost, a pokud skutečně drasticky nezmenšíte obsah vrácený takovým filtrováním způsobem, který výrazně zlepší šířku pásma používaného mezi serverem a klientem, pak je lepší. filtrování výsledku počátečního dotazu a základní projekce.

db.getCollection('retailers').find(
    { 'stores.offers.size': 'L'},
    { 'stores.$': 1 }
).forEach(function(doc) {
    // Technically this is only "one" store. So omit the projection
    // if you wanted more than "one" match
    doc.stores = doc.stores.filter(function(store) {
        store.offers = store.offers.filter(function(offer) {
            return offer.size.indexOf("L") != -1;
        });
        return store.offers.length != 0;
    });
    printjson(doc);
})

Práce se zpracováním dotazu „post“ vráceného objektu je tedy mnohem méně obtížná než použití agregačního kanálu. A jak bylo uvedeno, jediným „skutečným“ rozdílem by bylo to, že zahazujete ostatní prvky na „serveru“ namísto toho, abyste je odebírali „na dokument“ po přijetí, což může ušetřit trochu šířky pásma.

Ale pokud to neděláte v moderní verzi s pouze $match a $project , pak "náklady" na zpracování na serveru výrazně převáží "zisk" ze snížení režie sítě tím, že se nejprve odstraní nesrovnatelné prvky.

Ve všech případech získáte stejný výsledek:

{
        "_id" : ObjectId("56f277b1279871c20b8b4567"),
        "stores" : [
                {
                        "_id" : ObjectId("56f277b5279871c20b8b4783"),
                        "offers" : [
                                {
                                        "_id" : ObjectId("56f277b1279871c20b8b4567"),
                                        "size" : [
                                                "S",
                                                "L",
                                                "XL"
                                        ]
                                }
                        ]
                }
        ]
}


  1. Chyba při připojování k serveru MongoDb Atlas Server

  2. Převeďte DBObject na POJO pomocí MongoDB Java Driver

  3. implementace out-of-process cache pomocí Redis ve windows Azure

  4. redis:zálohování dump.rdb