SORT:对键的值进行排序

用户可以通过执行 SORT 命令,对列表元素、集合元素或者有序集合成员进行排序。为了让用户能够以不同的方式进行排序,Redis 为 SORT 命令提供了非常多的可选项,如果我们以不给定任何可选项的方式直接调用 SORT 命令,那么命令将对指定键储存的元素执行数字值排序:

  1. SORT key

在默认情况下,SORT 命令将按照从小到大的顺序,依次返回排序后的各个值。

比如说,以下例子就展示了如何对 lucky-numbers 集合储存的六个数字值进行排序:

  1. redis> SMEMBERS lucky-numbers -- 以乱序形式储存的集合元素
  2. 1) "1024"
  3. 2) "123456"
  4. 3) "10086"
  5. 4) "3.14"
  6. 5) "888"
  7. 6) "256"
  8.  
  9. redis> SORT lucky-numbers -- 排序后的集合元素
  10. 1) "3.14"
  11. 2) "256"
  12. 3) "888"
  13. 4) "1024"
  14. 5) "10086"
  15. 6) "123456"

而以下例子则展示了如何对 message-queue 列表中的数字值进行排序:

  1. redis> LRANGE message-queue 0 -1 -- 根据插入顺序进行排列的列表元素
  2. 1) "1024"
  3. 2) "256"
  4. 3) "128"
  5. 4) "512"
  6. 5) "64"
  7.  
  8. redis> SORT message-queue -- 排序后的列表元素
  9. 1) "64"
  10. 2) "128"
  11. 3) "256"
  12. 4) "512"
  13. 5) "1024"

指定排序方式

在默认情况下,SORT 命令执行的是升序排序操作:较小的值将被放到结果的较前位置,而较大的值则会被放到结果的较后位置。

通过使用可选的 ASC 选项或者 DESC 选项,用户可以指定 SORT 命令的排序方式,其中 ASC 表示执行升序排序操作,而 DESC 则表示执行降序排序操作:

  1. SORT key [ASC|DESC]

降序排序操作的做法跟升序排序操作的做法正好相反,它会把较大的值放到结果的较前位置,而较小的值则会被放到结果的较后位置。

举个例子,如果我们想要对 lucky-numbers 集合的元素实施降序排序,那么只需要执行以下代码就可以了:

  1. redis> SORT lucky-numbers DESC
  2. 1) "123456"
  3. 2) "10086"
  4. 3) "1024"
  5. 4) "888"
  6. 5) "256"
  7. 6) "3.14"

因为 SORT 命令在默认情况下进行的就是升序排序,所以 SORT key 命令和 SORT key ASC 命令产生的效果是完全相同的,因此我们在一般情况下并不会用到 ASC 选项 ——除非你有特别的理由,需要告诉别人你正在进行的是升序排序。

对字符串值进行排序

SORT 命令在默认情况下进行的是数字值排序,如果我们尝试直接使用 SORT 命令去对字符串元素进行排序,那么命令将产生一个错误:

  1. redis> SMEMBERS fruits
  2. 1) "cherry"
  3. 2) "banana"
  4. 3) "apple"
  5. 4) "mango"
  6. 5) "dragon fruit"
  7. 6) "watermelon"
  8.  
  9. redis> SORT fruits
  10. (error) ERR One or more scores can't be converted into double

为了让 SORT 命令能够对字符串值进行排序,我们必须让 SORT 命令执行字符串排序操作而不是数字值排序操作,这一点可以通过使用 ALPHA 选项来实现:

  1. SORT key [ALPHA]

作为例子,我们可以使用带 ALPHA 选项的 SORT 命令去对 fruits 集合进行排序:

  1. redis> SORT fruits ALPHA
  2. 1) "apple"
  3. 2) "banana"
  4. 3) "cherry"
  5. 4) "dragon fruit"
  6. 5) "mango"
  7. 6) "watermelon"

又或者使用以下命令,对 test-record 有序集合的成员进行排序:

  1. redis> ZRANGE test-record 0 -1 WITHSCORES -- 在默认情况下,有序集合成员将根据分值进行排序
  2. 1) "ben"
  3. 2) "70"
  4. 3) "aimee"
  5. 4) "86"
  6. 5) "david"
  7. 6) "99"
  8. 7) "cario"
  9. 8) "100"
  10.  
  11. redis> SORT test-record ALPHA -- 但使用 SORT 命令可以对成员本身进行排序
  12. 1) "aimee"
  13. 2) "ben"
  14. 3) "cario"
  15. 4) "david"

只获取部分排序结果

在默认情况下,SORT 命令将返回所有被排序的元素,但如果我们只需要其中一部分排序结果的话,那么可以使用可选的 LIMIT 选项:

  1. SORT key [LIMIT offset count]

其中 offset 参数用于指定返回结果之前需要跳过的元素数量,而 count 参数则用于指定需要获取的元素数量。

举个例子,如果我们想要知道 fruits 集合在排序之后的第 3 个元素是什么,那么只需要执行以下调用就可以了:

  1. redis> SORT fruits ALPHA LIMIT 2 1
  2. 1) "cherry"

注意,因为 offset 参数的值是从 0 开始计算的,所以这个命令在获取第三个被排序元素时使用了 2 而不是 3 来作为偏移量。

获取外部键的值作为结果

在默认情况下,SORT 命令将返回被排序的元素作为结果,但如果用户有需要的话,也可以使用 GET 选项去获取其他别的值作为排序结果:

  1. SORT key [[GET pattern] [GET pattern] ...]

一个 SORT 命令可以使用任意多个 GET pattern 选项,其中 pattern 参数的值可以是:

  • 包含 * 符号的字符串;

  • 包含 * 符号和 -> 符号的字符串;

  • 一个单独的 # 符号;

接下来的三个小节将分别介绍这三种值的用法和用途。

1. 获取字符串键的值

pattern 参数的值是一个包含 符号的字符串时,SORT 命令将把被排序的元素与 符号实行替换,构建出一个键名,然后使用 GET 命令去获取该键的值。


表 11-2 储存水果价格的各个字符串键,以及它们的值

字符串键
"apple-price"8.5
"banana-price"4.5
"cherry-price"7
"dragon fruit-price"6
"mango-price"5
"watermelon-price"3.5

举个例子,假设数据库里面储存着表 11-2 所示的一些字符串键,那么我们可以通过执行以下命令,对 fruits 集合的各个元素进行排序,然后根据排序后的元素去获取各种水果的价格:

  1. redis> SORT fruits ALPHA GET *-price
  2. 1) "8.5"
  3. 2) "4.5"
  4. 3) "7"
  5. 4) "6"
  6. 5) "5"
  7. 6) "3.5"

这个 SORT 命令的执行过程可以分为以下三个步骤:

  • fruits 集合的各个元素进行排序,得出一个由 "apple""banana""cherry""dragon fruit""mango""watermelon" 组成的有序元素排列。

  • 将排序后的各个元素与 *-price 模式进行匹配和替换,得出键名 "apple-price""banana-price""cherry-price""dragon fruit-price""mango-price""watermelon-price"

  • 使用 GET 命令去获取以上各个键的值,并将这些值依次放入到结果列表里面,最后把结果列表返回给客户端。

图 11-2 以图形方式展示了整个 SORT 命令的执行过程。


图 11-2 SORT fruits ALPHA GET *-price 命令的执行过程_images/IMAGE_SORT_GET_PROCESS.png


2. 获取散列中的键值

pattern 参数的值是一个包含 符号和 -> 符号的字符串时,SORT 命令将使用 -> 左边的字符串为散列名,-> 右边的字符串为字段名,调用 HGET 命令,从散列里面获取指定字段的值。此外,用户传入的散列名还需要包含 符号,这个 * 符号将被替换成被排序的元素。


表 11-3 储存着水果信息的散列

散列名散列中 inventory 字段的值
apple-info“1000”
banana-info“300”
cherry-info“50”
dragon fruit-info“500”
mango-info“250”
watermelon-info“324”

举个例子,假设数据库里面储存着表 11-3 所示的 apple-infobanana-info 等散列,而这些散列的 inventory 键则储存着相应水果的存货量,那么我们可以通过执行以下命令,对 fruits 集合的各个元素进行排序,然后根据排序后的元素去获取各种水果的存货量:

  1. redis> SORT fruits ALPHA GET *-info->inventory
  2. 1) "1000"
  3. 2) "300"
  4. 3) "50"
  5. 4) "500"
  6. 5) "250"
  7. 6) "324"

这个 SORT 命令的执行过程可以分为以下三个步骤:

  • fruits 集合的各个元素进行排序,得出一个由 "apple""banana""cherry""dragon fruit""mango""watermelon" 组成的有序元素排列。

  • 将排序后的各个元素与 *-info 模式进行匹配和替换,得出散列名 "apple-info""banana-info""cherry-info""dragon fruit-info""mango-info""watermelon-info"

  • 使用 HGET 命令,从以上各个散列中取出 inventory 字段的值,并将这些值依次放入到结果列表里面,最后把结果列表返回给客户端。

图 11-3 以图形方式展示了整个 SORT 命令的执行过程。


图 11-3 SORT fruits ALPHA GET *-info->inventory 命令的执行过程_images/IMAGE_SORT_HASH_GET_PROCESS.png


3. 获取被排序元素本身

pattern 参数的值是一个 # 符号时,SORT 命令将返回被排序的元素本身。

因为 SORT key 命令和 SORT key GET # 命令返回的是完全相同的结果,所以单独使用 GET # 并没有任何实际作用:

  1. redis> SORT fruits ALPHA
  2. 1) "apple"
  3. 2) "banana"
  4. 3) "cherry"
  5. 4) "dragon fruit"
  6. 5) "mango"
  7. 6) "watermelon"
  8.  
  9. redis> SORT fruits ALPHA GET # -- 与上一个命令的结果完全相同
  10. 1) "apple"
  11. 2) "banana"
  12. 3) "cherry"
  13. 4) "dragon fruit"
  14. 5) "mango"
  15. 6) "watermelon"

因此,我们一般只会在同时使用多个 GET 选项时,才使用 GET # 获取被排序的元素。比如说,以下代码就展示了如何在对水果进行排序的同时,获取水果的价格和库存量:

  1. redis> SORT fruits ALPHA GET # GET *-price GET *-info->inventory
  2. 1) "apple" -- 水果
  3. 2) "8.5" -- 价格
  4. 3) "1000" -- 库存量
  5. 4) "banana"
  6. 5) "4.5"
  7. 6) "300"
  8. 7) "cherry"
  9. 8) "7"
  10. 9) "50"
  11. 10) "dragon fruit"
  12. 11) "6"
  13. 12) "500"
  14. 13) "mango"
  15. 14) "5"
  16. 15) "250"
  17. 16) "watermelon"
  18. 17) "3.5"
  19. 18) "324"

使用外部键的值作为排序权重

在默认情况下,SORT 命令将使用被排序元素本身作为排序权重,但在有需要时,用户可以通过可选的 BY 选项,指定其他键的值作为排序的权重:

  1. SORT key [BY pattern]

pattern 参数的值既可以是包含 符号的字符串,也可以是包含 符号和 -> 符号的字符串,这两种值的作用和效果跟它们在 GET 选项时的作用和效果一样:前者用于获取字符串键的值,而后者则用于从散列里面获取指定字段的值。

举个例子,通过执行以下命令,我们可以使用储存在字符串键里面的水果价格作为权重,对水果进行排序:

  1. redis> SORT fruits BY *-price
  2. 1) "watermelon"
  3. 2) "banana"
  4. 3) "mango"
  5. 4) "dragon fruit"
  6. 5) "cherry"
  7. 6) "apple"

因为上面这个排序结果只展示了水果的名字,却没有展示水果的价格,所以这个排序结果并没有清楚地展示水果的名字和价格之间的关系。相反地,如果我们在使用 BY 选项的同时,使用两个 GET 选项去获取水果的名字以及价格,那么就能够直观地看出水果是按照价格进行排序的了:

  1. redis> SORT fruits BY *-price GET # GET *-price
  2. 1) "watermelon" -- 水果的名字
  3. 2) "3.5" -- 水果的价格
  4. 3) "banana"
  5. 4) "4.5"
  6. 5) "mango"
  7. 6) "5"
  8. 7) "dragon fruit"
  9. 8) "6"
  10. 9) "cherry"
  11. 10) "7"
  12. 11) "apple"
  13. 12) "8.5"

同样地,我们还可以通过执行以下命令,使用散列中记录的库存量作为权重,对水果进行排序并获取它们的库存量:

  1. redis> SORT fruits BY *-info->inventory GET # GET *-info->inventory
  2. 1) "cherry" -- 水果的名字
  3. 2) "50" -- 水果的库存量
  4. 3) "mango"
  5. 4) "250"
  6. 5) "banana"
  7. 6) "300"
  8. 7) "watermelon"
  9. 8) "324"
  10. 9) "dragon fruit"
  11. 10) "500"
  12. 11) "apple"
  13. 12) "1000"

保存排序结果

在默认情况下,SORT 命令会直接将排序结果返回给客户端,但如果用户有需要的话,也可以通过可选的 STORE 选项,以列表形式将排序结果储存到指定的键里面:

  1. SORT key [STORE destination]

如果用户给定的 destination 键已经存在,那么 SORT 命令会先移除该键,然后再储存排序结果。带有 STORE 选项的 SORT 命令在成功执行之后将返回被储存的元素数量作为结果。

作为例子,以下代码展示了如何将排序 fruits 集合所得的结果储存到 sorted-fruits 列表里面:

  1. redis> SORT fruits ALPHA STORE sorted-fruits
  2. (integer) 6 -- 有六个已排序元素被储存了
  3.  
  4. redis> LRANGE sorted-fruits 0 -1 -- 查看排序结果
  5. 1) "apple"
  6. 2) "banana"
  7. 3) "cherry"
  8. 4) "dragon fruit"
  9. 5) "mango"
  10. 6) "watermelon"

其他信息

属性
平均复杂度O(N*log(N)+M) ,其中 N 为被排序元素的数量,而 M 则为命令返回的元素数量。
版本要求SORT 命令从 Redis 1.0.0 版本开始可用。