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

Mongoose Query pro filtrování pole a naplnění souvisejícího obsahu

Zde musíte "promítnout" shodu, protože vše, co MongoDB dotaz dělá, je hledat "dokument", který má "alespoň jeden prvek" to je "větší než" podmínku, o kterou jste požádali.

Filtrování „pole“ tedy není totéž jako podmínka „dotaz“, kterou máte.

Jednoduchá "projekce" pouze vrátí "první" odpovídající položku do této podmínky. Takže to asi není to, co chcete, ale jako příklad:

Order.find({ "articles.quantity": { "$gte": 5 } })
    .select({ "articles.$": 1 })
    .populate({
        "path": "articles.article",
        "match": { "price": { "$lte": 500 } }
    }).exec(function(err,orders) {
       // populated and filtered twice
    }
)

Tento „druh“ dělá, co chcete, ale problém bude ve skutečnosti v tom, že se vrátí maximálně jeden prvek v rámci "articles" pole.

Chcete-li to provést správně, potřebujete .aggregate() pro filtrování obsahu pole. V ideálním případě se to provádí pomocí MongoDB 3.2 a $filter . Existuje však také speciální způsob .populate() zde:

Order.aggregate(
    [
        { "$match": { "artciles.quantity": { "$gte": 5 } } },
        { "$project": {
            "orderdate": 1,
            "articles": {
                "$filter": {
                    "input": "$articles",
                    "as": "article",
                    "cond": {
                       "$gte": [ "$$article.quantity", 5 ]
                    }
                }
            },
            "__v": 1
        }}
    ],
    function(err,orders) {
        Order.populate(
            orders.map(function(order) { return new Order(order) }),
            {
                "path": "articles.article",
                "match": { "price": { "$lte": 500 } }
            },
            function(err,orders) {
                // now it's all populated and mongoose documents
            }
        )
    }
)

Takže to, co se zde stane, je skutečné „filtrování“ pole v rámci .aggregate() , ale výsledek z toho už samozřejmě není "mongoose document", protože jeden aspekt .aggregate() je, že může "změnit" strukturu dokumentu, a z tohoto důvodu mongoose "předpokládá", že tomu tak je, a vrací pouze "prostý objekt".

To ve skutečnosti není problém, protože když vidíte $project fázi, ve skutečnosti požadujeme všechna stejná pole přítomná v dokumentu podle definovaného schématu. Takže i když se jedná pouze o „prostý objekt“, není problém jej „obsadit“ zpět do mongoose dokumentu.

Zde je .map() přichází, protože vrací pole převedených „dokumentů“, které je pak důležité pro další fázi.

Nyní zavoláte Model.populate() který pak může spustit další „populaci“ na „souboru dokumentů mangusty“.

Výsledek je pak konečně to, co chcete.

MongoDB starší verze než 3.2.x

Jediné, co se zde skutečně mění, je agregační kanál, takže to je vše, co je potřeba pro stručnost zahrnout.

MongoDB 2.6 - Může filtrovat pole pomocí kombinace $map a $setDifference . Výsledkem je "set", ale to není problém, když mangoose vytvoří _id pole ve všech polích vnořených dokumentů ve výchozím nastavení:

    [
        { "$match": { "artciles.quantity": { "$gte": 5 } } },
        { "$project": {
            "orderdate": 1,
            "articles": {
                "$setDiffernce": [
                   { "$map": {
                      "input": "$articles",
                      "as": "article",
                      "in": {
                         "$cond": [
                             { "$gte": [ "$$article.price", 5 ] },
                             "$$article",
                             false
                         ]
                      }
                   }},
                   [false]
                ]
            },
            "__v": 1
        }}
    ],

Starší verze musí používat $unwind :

    [
        { "$match": { "artciles.quantity": { "$gte": 5 } }},
        { "$unwind": "$articles" },
        { "$match": { "artciles.quantity": { "$gte": 5 } }},
        { "$group": {
          "_id": "$_id",
          "orderdate": { "$first": "$orderdate" },
          "articles": { "$push": "$articles" },
          "__v": { "$first": "$__v" }
        }}
    ],

Alternativa $lookup

Další alternativou je místo toho dělat vše na "serveru". Toto je možnost s $lookup MongoDB 3.2 a vyšší:

Order.aggregate(
    [
        { "$match": { "artciles.quantity": { "$gte": 5 } }},
        { "$project": {
            "orderdate": 1,
            "articles": {
                "$filter": {
                    "input": "$articles",
                    "as": "article",
                    "cond": {
                       "$gte": [ "$$article.quantity", 5 ]
                    }
                }
            },
            "__v": 1
        }},
        { "$unwind": "$articles" },
        { "$lookup": {
            "from": "articles",
            "localField": "articles.article",
            "foreignField": "_id",
            "as": "articles.article"
        }},
        { "$unwind": "$articles.article" },
        { "$group": {
          "_id": "$_id",
          "orderdate": { "$first": "$orderdate" },
          "articles": { "$push": "$articles" },
          "__v": { "$first": "$__v" }
        }},
        { "$project": {
            "orderdate": 1,
            "articles": {
                "$filter": {
                    "input": "$articles",
                    "as": "article",
                    "cond": {
                       "$lte": [ "$$article.article.price", 500 ]
                    }
                }
            },
            "__v": 1
        }}
    ],
    function(err,orders) {

    }
)

A ačkoli jsou to jen obyčejné dokumenty, jsou to stejné výsledky, jaké byste získali z .populate() přístup. A samozřejmě můžete vždy jít a "obsadit" do dokumentů mongoose ve všech případech znovu, pokud opravdu musíte.

Nejkratší cesta

To se skutečně vrací k původnímu prohlášení, kde v podstatě jen „přijmete“, že „dotaz“ není určen k „filtrování“ obsahu pole. .populate() může to s radostí udělat, protože je to jen další "dotaz" a cpe se do "dokumentů" z pohodlí.

Pokud tedy skutečně nešetříte „hromady“ šířky pásma odstraněním dalších členů pole v poli původního dokumentu, pak stačí .filter() v kódu následného zpracování:

Order.find({ "articles.quantity": { "$gte": 5 } })
    .populate({
        "path": "articles.article",
        "match": { "price": { "$lte": 500 } }
    }).exec(function(err,orders) {
        orders = orders.filter(function(order) {
            order.articles = order.articles.filter(function(article) {
                return (
                    ( article.quantity >= 5 ) &&
                    ( article.article != null )
                )
            });
            return order.aricles.length > 0;
        })

        // orders has non matching entries removed            
    }
)


  1. MongoDB SELECT COUNT GROUP BY

  2. Chyba Node.js Chyba:Nelze najít modul 'mongoose'

  3. Mongodb concat int a řetězec

  4. MongoDB $reverseArray