redis sortコマンドの使い方まとめ

## redis sortコマンドの使い方まとめ

redis sortコマンドの使い方についてまとめました。
この記事で説明する内容は下記の環境で動作確認を行いました。

* Windows7 (64bit)
* redis 2.8.19

### sortコマンドの仕様

sortコマンドは、リスト、セット、ソート済みセットのメンバーや外部データを使って要素のソートを行います。

```text:syntax
SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]
```

参考

> [redis command - sort] (http://redis.io/commands/sort)


### 使い方の確認方法

次のサンプルデータを使用してsortコマンドの使い方を確認します。

#### サンプルデータ

**果物データ**

「果物データ」は果物の名前、重量、糖度などをハッシュで格納します。
ハッシュのキー名は`fruits:`に続けて果物の名前を付加します。

例えば、りんごのハッシュキーは下記のようになります。

```text:example
fruits:apple
```

果物データの内容

|id |weight |sugar|name       |desc                                             |
|:--|:------|:----|:----------|:------------------------------------------------|
|1  |300    |14   |apple      |バラ科リンゴ属の落葉高木樹。またはその果実のこと      |
|2  |180    |12   |orange     |ミカン科ミカン属の常緑小高木、またはその果実のこと    |
|3  |140    |10   |grape      |ブドウ科のつる性落葉低木である。またその果実のこと    |
|5  |200    |12   |peach      |バラ科モモ属の落葉小高木。またその果実のこと         |
|4  |300    |10   |mango      |ウルシ科マンゴー属の果樹、またその果実               |
|6  |190    |13   |melon      |果肉色は、主に赤肉種・青肉種・白肉種に分類される      |
|7  |15     |7    |lemon      |ミカン科ミカン属の常緑低木、またはその果実のこと      |
|8  |95     |21   |banana     |バショウ科バショウ属のうち、果実を食用とする品種群の総称|
|9  |3000   |12   |watermelon |果実を食用にするために栽培されるウリ科のつる性一年草、またその果実のこと|
|10 |44     |13   |strawberry |バラ科の多年草                                     |
|11 |300    |13   |pear       |果肉は白色で、甘く果汁が多い                        |

* id: 管理No
* weight : 平均重量(g)
* sugar : 平均糖度(%)
* name : 名称(ハッシュのキー名の一部にもなります)

**果物の人気ランキング**

「果物の人気ランキング」は人気ポイントをスコアとするソート済みセットで管理します。
ソート済みセットのキー名は`popular`とします。

人気ランキングの内容

|member     |point (score)|メモ       |
|:----------|:-----------:|:----------|
|strawberry | 75          |           |
|orange     | 68          |           |
|peach      | 68          |           |
|pear       | 65          |           |
|apple      | 64          |           |
|grape      | 61          |           |
|melon      | 59          |           |
|watermelon | 58          |           |
|banana     | 56          |           |
|mango      | 0           |ランキング外なので0ポイント |
|lemon      | 0           |ランキング外なので0ポイント |

**好きな果物データ**

好きな果物を持ったセットです。上記のハッシュやランキングには無い果物もあります。
セットのキー名は`myfavorite`とします。

|member    |メモ     |
|:---------|:--------|
|apple     |         |
|orange    |         |
|peach     |         |
|pear      |         |
|pineapple |パイナップルは上記の果物データにも人気ランキングにもありません |

#### サンプルデータの準備

**果物データの登録 (ハッシュ)**

```c:fruits:*
> hmset fruits:apple      id 1  weight 300   sugar 14 name "apple"      desc "バラ科リンゴ属の落葉高木樹。またはその果実のこと"
> hmset fruits:orange     id 2  weight 180   sugar 12 name "orange"     desc "ミカン科ミカン属の常緑小高木、またはその果実のこと"
> hmset fruits:grape      id 3  weight 140   sugar 10 name "grape"      desc "ブドウ科のつる性落葉低木である。またその果実のこと"
> hmset fruits:peach      id 4  weight 200   sugar 12 name "peach"      desc "バラ科モモ属の落葉小高木。またその果実のこと"
> hmset fruits:mango      id 5  weight 300   sugar 10 name "mango"      desc "ウルシ科マンゴー属の果樹、またその果実"
> hmset fruits:melon      id 6  weight 190   sugar 13 name "melon"      desc "果肉色は、主に赤肉種・青肉種・白肉種に分類される"
> hmset fruits:lemon      id 7  weight  15   sugar 7  name "lemon"      desc "ミカン科ミカン属の常緑低木、またはその果実のこと"
> hmset fruits:banana     id 8  weight  95   sugar 21 name "banana"     desc "バショウ科バショウ属のうち、果実を食用とする品種群の総称"
> hmset fruits:watermelon id 9  weight  3000 sugar 12 name "watermelon" desc "果実を食用にするために栽培されるウリ科のつる性一年草、またその果実のこと"
> hmset fruits:strawberry id 10 weight  44   sugar 13 name "strawberry" desc "バラ科の多年草"
> hmset fruits:pear       id 11 weight  300  sugar 13 name "pear"       desc "果肉は白色で、甘く果汁が多い"
```

**果物の人気ランキングの登録 (ソート済みセット)**

```c:popular
> zadd popular 75 strawberry 68 orange 68 peach 65 pear 64 apple 61 grape 59 melon 58 watermelon 56 banana 0 mango 0 lemon
(integer) 11
```

**好きな果物データの登録 (セット)**

```c:myfavorite
> sadd myfavorite apple orange peach pear pineapple
(integer) 5
```

### 使い方の確認

#### 外部データを使ったソート(その1)

「果物の人気ランキング」をハッシュ型の「果物データ」を使ってソートします。
この例では「果物データ」が持つweightフィールドの降順でソートします。

```c:sort
> sort popular by fruits:*->weight get # get fruits:*->id get fruits:*->weight desc
 1) "watermelon"
 2) "9"
 3) "3000"
 4) "pear"
 5) "11"
 6) "300"
 7) "mango"
 8) "5"
 9) "300"
10) "apple"
11) "1"
12) "300"
13) "peach"
14) "4"
15) "200"
16) "melon"
17) "6"
18) "190"
19) "orange"
20) "2"
21) "180"
22) "grape"
23) "3"
24) "140"
25) "banana"
26) "8"
27) "95"
28) "strawberry"
29) "10"
30) "44"
31) "lemon"
32) "7"
33) "15"
```

**確認点**

byオプションでソートに使用するフィールドを指定します。
この例では、`fruits:*`ハッシュのweightフィールドを使用しています。

```c
by fruits:*->weight
```

`*`は`popular`の各メンバーが当てはめられるワイルドカードの役目を果たします。
たとえば、メンバーが"apple"のときは次のように解釈することができます。

```c
by fruits:apple->weight
```

getオプションの`#`は、キーに指定した`popular`のメンバーを取得します。

```c
get #
```

`->`は、ハッシュのフィールドを指します。
この例では、`fruits:*`ハッシュのidフィールドを取得しています。

```c
get fruits:*->id
```

#### 外部データを使ったソート(その2)

nameフィールドでソートしようとするとエラーが発生します。
ソートに使用するフィールドの値が数値として表現できない場合は下記のエラーとなります。

```c:sort
> sort popular by fruits:*->name get # get fruits:*->id get fruits:*->weight asc
(error) ERR One or more scores can't be converted into double
```

**確認点**

文字列でソートする場合は、`alpha`修飾子を使用します。

```c:sort
> sort popular by fruits:*->name get # get fruits:*->id get fruits:*->weight asc alpha
 1) "apple"
 2) "1"
 3) "300"
 4) "banana"
 5) "8"
 6) "95"
 7) "grape"
 8) "3"
 9) "140"
10) "lemon"
11) "7"
12) "15"
13) "mango"
14) "5"
15) "300"
16) "melon"
17) "6"
18) "190"
19) "orange"
20) "2"
21) "180"
22) "peach"
23) "4"
24) "200"
25) "pear"
26) "11"
27) "300"
28) "strawberry"
29) "10"
30) "44"
31) "watermelon"
32) "9"
33) "3000"
```

#### 複数の外部データを利用する

各果物の在庫を管理するため文字列型のキーに在庫数を登録します。

```c:warehouse
> set warehouse:apple "30"
> set warehouse:orange "50"
> set warehouse:grape "15"
> set warehouse:peach "20"
> set warehouse:mango "15"
> set warehouse:melon "5"
> set warehouse:lemon "45"
> set warehouse:banana "60"
> set warehouse:watermelon "30"
> set warehouse:strawberry "15"
> set warehouse:pear "10"
```

在庫数を上記の文字列型より取得します。
この例のように、複数の外部データからデータを取得することができます。

```c:sort
> sort popular by fruits:*->id get # get fruits:*->id get warehouse:* asc
 1) "apple"
 2) "1"
 3) "30"
 4) "orange"
 5) "2"
 6) "50"
 7) "grape"
 8) "3"
 9) "15"
10) "peach"
11) "4"
12) "20"
13) "mango"
14) "5"
15) "15"
16) "melon"
17) "6"
18) "5"
19) "lemon"
20) "7"
21) "45"
22) "banana"
23) "8"
24) "60"
25) "watermelon"
26) "9"
27) "30"
28) "strawberry"
29) "10"
30) "15"
31) "pear"
32) "11"
33) "10"
```

**確認点**

文字列型の場合は`->`を使用する必要はありません。
`*`の部分にメンバーが当てはめられると完全なキー名となり、そのキーが持つ値を取得します。

```c
get warehouse:*
```

たとえば、メンバーが"apple"のときは次のように解釈することができます。

```c
get warehouse:apple
```

#### 取得するデータ位置、件数の指定

limit修飾子を使って取得するデータ位置と件数を指定することができます。
offsetにはデータの取得位置を指定します。(offsetは0から始まります)
countには取得したい件数を指定します。

```
LIMIT offset count
```

この例では先頭から5件取得します。

```c
> sort popular by fruits:*->weight get # get fruits:*->id get fruits:*->weight limit 0 5 desc
 1) "watermelon"
 2) "9"
 3) "3000"
 4) "pear"
 5) "11"
 6) "300"
 7) "mango"
 8) "5"
 9) "300"
10) "apple"
11) "1"
12) "300"
13) "peach"
14) "4"
15) "200"
```

#### 該当する外部データが無い場合

sortコマンドのキーに指定したデータ（この例では`myfavorite`のメンバー）と対応する外部データが無い場合は`nil`が返ります。
この例では、pineappleに対応するハッシュが存在しないので`nil`が返ります。

```c:sort
> sort myfavorite by fruits:*->id get # get fruits:*->id get fruits:*->weight asc
 1) "pineapple"
 2) (nil)
 3) (nil)
 4) "apple"
 5) "1"
 6) "300"
 7) "orange"
 8) "2"
 9) "180"
10) "peach"
11) "4"
12) "200"
13) "pear"
14) "11"
15) "300"
```

#### 外部データを使わないソート

キーのメンバーでソートするシンプルなソートですが、セット型やリスト型もソートすることが可能です。
この例では、`myfavorite`のメンバー(appleやorange)で昇順のソートを行っています。
メンバーはアルファベットなのでalpha修飾子が必要です。もしメンバーが数値であれば必要ありません。

```c
> sort myfavorite asc alpha
1) "apple"
2) "orange"
3) "peach"
4) "pear"
5) "pineapple"
```

ソート済みセットもメンバーでソートすることが可能ですが、スコアでソートさせたい場合はbyオプションに`nosort`を指定します。

```c
> sort popular by nosort desc
 1) "strawberry"
 2) "peach"
 3) "orange"
 4) "pear"
 5) "apple"
 6) "grape"
 7) "melon"
 8) "watermelon"
 9) "banana"
10) "mango"
11) "lemon"
```

#### ソート結果を保存する

`store`オプションでソート結果をリスト形式で保存することができます。

```c
127.0.0.1:6379> sort popular by fruits:*->id get # get fruits:*->id get fruits:*->weight get fruits:*->sugar limit 0 5 asc store dest
(integer) 20

> type dest
list

> lrange dest 0 -1
 1) "apple"
 2) "1"
 3) "300"
 4) "14"
 5) "orange"
 6) "2"
 7) "180"
 8) "12"
 9) "grape"
10) "3"
11) "140"
12) "10"
13) "peach"
14) "4"
15) "200"
16) "12"
17) "mango"
18) "5"
19) "300"
20) "10"
```