Mongoose 查询过滤数组并填充相关内容 [英] Mongoose Query to filter an array and Populate related content

查看:27
本文介绍了Mongoose 查询过滤数组并填充相关内容的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试查询属性,该属性是对另一个架构的引用和一些附加数据的数组.为了更好地说明,这里是架构:

I'm trying to query the property that is an array of both reference to another schema and some additional data. For better clarification, here's the schema:

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

虽然我设法成功查询参考,即:

While I managed to successfully query the reference, i.e.:

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 属性时遇到了一些问题,即这不起作用:

I have some issues querying the quantity attribute, i.e. this doesn't work:

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 进行查询?如果是这样,最好的方法是什么?

Is it even possible to base the query on quantity? And if so, what would be the best approach?

谢谢!

更新:

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

The problem is, the result is either a complete array, or nothing (see updated question). I want to get only those records that have quantity more or the same as 5. With your (and mine) approach I get either no records at all (if I set $gte: 5001) or both records (if I set $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
}

推荐答案

您需要在此处投影"匹配项,因为 MongoDB 查询所做的只是查找具有 至少一个元素"的文档"大于"您要求的条件.

You need to "project" the match here since all the MongoDB query does is look for a "document" that has "at least one element" that is "greater than" the condition you asked for.

所以过滤一个数组"和你的查询"条件是不一样的.

So filtering an "array" is not the same as the "query" condition you have.

一个简单的投影"只会将第一个"匹配项返回到该条件.所以它可能不是你想要的,但作为一个例子:

A simple "projection" will just return the "first" matched item to that condtion. So it's probably not what you want, but as an example:

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" 数组中返回最多 一个 元素.

That "sort of" does what you want, but the problem is really going to be that will only ever return at most one element within the "articles" array.

要正确执行此操作,您需要 .aggregate() 来过滤数组内容.理想情况下,这是使用 MongoDB 3.2 和 $filter.但是这里还有一个特殊的.populate()方式:

To do this properly you need .aggregate() to filter the array content. Ideally this is done with MongoDB 3.2 and $filter. But there is also a special way to .populate() here:

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假定"就是这种情况,只返回一个普通对象".

So what happens here is the actual "filtering" of the array happens within the .aggregate() statement, but of course the result from this is no longer a "mongoose document" because one aspect of .aggregate() is that it can "alter" the document structure, and for this reason mongoose "presumes" that is the case and just returns a "plain object".

这不是一个真正的问题,因为当您看到 $project 阶段时,我们实际上是根据定义的模式要求文档中存在的所有相同字段.因此,即使它只是一个普通对象",将其转换"回 mongoose 文档也是没有问题的.

That's not really a problem, since when you see the $project stage, we are actually asking for all of the same fields present in the document according to the defined schema. So even though it's just a "plain object" there is no problem "casting" it back into an mongoose document.

这就是 .map() 的用武之地,因为它返回一个转换后的文档"数组,这对于下一阶段很重要.

This is where the .map() comes in, as it returns an array of converted "documents", which is then important for the next stage.

现在你调用 Model.populate() 然后可以在猫鼬文档数组"上运行进一步的人口".

Now you call Model.populate() which can then run the further "population" on the "array of mongoose documents".

结果就是你想要的.

这里唯一真正改变的是聚合管道,为了简洁起见,这就是所有需要包含的内容.

The only things that really change here are the aggregation pipeline, So that is all that needs to be included for brevity.

MongoDB 2.6 - 可以使用 $map$setDifference.结果是一个集合",但当 mongoose 默认在所有子文档数组上创建 _id 字段时,这不是问题:

MongoDB 2.6 - Can filter arrays with a combination of $map and $setDifference. The result is a "set" but that is not a problem when mongoose creates an _id field on all sub-document arrays by default:

    [
        { "$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:

Older revisions of than that must use $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 及更高版本:

The $lookup Alternative

Another alternate is to just do everything on the "server" instead. This is an option with $lookup of MongoDB 3.2 and greater:

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() 方法获得的结果相同.当然,如果你真的必须的话,你总是可以在所有情况下再次投射"到猫鼬文件.

And though those are just plain documents, it's just the same results as what you would have got from the .populate() approach. And of course you can always go and "cast" to mongoose documents in all cases again if you really must.

这实际上可以追溯到原始语句,您基本上只是接受"查询"并不意味着过滤"数组内容..populate() 可以很高兴地这样做,因为它只是另一个查询"并且为了方便而填充文档".

This really goes back to the orginal statement where you basically just "accept" that the "query" is not meant to "filter" the array content. The .populate() can happilly do so becuse it's just another "query" and is stuffing in "documents" by convenience.

因此,如果您确实没有通过删除原始文档数组中的其他数组成员来节省bucketloads"带宽,那么只需在后处理代码中将它们 .filter() 去掉:.filter()p>

So if you really are not saving "bucketloads" of bandwith by the removal of additional array members in the orginal document array, then just .filter() them out in post processing code:

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

这篇关于Mongoose 查询过滤数组并填充相关内容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆