3.2.5. 分页食谱¶
本食谱介绍如何对视图结果进行分页。分页是一种用户界面 (UI) 模式,允许显示大量行(结果集),而无需一次将所有行加载到 UI 中。一个固定大小的子集,即页面,与下一个和上一个链接或按钮一起显示,这些链接或按钮可以将视窗在结果集上移动到相邻页面。
我们假设您熟悉创建和查询文档和视图,以及多个视图查询选项。
3.2.5.1. 示例数据¶
为了获得一些可操作的数据,我们将创建一个乐队列表,每个乐队一个文档
{ "name":"Biffy Clyro" }
{ "name":"Foo Fighters" }
{ "name":"Tool" }
{ "name":"Nirvana" }
{ "name":"Helmet" }
{ "name":"Tenacious D" }
{ "name":"Future of the Left" }
{ "name":"A Perfect Circle" }
{ "name":"Silverchair" }
{ "name":"Queens of the Stone Age" }
{ "name":"Kerub" }
3.2.5.2. 一个视图¶
我们需要一个简单的映射函数,它可以提供乐队名称的字母顺序列表。这应该很容易,但我们添加了一些额外的智能功能,以过滤掉乐队名称前面的“The”和“A”,并将它们放到正确的位置
function(doc) {
if(doc.name) {
var name = doc.name.replace(/^(A|The) /, "");
emit(name, null);
}
}
视图结果是乐队名称的字母顺序列表。现在假设我们想一次显示五个乐队名称,并有一个指向构成一页的接下来的五个名称的链接,以及一个指向上一页的链接(如果我们不在第一页)。
我们已经了解了如何在之前的文档中使用startkey
、limit
和skip
参数。我们将在本文中再次使用它们。首先,让我们看一下完整的结果集
{"total_rows":11,"offset":0,"rows":[
{"id":"a0746072bba60a62b01209f467ca4fe2","key":"Biffy Clyro","value":null},
{"id":"b47d82284969f10cd1b6ea460ad62d00","key":"Foo Fighters","value":null},
{"id":"45ccde324611f86ad4932555dea7fce0","key":"Tenacious D","value":null},
{"id":"d7ab24bb3489a9010c7d1a2087a4a9e4","key":"Future of the Left","value":null},
{"id":"ad2f85ef87f5a9a65db5b3a75a03cd82","key":"Helmet","value":null},
{"id":"a2f31cfa68118a6ae9d35444fcb1a3cf","key":"Nirvana","value":null},
{"id":"67373171d0f626b811bdc34e92e77901","key":"Kerub","value":null},
{"id":"3e1b84630c384f6aef1a5c50a81e4a34","key":"Perfect Circle","value":null},
{"id":"84a371a7b8414237fad1b6aaf68cd16a","key":"Queens of the Stone Age","value":null},
{"id":"dcdaf08242a4be7da1a36e25f4f0b022","key":"Silverchair","value":null},
{"id":"fd590d4ad53771db47b0406054f02243","key":"Tool","value":null}
]}
3.2.5.3. 设置¶
分页的机制非常简单
显示第一页
如果有更多行要显示,则显示下一个链接
绘制后续页面
如果这不是第一页,则显示上一个链接
如果有更多行要显示,则显示下一个链接
或者在一个伪 JavaScript 代码片段中
var result = new Result();
var page = result.getPage();
page.display();
if(result.hasPrev()) {
page.display_link('prev');
}
if(result.hasNext()) {
page.display_link('next');
}
3.2.5.4. 分页¶
要从视图结果中获取前五行,您可以使用?limit=5
查询参数
curl -X GET http://127.0.0.1:5984/artists/_design/artists/_view/by-name?limit=5
结果
{"total_rows":11,"offset":0,"rows":[
{"id":"a0746072bba60a62b01209f467ca4fe2","key":"Biffy Clyro","value":null},
{"id":"b47d82284969f10cd1b6ea460ad62d00","key":"Foo Fighters","value":null},
{"id":"45ccde324611f86ad4932555dea7fce0","key":"Tenacious D","value":null},
{"id":"d7ab24bb3489a9010c7d1a2087a4a9e4","key":"Future of the Left","value":null},
{"id":"ad2f85ef87f5a9a65db5b3a75a03cd82","key":"Helmet","value":null}
]}
通过将total_rows
值与我们的limit
值进行比较,我们可以确定是否还有更多页面要显示。我们还通过offset成员知道我们位于第一页。我们可以计算skip=
的值以获取下一页的结果
var rows_per_page = 5;
var page = (offset / rows_per_page) + 1; // == 1
var skip = page * rows_per_page; // == 5 for the first page, 10 for the second ...
因此,我们使用以下命令查询 CouchDB:
curl -X GET 'http://127.0.0.1:5984/artists/_design/artists/_view/by-name?limit=5&skip=5'
请注意,我们必须使用'
(单引号)来转义&
字符,该字符对我们执行 curl 的 shell 来说是特殊的。
结果
{"total_rows":11,"offset":5,"rows":[
{"id":"a2f31cfa68118a6ae9d35444fcb1a3cf","key":"Nirvana","value":null},
{"id":"67373171d0f626b811bdc34e92e77901","key":"Kerub","value":null},
{"id":"3e1b84630c384f6aef1a5c50a81e4a34","key":"Perfect Circle","value":null},
{"id":"84a371a7b8414237fad1b6aaf68cd16a","key":"Queens of the Stone Age",
"value":null},
{"id":"dcdaf08242a4be7da1a36e25f4f0b022","key":"Silverchair","value":null}
]}
实现hasPrev()
和hasNext()
方法非常简单
function hasPrev()
{
return page > 1;
}
function hasNext()
{
var last_page = Math.floor(total_rows / rows_per_page) +
(total_rows % rows_per_page);
return page != last_page;
}
3.2.5.5. 分页(备用方法)¶
上述方法在 CouchDB 1.2 之前对较大的跳过值执行效率低下。此外,即使使用更新版本的 CouchDB,某些用例也可能需要以下备用方法。其中一个案例是当需要防止重复结果时。仅使用 skip,新文档可能会在分页期间插入,这可能会改变后续页面的起始偏移量。
正确的解决方案并不难。与其将结果集切分成大小相等的页面,我们一次查看 10 行,并使用startkey
跳到接下来的 10 行。我们甚至使用 skip,但仅使用值 1。
以下是它的工作原理
从视图中请求rows_per_page + 1行
显示rows_per_page行,store + 1行作为
next_startkey
和next_startkey_docid
作为页面信息,保留
startkey
和next_startkey
使用
next_*
值创建下一个链接,并使用其他值创建上一个链接
查找下一页的技巧非常简单。与其为一页请求 10 行,您请求 11 行,但只显示 10 行,并将第 11 行中的值用作下一页的startkey
。填充指向上一页的链接就像将当前startkey
传递到下一页一样简单。如果没有上一个startkey
,我们就在第一页。如果我们获得的rows_per_page行或更少,我们将停止显示指向下一页的链接。这被称为链接列表分页,因为我们从一页到另一页,或从一个列表项到另一个列表项,而不是直接跳转到预先计算的页面。不过,有一个注意事项。你能发现它吗?
CouchDB 视图键不必是唯一的;您可以读取多个索引条目。如果一个键的索引条目多于应该在一页上的行数怎么办?startkey
跳转到第一行,如果您没有额外的参数可以使用,您就完蛋了。具有相同值的视图键在内部按docid排序,即创建该视图行的文档的 ID。您可以使用startkey_docid
和endkey_docid
参数来获取这些行的子集。对于分页,我们仍然不需要endkey_docid
,但startkey_docid
非常方便。除了startkey
和limit
之外,您还使用startkey_docid
进行分页,当且仅当您获取的额外行(用于查找下一页)与当前startkey
具有相同的键时。
重要的是要注意,*_docid
参数仅在*key
参数之外有效,并且仅用于进一步缩小单个键的视图结果集。它们不能单独使用(唯一的例外是内置的_all_docs 视图,该视图已按文档 ID 排序)。
这种方法的优点是所有键操作都可以在视图背后的超快速 B 树索引上执行。查找页面不包括不必要地扫描数百或数千行。
3.2.5.6. 跳转到页面¶
链接列表式分页的一个缺点是,您无法从页码和每页行数预先计算特定页面的行。跳转到特定页面实际上行不通。如果有人提出这个问题,我们的本能反应是,“甚至谷歌都没有这样做!”,而且我们往往能摆脱它。谷歌在第一页上总是假装找到另外 10 页的结果。只有当您点击第二页(很少有人真正这样做)时,谷歌才会显示减少的页面集。如果您浏览结果,您将获得指向前 10 页和后 10 页的链接,但不会更多。预先计算 20 页所需的startkey
和startkey_docid
是一项可行的操作,也是一种实用的优化,可以了解可能包含数万行或更多行的结果集中的每一页的行。
如果您确实需要在整个文档范围内跳转到页面(我们已经看到一些应用程序需要这样做),您仍然可以将整数值索引作为视图索引,并采用混合方法来解决分页问题。