CouchDB中的分页是什么?

47
如何实现分页所需的查询?
当请求第一页时,获取前5个条目。对于第二页,获取接下来的5个条目,以此类推。
我计划使用couchdb-python模块实现,但这不应影响实现。
4个回答

36

CouchDB Guide对于分页有一个很好的讨论,包括很多示例代码,在这里:http://guide.couchdb.org/draft/recipes.html#pagination

  • 从视图中请求rows_per_page + 1行数据。
  • 显示rows_per_page行数据,并将最后一行保存为next_startkey
  • 作为页面信息,保留startkeynext_startkey
  • 使用next_*值创建下一页链接,使用其他值创建上一页链接。

注意:在CouchDB中获取页面的正确方法是通过指定起始键而不是起始索引。但是如何知道第二页应该从哪个键开始呢?巧妙的解决方案是:“不是请求一页10行数据,而是请求11行数据,只显示其中的10行,并使用第11行的值作为下一页的起始键。”

如果您希望多个文档发出相同的键,则还需要使用startdocid来正确分页。原因是仅使用startkey将不再足以唯一标识一行。如果您不提供startkey,则这些参数将无用。实际上,CouchDB首先查看startkey参数,然后如果多个潜在的起始行具有相同的键但具有不同的文档ID,则使用startdocid参数进一步重新定义范围的开始。对于enddocid也是同样的情况。


4
这种方法的问题在于,您无法真正多次点击“上一页”,只能点击一次。当您转到下一页时,您要么必须手动索引所有可能出现在第一页的内容,要么只能返回1页,然后您就没有任何信息可以再返回到之前的页面了。 - Patrick
1
对于那些遇到@for3st的困境的人,数组的自然特性可以帮助解决这个问题。通过将上一页的起始_id推入数组中,您可以在单击“上一页”时轻松地pop()数组_id。最多只需要跟踪整数数组即可。 - wootencl

13

CouchDB的HTTP View API提供了充分的支持以高效地进行分页。

最简单的方法是使用startkeycount。Count是CouchDB将为该视图请求返回的最大条目数,这取决于您的设计,而startkey则是您希望CouchDB开始的位置。当您请求视图时,它还会告诉您有多少条目,让您可以计算出有多少页(如果您想向用户展示的话)。

因此,第一个请求不会指定startkey,只是指定要显示的条目数计数。然后您可以记录返回的最后一个条目的键,并将其用作下一页的起始键。在这个简单的形式中,您会得到一个重叠,其中一个页面的最后一个条目是下一个页面的第一个条目。如果这不是您想要的,那么简单地不显示页面最后一条目即可。

另一种更简单的方法是使用skip参数来确定页面的起始文档,但应谨慎使用此方法。skip参数仅仅是导致内部引擎不返回正在迭代的条目。虽然这会产生期望的行为,但比通过关键字找到页面的第一个文档要慢得多。跳过的文档数越多,请求速度就会越慢。


3
我再次进行了编辑。在大多数情况下,使用跳过(skip)并不是一个好的做法。 - Kerr
啊,我想这可能是情况...有没有更快的方法找到第x个键? - dbr
1
不,要找到第n个条目,你需要遍历索引树,因为你不知道树中一个分支有多少条目。但是你可以更快地找到特定的键。 - Kerr
2
看了你发的链接,我发现没有“count”选项。相反,有一个“limit”选项。也许它被更改了? - nivcaner
skip现在实际上非常高效(自从修复了COUCHDB-1076),并且它是一种在一般情况下安全迭代的唯一方法。但在startkey_docid分页方法中存在边缘情况,这将阻止其进展(例如,如果每个文档多次发出,以至于页面中的所有行都包含相同的键/文档ID)。 - Will Holley
显示剩余2条评论

1

目前为止,这是我想出来的方法——获取所有帖子的ID,然后检索前x个ID的实际项目。

虽然不是非常高效,但比检索所有帖子然后将大多数帖子丢掉要好得多。话虽如此,令我惊讶的是,它似乎运行得相当快——我运行了100次posthelper.page()方法,只用了大约0.5秒。

我不想在实际问题中发布这篇文章,因为这样会对答案产生更大的影响——这是代码:

allPostsUuid = """
function(doc) {
if(doc.type == 'post'){
    emit(doc._id, null);
}
}
"""

class PostsHelper:
    def __init__(self):
        server = Server(config.dbhost)
        db = server[config.dbname]
        return db


    def _getPostByUuid(self, uuid):
        return self.db.get(uuid)

    def page(self, number = 1):
        number -= 1 # start at zero offset
        start = number * config.perPage
        end = start + config.perPage

        allUuids = [
            x.key for x in self.db.query(allPostsUuid)
        ]
        ret = [
            self._getPostByUuid(x) for x in allUuids[start : end]
        ]

        if len(ret) == 0:
            raise Error404("Invalid page (%s results)" % (len(allUuids)))
        else:
            return ret

0
下面是我找到的递归方式: 定义两个变量
  var lastOffset = 0; var counter = 0;

  function someRecursive(lastOffset,counter) {

  queryView(db, whereClause).then(result => {
      var rows_per_page = 5; 

//formula below 
var page = Math.floor((lastOffset == 0 ? 0: (result.offset - lastOffset) +

  (rows_per_page * counter)) /  rows_per_page) + 1;

   var skip = page * rows_per_page;
  if (somerecursionexitcondition) {
                   counter = lastOffset == 0 ? lastOffset: counter + 1;
                   lastOffset =result.offset;
              someRecursive(lastOffset, counter).then(result => {
                               resolve();

                           });
  });

  }

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