【问题标题】:Sort and paginate a collection对集合进行排序和分页
【发布时间】:2015-09-18 17:09:27
【问题描述】:

如何对非唯一字段排序的查询进行分页?例如,集合中的文档可能是(按 s:1 排序,然后是 _id:-1):

{_id: 19, s: 3},
{_id: 17, s: 3},
{_id: 58, s: 4},
// etc...

有一个简单的限制/跳过方法可以工作......缓慢。

是否可以使用类似的东西:

db.collection.find()
  .sort({s:1, _id:-1})
  .min({s:3, _id:17})    // this does not work as wanted!
  .limit(2);

检索

{_id: 17, s: 3},
{_id: 58, s: 4}

?

【问题讨论】:

    标签: mongodb sorting pagination


    【解决方案1】:

    如果您想按“页码”进行分页,那么在您对键上的结果进行排序后,您几乎会被.limit().skip() 方法所困扰。您可能已经阅读并发现它“效率不高”,主要是由于“跳过”“n”个结果以到达某个页面的成本。

    但原则在你需要的地方是合理的:

    db.collection.find().sort({ "s": -1, "_id": 1 }).skip(<page-1>).limit(<pageSize>)
    

    如果您只需要在分页中“向前”移动,则可以使用更快的替代方法,也可以用于“排序”结果。

    关键是保持对“s”的“最后一次看到”值的引用,然后通常是_id值的列表,直到“s”的值发生变化。所以用更多的文档进行演示,为了演示目的已经排序:

    { "_id": 1, "s": 3 },
    { "_id": 2, "s": 3 },
    { "_id": 3, "s": 3 },
    { "_id": 4, "s": 2 },
    { "_id": 5, "s": 1 },
    { "_id": 6, "s": 1 },
    

    为了获得“两个”结果的“第一页”,您的第一个查询很简单:

    db.collection.find().sort({ "s": -1, "_id": 1}).limit(2)
    

    但在处理文档时要遵循这一点:

    var lastVal = null,
        lastSeen = [];
    
    db.collection.find().sort({ "s": -1, "_id": 1}).limit(2).forEach(function(doc) {
        if ( doc.s != lastVal ) {    // Change when different
            lastVal = doc.s;
            lastSeen = [];
        }
        lastSeen.push(doc._id);      // Push _id onto array
        // do other things like output
    })
    

    因此,在第一次迭代中,lastVal 的值将是 3lastSeen 将包含数组 [1,2] 中的两个文档 _id 值。 您可以将这些内容存储在等待下一页请求的用户会话数据中。

    根据您对下一页设置的请求,您发出以下命令:

    var lastVal = 3,
        lastSeen = [1,2];
    
    db.collection.find({ 
        "_id": { "$nin": lastSeen }, 
        "s": { "$lte": lastVal }
    }).sort({ "s": -1, "_id": 1}).limit(2).forEach(function(doc) {
        if ( doc.s != lastVal ) {    // Change when different
            lastVal = doc.s;
            lastSeen = [];
        }
        lastSeen.push(doc._id);      // Push _id onto array
        // do other things like output
    })
    

    这要求“s”的选择都需要从记录的lastVal的值“小于或等于”(由于排序的方向)开始,并且“_id”字段不能包含lastSeen中记录的值。

    生成的下一页是:

    { "_id": 3, "s": 3 },
    { "_id": 4, "s": 2 },
    

    但是现在如果你按照逻辑lastVal 当然是2lastSeen 现在只有一个数组元素[4]。由于下一个查询只需要从 2 开始作为一个小于或等于的值,因此无需保留其他先前看到的“_id”值,因为它们不会在该选择范围内。

    然后这个过程就开始了:

    var lastVal = 2,
        lastSeen = [2];
    
    db.collection.find({ 
        "_id": { "$nin": lastSeen }, 
        "s": { "$lte": lastVal }
    }).sort({ "s": -1, "_id": 1}).limit(2).forEach(function(doc) {
        if ( doc.s != lastVal ) {    // Change when different
            lastVal = doc.s;
            lastSeen = [];
        }
        lastSeen.push(doc._id);      // Push _id onto array
        // do other things like output
    })
    

    因此,通过遵循这种逻辑模式,您可以“存储”从结果的“上一页”中找到的信息,并非常有效地在结果中“前进”。

    但如果您需要跳转到“第 20 页”或类似类型的操作,那么您将被 .limit().skip() 卡住。那样会慢一些,但这取决于你能忍受什么。

    【讨论】:

    • 不错的解决方案,@blakes-seven 你能解释一下为什么在获取第二页时你使用 lastSeen = [2] 而不是 [4]?
    • 您也可以应用条件来获取s &lt; lastVal or (s == lastVal and _id &gt; lastId)。这样你就可以省略比简单比较慢的 IN 操作
    【解决方案2】:
    db.t1.drop()
    db.t1.save({_id:19, s:3})
    db.t1.save({_id:17, s:3})
    db.t1.save({_id:58, s:4})
    
    db.t1.find().sort({s:1, _id:-1}).skip(1).limit(2)
    
    --Result
    { "_id" : 17, "s" : 3 }
    { "_id" : 58, "s" : 4 }
    

    -$

    【讨论】:

    • 我认为 -1 是因为在没有任何解释的情况下跳入代码并且(可能)使用了 .skip().limit()
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-06
    • 1970-01-01
    • 2012-12-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多