MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Запитване на Mongoose за филтриране на масив и попълване на свързано съдържание

Тук трябва да "проектирате" съвпадението, тъй като всичко, което заявката MongoDB прави, е да търси "документ", който има "поне един елемент" което е „по-голямо от“ условието, което поискахте.

Така че филтрирането на „масив“ не е същото като условието „заявка“, което имате.

Една проста "проекция" просто ще върне "първия" съответстващ елемент към това условие. Така че вероятно не е това, което искате, а като пример:

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

Това "нещо" прави това, което искате, но проблемът наистина ще бъде, че винаги ще се върне само един елемент в "articles" масив.

За да направите това правилно, ви трябва .aggregate() за филтриране на съдържанието на масива. В идеалния случай това се прави с MongoDB 3.2 и $filter . Но има и специален начин за .populate() тук:

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

И така, това, което се случва тук, е действителното "филтриране" на масива, което се случва в .aggregate() изявление, но разбира се резултатът от това вече не е "документ на мангуста", защото един аспект на .aggregate() е, че може да "промени" структурата на документа и поради тази причина mongoose "предполага", че е така и просто връща "обикновен обект".

Това всъщност не е проблем, тъй като когато видите $project етап, ние всъщност искаме всички същите полета, присъстващи в документа, съгласно дефинираната схема. Така че, въпреки че е просто „обикновен обект“, няма проблем да го „прехвърляте“ обратно в документ от мангуста.

Това е мястото, където .map() идва, тъй като връща масив от преобразувани „документи“, което след това е важно за следващия етап.

Сега извиквате Model.populate() който след това може да изпълнява по-нататъшното "популация" в "масива от документи на мангуста".

Тогава резултатът най-накрая е това, което искате.

По-стари версии на MongoDB от 3.2.x

Единствените неща, които наистина се променят тук, са конвейерът за агрегиране, така че това е всичко, което трябва да бъде включено за краткост.

MongoDB 2.6 - Може да филтрира масиви с комбинация от $map и $setDifference . Резултатът е "набор", но това не е проблем, когато mongoose създава _id поле на всички масиви от поддокументи по подразбиране:

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

По-старите ревизии от това трябва да използват $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

Друга алтернатива е просто да правите всичко на "сървъра". Това е опция с $lookup на MongoDB 3.2 и по-нова версия:

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

    }
)

И въпреки че това са просто обикновени документи, резултатите са същите като тези, които бихте получили от .populate() Приближаване. И, разбира се, винаги можете да отидете и да „прехвърляте“ към документите на мангусти във всички случаи отново, ако наистина трябва.

Най-краткият път

Това наистина се връща към оригиналната декларация, където по същество просто "приемате", че "заявката" не е предназначена да "филтрира" съдържанието на масива. .populate() с радост може да го направи, защото това е просто поредната „заявка“ и се пълни с „документи“ за удобство.

Така че, ако наистина не спестявате "кофи" от честотна лента чрез премахване на допълнителни членове на масива в оригиналния масив от документи, тогава просто .filter() ги извеждат в код за последваща обработка:

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. Как да сортирам поддокументи в полето на масива?

  2. Mongoose:Изисква се път за грешка при валидиране

  3. Как да защитя полето за парола в Mongoose/MongoDB, така че да не се върне в заявка, когато попълвам колекции?

  4. ScaleGrid обявява MongoDB хостинг услуги в Канада

  5. MongoDB вложено търсене с 3 нива