【问题标题】:Sort results by array size按数组大小对结果进行排序
【发布时间】:2016-01-06 21:38:16
【问题描述】:

我有一个名为 article 的集合,我需要按照它包含的数组的大小对返回的对象进行排序。做这个的最好方式是什么? 我想到的一种方法是检索整个对象列表并在 JS 中手动对其进行排序。但是我一次返回 10 篇文章,所以每次调用这个 API 时,它都必须做不必要的数组排序工作。

Article.find({})
     .limit(10)
     .skip(req.params.page*10)
     //Something like using $project to make a new variable called votecount that counts objects in a given array.
     .sort({votecount:-1})
     .exec(function(err,arts){
      articleObj.articles = arts;
        if (arts.length<10){
          articleObj.reachedEnd = true;
        }
        res.json(articleObj);
     });

我需要计算票数。这是一个示例对象:

{
"_id" : ObjectId("55f50cfddcf1ad6931fb8dd4"),
"timestamp" : "2015-09-13T00:58:57-5:00",
"url" : "http://www.nytimes.com/2015/09/13/sports/floyd-mayweather-finishes-bout-and-maybe-his-career-with-lopsided-win-over-andre-berto.html",
"abstract" : "Mayweather’s victory by unanimous decision gave him a record of 49-0, the same as the legendary heavyweight Rocky Marciano.",
"title" : "Mayweather Wins Easily in What He Calls Last Bout",
"section" : "Sports",
"comments" : [ ],
"votes" : {
    "up" : [
        ObjectId("55e5e16934d355d61c471e48")
    ],
    "down" : [ ]
},
"image" : {
    "caption" : "Floyd Mayweather Jr. after learning he defeated Andre Berto in a unanimous decision.",
    "url" : "http://static01.nyt.com/images/2015/09/14/sports/13fight/13fight-mediumThreeByTwo210.jpg"
},
"__v" : 0
}

【问题讨论】:

    标签: javascript mongodb mongoose mongodb-query aggregation-framework


    【解决方案1】:

    您需要使用.aggregate() 方法,因为所有“排序”参数都必须是文档中存在的字段,这样您就可以将数组的$size“投影”到文档中进行排序:

    Article.aggregate(
        [
            { "$project": {
                "timestamp": 1,
                "url": 1,
                "abstract": 1,
                "title": 1,
                "section": 1,
                "comments": 1,
                "votes": 1,
                "image": 1,
                "voteCount": { 
                    "$subtract": [
                        { "$size": "$votes.up" },
                        { "$size": "$votes.down" }
                    ]
                }
            }},
            { "$sort": { "voteCount": -1 } },
            { "$skip": req.params.page*10 },
            { "$limit": 10 },
        ],
        function(err,results) {
            // results here
        }
    );
    

    当然,这是有代价的,因为您基本上需要在每次迭代中计算大小。因此,更好的做法是在每次更新操作时保持文档中的“计数”,并且更好地使用Bulk Operations 以适应所有情况:

    var bulk = Atricle.collection.intializeOrderedBulkOp();
    
    // Swap out a downvote where present
    bulk.find({ 
        "_id": id, 
        "votes.up": { "$ne": userId },
        "votes.down": userId
    }).updateOne({
        "$push": { "votes.up": userId },
        "$pull": { "votes.down": userId }
        "$inc": { "voteCount": 2 }
    });
    
    // Add an upvote where not present
    bulk.find({ 
        "_id": id, 
        "votes.up": { "$ne": userId },
        "votes.down": { "$ne": userId }
    }).updateOne({
        "$push": { "votes.up": userId },
        "$inc": { "voteCount": 1 }
    });
    
    bulk.execute(function(err,response) {
        // maybe do something here
    });
    

    当然,“downvote”与此过程相反。

    这里的要点是,在处理每个投票时,“计数”或“分数”会同时保持更新。这允许进行正常的查询和排序,而无需在每次访问数据时都进行计算,因为它已经完成了。

    后一种情况是处理此问题的最有效方式。

    【讨论】:

    • 我实际上并没有将两者联系起来。他们分别显示赞成票和反对票的数量。你能为此简化代码吗?
    • @AyushGupta 在聚合情况下,只需删除 $subtract 并返回要排序的数组的 $size。当然,真正的重点是存储计数,因此最好在更新每个包含计数的数组时存储字段,具体取决于是否删除或添加了项目。这个原则应该很容易遵循。
    猜你喜欢
    • 2018-07-25
    • 2013-09-18
    • 2015-01-27
    • 2012-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-18
    相关资源
    最近更新 更多