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

Mongoose Query til at filtrere en matrix og udfylde relateret indhold

Du skal "projicere" matchet her, da det eneste, MongoDB-forespørgslen gør, er at lede efter et "dokument", der har "mindst ét ​​element" det er "større end" den betingelse, du bad om.

Så filtrering af et "array" er ikke det samme som den "forespørgsel"-tilstand, du har.

En simpel "projektion" vil bare returnere det "første" matchede element til den tilstand. Så det er nok ikke det du vil have, men som et eksempel:

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

Den "slags" gør, hvad du vil, men problemet vil i virkeligheden være, at der kun vil vende tilbage højst én element i "articles" array.

For at gøre dette korrekt skal du bruge .aggregate() for at filtrere matrixindholdet. Ideelt set gøres dette med MongoDB 3.2 og $filter . Men der er også en særlig måde at .populate() på her:

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

Så det, der sker her, er, at den faktiske "filtrering" af arrayet sker inden for .aggregate() sætning, men resultatet fra dette er selvfølgelig ikke længere et "mongoose-dokument", fordi et aspekt af .aggregate() er, at den kan "ændre" dokumentstrukturen, og af denne grund "antager" mongoose det er tilfældet og returnerer bare et "almindeligt objekt".

Det er egentlig ikke et problem, siden når du ser $project fase, beder vi faktisk om alle de samme felter, der findes i dokumentet i henhold til det definerede skema. Så selvom det bare er et "almindeligt objekt", er der ikke noget problem at "caste" det tilbage til et mangustdokument.

Det er her .map() kommer ind, da det returnerer en række konverterede "dokumenter", som så er vigtige for næste trin.

Nu kalder du Model.populate() som så kan køre den yderligere "population" på "arrayen af ​​mongoose-dokumenter".

Så er resultatet endelig, hvad du ønsker.

MongoDB ældre versioner end 3.2.x

De eneste ting, der virkelig ændrer sig her, er aggregeringspipelinen, så det er alt, der skal medtages for kortheds skyld.

MongoDB 2.6 - Kan filtrere arrays med en kombination af $map og $setDifference . Resultatet er et "sæt", men det er ikke et problem, når mongoose opretter et _id felt på alle underdokumentarrays som standard:

    [
        { "$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
        }}
    ],

Ældre revisioner af end det skal bruge $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" }
        }}
    ],

$lookup-alternativet

En anden alternativ er bare at gøre alt på "serveren" i stedet for. Dette er en mulighed med $lookup af MongoDB 3.2 og nyere:

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

    }
)

Og selvom det kun er almindelige dokumenter, er det bare de samme resultater, som du ville have fået fra .populate() nærme sig. Og du kan selvfølgelig altid gå hen og "caste" til mongoose-dokumenter i alle tilfælde igen, hvis du virkelig skal.

Den "korteste" vej

Dette går virkelig tilbage til det oprindelige udsagn, hvor du grundlæggende bare "accepterer", at "forespørgslen" ikke er beregnet til at "filtrere" array-indholdet. .populate() kan med glæde gøre det, fordi det bare er endnu en "forespørgsel" og fylder "dokumenter" efter bekvemmelighed.

Så hvis du virkelig ikke gemmer "bucketloads" af båndbredde ved at fjerne yderligere array-medlemmer i det originale dokumentarray, så bare .filter() dem ud i efterbehandlingskoden:

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. Flere tæller med enkelt forespørgsel i mongodb

  2. mongorestore mislykkedes:ingen tilgængelige servere

  3. Sådan automatiseres og administreres MongoDB med ClusterControl

  4. Operationel databasetilgængelighed