按照数组大小对结果进行排序

3

我有一个名为 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
}
1个回答

3
你需要使用.aggregate()方法,因为所有的“sort”参数必须是文档中存在的字段,这样你就可以将数组的$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
    }
);

当然,这样做会有代价,因为您需要在每次迭代中计算大小。所以最好的做法是在每个更新操作中在文档内保留“投票数”计数,并且使用批量操作适用于所有情况:
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”是这个过程的相反过程。

这里的关键在于,每次投票时,“计数”或“得分”会同时更新。这样可以进行普通查询和排序,而无需每次访问数据时都进行计算,因为已经完成了。

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


我实际上没有将它们链接在一起。它们分别显示了赞成票和反对票的计数。你能简化代码只为此吗? - Ayush Gupta
1
@AyushGupta 在聚合的情况下,只需删除“$subtract”,并返回您要排序的数组的“$size”。当然,实际要点是存储计数,因此最好在更新每个包含计数的数组时存储字段,具体取决于是否已删除或添加项目。这个原则应该很容易遵循。 - Blakes Seven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接