MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Mongoose Query do filtrowania tablicy i wypełniania powiązanej treści

Musisz tu „zaprojektować” dopasowanie, ponieważ jedyne, co robi zapytanie MongoDB, to szukanie „dokumentu”, który ma „co najmniej jeden element” czyli "większe niż" stan, o który prosiłeś.

Tak więc filtrowanie „tablicy” to nie to samo, co warunek „zapytanie”.

Prosta „projekcja” po prostu przywróci „pierwszy” dopasowany element do tego warunku. Więc prawdopodobnie nie tego chcesz, ale jako przykład:

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

Ten „rodzaj” robi to, co chcesz, ale tak naprawdę problem będzie polegał na tym, że powróci tylko co najwyżej jeden element w "articles" tablica.

Aby zrobić to poprawnie, potrzebujesz .aggregate() do filtrowania zawartości tablicy. Najlepiej zrobić to za pomocą MongoDB 3.2 i $filter . Ale jest też specjalny sposób na .populate() tutaj:

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

To, co się tutaj dzieje, to faktyczne „filtrowanie” tablicy w ramach .aggregate() oświadczenie, ale oczywiście wynik tego nie jest już "dokumentem mangusty", ponieważ jeden aspekt .aggregate() jest to, że może „zmienić” strukturę dokumentu, iz tego powodu mangusta „zakłada”, że tak jest i po prostu zwraca „zwykły obiekt”.

To naprawdę nie jest problem, ponieważ kiedy widzisz $project na etapie, w rzeczywistości prosimy o wszystkie te same pola obecne w dokumencie zgodnie ze zdefiniowanym schematem. Więc nawet jeśli jest to tylko „zwykły obiekt”, nie ma problemu z „przerzuceniem” go z powrotem do dokumentu z mangustą.

To tutaj .map() pojawia się, ponieważ zwraca tablicę przekonwertowanych „dokumentów”, co jest następnie ważne w następnym etapie.

Teraz wywołujesz Model.populate() który może następnie uruchomić dalszą „populację” na „tablicy dokumentów dotyczących mangusty”.

Rezultat jest w końcu taki, jaki chcesz.

MongoDB starsze wersje niż 3.2.x

Jedyne, co naprawdę się tutaj zmienia, to potok agregacji, więc to wszystko, co należy uwzględnić, aby uzyskać zwięzłość.

MongoDB 2.6 - Może filtrować tablice za pomocą kombinacji $map i $setDifference . Wynikiem jest „zestaw”, ale nie stanowi to problemu, gdy mangusta tworzy _id domyślnie we wszystkich tablicach dokumentów podrzędnych:

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

Starsze wersje muszą używać $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" }
        }}
    ],

Alternatywna wyszukiwarka $

Inną alternatywą jest zrobienie wszystkiego na „serwerze”. To jest opcja z $lookup MongoDB 3.2 i nowszych:

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

    }
)

I chociaż są to tylko zwykłe dokumenty, są to dokładnie te same wyniki, które można uzyskać z .populate() zbliżać się. I oczywiście zawsze możesz ponownie „przesłać” dokumenty z mangusty we wszystkich przypadkach, jeśli naprawdę musisz.

Najkrótsza ścieżka

To naprawdę wraca do oryginalnej instrukcji, w której po prostu „zaakceptujesz”, że „zapytanie” nie ma na celu „filtrowania” zawartości tablicy. .populate() może z radością to zrobić, ponieważ jest to tylko kolejne „zapytanie” i jest dla wygody umieszczane w „dokumentach”.

Więc jeśli naprawdę nie oszczędzasz „zasobników” przepustowości przez usunięcie dodatkowych elementów tablicy z oryginalnej tablicy dokumentów, po prostu .filter() je w kodzie przetwarzania końcowego:

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. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Meteor:przesyłanie pliku z klienta do kolekcji Mongo vs system plików vs GridFS

  2. Zdalne łączenie się z interfejsem MongoDB http na serwerze EC2

  3. Jak przechowywać informacje geoprzestrzenne w mongoDB

  4. Mongodb $lookup Nie działa z _id

  5. show dbs daje błąd braku autoryzacji do wykonania polecenia