MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Mongoose-Abfrage zum Filtern eines Arrays und Auffüllen verwandter Inhalte

Sie müssen die Übereinstimmung hier "projizieren", da die MongoDB-Abfrage lediglich nach einem "Dokument" sucht, das "mindestens ein Element" enthält das ist "größer als" die Bedingung, nach der Sie gefragt haben.

Das Filtern eines "Arrays" ist also nicht dasselbe wie die "Abfrage"-Bedingung, die Sie haben.

Eine einfache "Projektion" bringt nur das "erste" übereinstimmende Element in diesen Zustand zurück. Es ist also wahrscheinlich nicht das, was Sie wollen, aber als Beispiel:

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

Diese "Art von" macht, was Sie wollen, aber das Problem wird wirklich sein, dass immer nur höchstens eins zurückgegeben wird -Element innerhalb des "articles" Array.

Dazu benötigen Sie .aggregate() um den Inhalt des Arrays zu filtern. Idealerweise geschieht dies mit MongoDB 3.2 und $filter . Aber es gibt auch einen speziellen Weg zu .populate() hier:

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

Was hier also passiert, ist die eigentliche "Filterung" des Arrays innerhalb von .aggregate() -Anweisung, aber natürlich ist das Ergebnis daraus kein "Mungo-Dokument" mehr, weil ein Aspekt von .aggregate() ist, dass es die Dokumentstruktur "ändern" kann, und aus diesem Grund "vermutet" Mongoose, dass dies der Fall ist, und gibt nur ein "einfaches Objekt" zurück.

Das ist eigentlich kein Problem, denn wenn Sie das $project sehen Schritt, fragen wir tatsächlich nach allen gleichen Feldern, die im Dokument gemäß dem definierten Schema vorhanden sind. Obwohl es also nur ein "einfaches Objekt" ist, gibt es kein Problem, es wieder in ein Mongoose-Dokument "umzuwandeln".

Hier ist die .map() kommt, da es eine Reihe konvertierter "Dokumente" zurückgibt, die dann für die nächste Stufe wichtig sind.

Jetzt rufen Sie Model.populate() auf das kann dann die weitere "Bevölkerung" auf dem "Array of Mongoose Documents" ausführen.

Das Ergebnis ist dann endlich das, was Sie wollen.

Ältere MongoDB-Versionen als 3.2.x

Die einzigen Dinge, die sich hier wirklich ändern, sind die Aggregationspipeline. Das ist also alles, was der Kürze halber aufgenommen werden muss.

MongoDB 2.6 - Kann Arrays mit einer Kombination aus $map filtern und $setDifference . Das Ergebnis ist ein "Set", aber das ist kein Problem, wenn Mongoose eine _id erstellt standardmäßig in allen Unterdokumentarrays:

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

Ältere Versionen davon müssen $unwind verwenden :

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

Die $lookup-Alternative

Eine andere Alternative besteht darin, stattdessen einfach alles auf dem "Server" zu erledigen. Dies ist eine Option mit $lookup von MongoDB 3.2 und höher:

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

    }
)

Und obwohl dies nur einfache Dokumente sind, sind es genau die gleichen Ergebnisse, die Sie von .populate() erhalten hätten sich nähern. Und natürlich können Sie jederzeit wieder auf Mongoose-Dokumente "werfen", wenn Sie es wirklich müssen.

Der "kürzeste" Weg

Dies geht wirklich auf die ursprüngliche Anweisung zurück, in der Sie im Grunde nur "akzeptieren", dass die "Abfrage" den Array-Inhalt nicht "filtern" soll. Die .populate() kann dies gerne tun, da es nur eine weitere "Abfrage" ist und der Einfachheit halber "Dokumente" einfügt.

Wenn Sie also wirklich keine "Eimerladungen" Bandbreite sparen, indem Sie zusätzliche Array-Mitglieder aus dem ursprünglichen Dokument-Array entfernen, dann einfach .filter() sie im Nachbearbeitungscode aus:

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