3.2.2. 视图排序

3.2.2.1. 基础

视图函数指定每个行的键和值。CouchDB 通过此键对视图行进行排序。在以下示例中,LastName 属性用作键,因此结果将按 LastName 排序

function(doc) {
    if (doc.Type == "customer") {
        emit(doc.LastName, {FirstName: doc.FirstName, Address: doc.Address});
    }
}

CouchDB 允许使用任意 JSON 结构作为键。您可以使用 JSON 数组作为键来对排序和分组进行细粒度控制。

3.2.2.2. 示例

以下巧妙的技巧将返回客户和订单文档。键由客户 _id 和排序标记组成。因为订单文档的键以客户文档的 _id 开头,所以所有订单将按客户排序。因为客户的排序标记低于订单的标记,所以客户文档将出现在关联的订单之前。排序标记的 0 和 1 值是任意的。

function(doc) {
    if (doc.Type == "customer") {
        emit([doc._id, 0], null);
    } else if (doc.Type == "order") {
        emit([doc.customer_id, 1], null);
    }
}

要列出具有 _id XYZ 的特定客户及其所有订单,请将 startkey 和 endkey 范围限制为仅涵盖该客户的 _id 的文档

startkey=["XYZ"]&endkey=["XYZ", {}]

不建议在视图中发出文档本身。相反,要包含请求视图时的文档主体,请使用 ?include_docs=true 请求视图。

3.2.2.3. 按日期排序

将日期属性存储在人类可读的格式(即作为 字符串)中,但仍然按日期排序可能很方便。这可以通过在 emit() 函数中将日期转换为 数字 来完成。例如,给定一个具有 'Wed Jul 23 16:29:21 +0100 2013' 的 created_at 属性的文档,以下 emit 函数将按日期排序

emit(Date.parse(doc.created_at).getTime(), null);

或者,如果您使用按字典顺序排序的日期格式,例如 "2013/06/09 13:52:11 +0000",您可以直接

emit(doc.created_at, null);

并避免转换。作为奖励,此日期格式与 JavaScript 日期解析器兼容,因此您可以在客户端 JavaScript 中使用 new Date(doc.created_at) 使浏览器中的日期排序变得容易。

3.2.2.4. 字符串范围

如果您需要包含具有给定前缀的所有字符串的 start 和 end 键,最好使用高值 Unicode 字符,而不是使用 'ZZZZ' 后缀。

也就是说,而不是

startkey="abc"&endkey="abcZZZZZZZZZ"

您应该使用

startkey="abc"&endkey="abc\ufff0"

3.2.2.5. 排序规范

本节基于 view_collation.js 中的 view_collation 函数。

// special values sort before all other types
null
false
true

// then numbers
1
2
3.0
4

// then text, case sensitive
"a"
"A"
"aa"
"b"
"B"
"ba"
"bb"

// then arrays. compared element by element until different.
// Longer arrays sort after their prefixes
["a"]
["b"]
["b","c"]
["b","c", "a"]
["b","d"]
["b","d", "e"]

// then object, compares each key value in the list until different.
// larger objects sort after their subset objects.
{a:1}
{a:2}
{b:1}
{b:2}
{b:2, a:1} // Member order does matter for collation.
           // CouchDB preserves member order
           // but doesn't require that clients will.
           // this test might fail if used with a js engine
           // that doesn't preserve order
{b:2, c:2}

字符串的比较使用 ICU 完成,它实现了 Unicode 排序算法,对键进行字典排序。如果您期望 ASCII 排序,这可能会产生令人惊讶的结果。请注意

  • 所有符号都排在数字和字母之前(即使是“高”符号,如波浪号,0x7e

  • 不考虑大小写比较不同的字母序列,因此 a < aa 但也有 A < aaa < AA

  • 相同的字母序列按大小写进行比较,小写字母排在大写字母之前,因此 a < A

您可以通过以下方式演示 7 位 ASCII 字符的排序顺序

require 'rubygems'
require 'restclient'
require 'json'

DB="http://127.0.0.1:5984/collator"

RestClient.delete DB rescue nil
RestClient.put "#{DB}",""

(32..126).each do |c|
    RestClient.put "#{DB}/#{c.to_s(16)}", {"x"=>c.chr}.to_json
end

RestClient.put "#{DB}/_design/test", <<EOS
{
    "views":{
        "one":{
            "map":"function (doc) { emit(doc.x,null); }"
        }
    }
}
EOS

puts RestClient.get("#{DB}/_design/test/_view/one")

这表明排序顺序为

` ^ _ - , ; : ! ? . ' " ( ) [ ] { } @ * / \ & # % + < = > | ~ $ 0 1 2 3 4 5 6 7 8 9
a A b B c C d D e E f F g G h H i I j J k K l L m M n N o O p P q Q r R s S t T u U v V w W x X y Y z Z

3.2.2.5.1. 键范围

在查询键范围时要格外小心。例如:查询

startkey="Abc"&endkey="AbcZZZZ"

将匹配“ABC”和“abc1”,但不匹配“abc”。这是因为 UCA 排序为

abc < Abc < ABC < abc1 < AbcZZZZZ

对于大多数应用程序,为了避免问题,您应该将 startkey 转换为小写

startkey="abc"&endkey="abcZZZZZZZZ"

将匹配所有以 [aA][bB][cC] 开头的键

3.2.2.5.2. 复杂键

查询 startkey=["foo"]&endkey=["foo",{}] 将匹配大多数第一个元素为“foo”的数组键,例如 ["foo","bar"]["foo",["bar","baz"]]。但是它不会匹配 ["foo",{"an":"object"}]

3.2.2.6. _all_docs

_all_docs 视图是一个特例,因为它对文档 ID 使用 ASCII 排序,而不是 UCA

startkey="_design/"&endkey="_design/ZZZZZZZZ"

将找不到 _design/abc,因为在 ASCII 序列中 ‘Z’ 位于 ‘a’ 之前。更好的解决方案是

startkey="_design/"&endkey="_design0"

3.2.2.7. 原生排序

为了从视图中挤出更多性能,您可以为原生 Erlang 排序指定 "options":{"collation":"raw"},尤其是在您不需要 UCA 的情况下。这将提供不同的排序顺序

1
false
null
true
{"a":"a"},
["a"]
"a"

请注意,{} 不再是合适的“高”键哨兵值。请改用类似 "\ufff0" 的字符串。