Mongoose Query过滤数组并填充相关内容

时间:2016-04-02 08:50:55

标签: node.js mongodb mongoose mongodb-query aggregation-framework

我试图查询属性,该属性是对另一个模式和一些其他数据的引用数组。为了更好地澄清,这里是架构:

    var orderSchema = new Schema({
        orderDate: Date,
        articles: [{
            article: {
                type: Schema.Types.ObjectId,
                ref: 'Article'
            },
            quantity: 'Number'
        }]
    }),
    Order = mongoose.model('Order', orderSchema);

虽然我设法成功查询了引用,即:

Order.find({}).populate('articles.article', null, {
    price: {
        $lte: 500
    }
}).exec(function(err, data) {
    for (var order of data) {
        for (var article of order.articles) {
            console.log(article);
        }
    }
});

我在查询quantity属性时遇到了一些问题,即这不起作用:

Order.find({}).where({
    'articles.quantity': {
        $gte: 5
    }
}).populate('articles.article', null, {
    /*price: {
        $lte: 500
    }*/
}).exec(function(err, data) {
    for (var order of data) {
        for (var article of order.articles) {
            console.log(article);
        }
    }
});

甚至可以将查询基于quantity吗?如果是这样,最好的方法是什么?

谢谢!

更新

问题是,结果是一个完整的数组,或者什么都没有(请参阅更新的问题)。我想只获得那些数量大于或等于5的记录。使用你的(和我的)方法,我得到根本没有记录(如果我设置$ gte:5001)或两个记录(如果我设置$ gte: 5000)

{
    "_id": ObjectId('56fe76c12f7174ac5018054f'),
    "orderDate": ISODate('2016-04-01T13:25:21.055Z'),
    "articles": [
        {
            "article": ObjectId('56fe76c12f7174ac5018054b'),
            "quantity": 5000,
            "_id": ObjectId('56fe76c12f7174ac50180551')
        },
        {
            "article": ObjectId('56fe76c12f7174ac5018054c'),
            "quantity": 1,
            "_id": ObjectId('56fe76c12f7174ac50180552')
        }
    ],
    "__v": 1
}

2 个答案:

答案 0 :(得分:3)

你需要"项目"所有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()语句中,但当然结果不再是" mongoose文档"因为.aggregate()的一个方面是它可以改变"文件结构,因此mongoose"假设"就是这种情况,只返回一个"普通对象"。

这不是一个真正的问题,因为当你看到$project阶段时,我们实际上是根据定义的模式要求文档中存在的所有相同字段。所以即使它只是一个普通的对象"没有问题"铸造"它回到了一个mongoose文件中。

这是.map()的来源,因为它会返回一个已转换的"文档"的数组,这对下一阶段非常重要。

现在你打电话给Model.populate()然后可以运行更多的"人口"在" mongoose文件数组"。

结果最终是你想要的。

MongoDB旧版本而不是3.2.x

这里真正改变的唯一事情就是聚合管道,所以为了简洁,所有这些都需要包含在内。

MongoDB 2.6 - 可以使用$map$setDifference的组合过滤数组。结果是" set"但是,当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 Alternative

另一个替代方案是在"服务器"上做所有事情。代替。这是MongoDB 3.2及更高版本$lookup的一个选项:

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()可以高兴地这样做,因为它只是另一个"查询"并填写"文件"方便。

所以,如果你真的没有保存" bucketloads"通过删除原始文档数组中的其他数组成员来带宽,然后在后处理代码中将它们.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 :(得分:-1)


exports.getStudentBy = async (req, res) => {
  try {
    // get search_criteria from query parameter
    // build a query object with it
    // send data to the frontend

    const { search_field, search_value } = req.query;

    const queryObj = {};

    if (search_field !== '' && search_value !== '') {
      queryObj[search_field] = search_value;
    }

    const student = await Student.find(queryObj);