对集合进行排序和分页

Sort and paginate a collection

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

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

有一个简单的 limit/skip 方法可以工作......慢慢来。

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

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}

?

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 }

-$

如果你想按 "page numbers" 分页,那么你几乎只能使用 .limit() and .skip() 方法对你的键进行排序后应用。您可能已经阅读了一些内容,发现它是 "not very efficient",主要是由于 "skipping" 通过 "n" 结果到达某个页面的成本。

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

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

如果您只需要在分页中移动 "forward",则可以使用更快的替代方法,并且对于 "sorted" 结果也是如此。

关键是保持对 "s" 的 "last seen" 值的引用,然后通常是 _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 },

为了获得 "two" 个结果中的 "first page" 个,您的第一个查询很简单:

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 值将为 3,而 lastSeen 将包含数组 [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"的选择需要从一个值"less than or equal to"开始(因为排序的方向)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
})

因此,通过遵循该逻辑模式,您可以 "store" 从 "previousc page" 结果中找到的信息,并非常有效地 "forward" 移动结果。

但是如果你需要跳转到"page 20"或类似类型的操作,那么你就会被.limit().skip()卡住。那样比较慢,但这取决于你能忍受什么。