RedisのZUNIONSTORE、ZINTERSTORE を理解する

[公式ドキュメントのZUNIONSTORE](http://redis.io/commands/zunionstore)と[ZINTERSTORE](http://redis.io/commands/zinterstore)に書いてあるけれど、図解しないとわかりにくいと思います。

# 前提

## 私のSortedSetに対する理解の仕方について
私は、[SortedSetをこのように理解している](http://qiita.com/ndxbn/items/e4a25c14c275cb5356a1)ので、そのつもりでお願いします。

## このページで使用する、サンプルデータ
説明用に、事前に以下のように2つのSortedSetレコードを準備したことにします。

```bash
ZADD zset1 1 "one"
ZADD zset1 2 "two"
ZADD zset1 3 "three"

ZADD zset2 11 "one"
ZADD zset2 33 "three"
ZADD zset2 44 "four"
```

表にすると、各レコード（変数）は以下のようになってます。  
縦軸に`key`、横軸に`member`、値が`Score`です。

↓member＼key→ | zset1 | zset2
:-- | --: | --:
one | 1 | 11
two | 2 | (nil)
three | 3 | 33
four | (nil) | 44


# 基本
[Union（和集合）とかIntersect（共通集合）](http://www.geisya.or.jp/~mwm48961/koukou/s1set03.htm)をして、Store（保存）します。デフォだと、各レコードにある`Score`を足し算します。  
ミニマルな文法は、以下の通りです。

> ZUNIONSTORE destination numkeys key [key ...]
> ZINTERSTORE destination numkeys key [key ...]

* `distination`は、出力先の`key`名
* `num`は、Union・Intersectしたい`SortedSet`の数
* `key`は、Union・Intersectしたい`SortedSet`のKey名

### ZUNIONSTORE（和集合）の例

```bash
ZUNIONSTORE union1 2 zset1 zset2
```

これをやると、以下の表のようになります。

↓member＼key→ | zset1 | zset2 | union1
:-- | --: | --: | :-- |
one | 1 | 11 | → 12
two | 2 | (nil) | → 2
three | 3 | 33 | → 39
four | (nil) | 44 | → 44

`SortedSet`は**常に`Score`でソートされる**ので、以下のようになります。

```bash
ZRANGE union1 0 -1 WITHSCORES
1) "two"
2) "2"
3) "one"
4) "12"
5) "three"
6) "36"
7) "four"
8) "44"
```

### ZINTERSTORE（共通集合）の例

```bash
ZINTERSTORE inter1 2 zset1 zset2
```

これをやると、以下の表のようになります。

↓member＼key→ | zset1 | zset2 | inter1
:-- | --: | --: | :-- |
one | 1 | 11 | → 12
two | 2 | (nil) | → (nil)
three | 3 | 33 | → 39
four | (nil) | 4 | → (nil)

どうなってるか、確認してみます。

```bash
ZRANGE inter1 0 -1 WITHSCORES
1) "one"
2) "12"
3) "three"
4) "36"
```

# 各種オプション
UNIONとINTERSECTが何かはわかったと思うので、オプションはUNIONで説明します。（公式ドキュメントも、INTERSECTの方には「UNIONのほう見て」って書いてあるし。）

## WEIGHTSオプション
各`key`に対する`WEIGHTS`（重み）オプションです。（この説明わかるのか…？）  
`ZUNIONSTORE`と`ZINTERSTORE`に対する`WEIGHTS`の文法は、以下の通りです。

> ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]]
> ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]]

…まぁ、同じですな。

* `weight`は、`key`と同じ数になります。

#### WEIGHTSオプションを使うとどうなるか
以下のようにやってみましょう。

```bash
ZUNIONSTORE union2 2 zset1 zset2 WEIGHTS 2 7
```

これをやると、以下の表のようになります。

↓member＼key→ | zset1（×2） | zset2（×7） | union2
:-- | --: | --: | :-- |
one | 1×2 | 11×7 | → 79
two | 2×2 | (nil) | → 4
three | 3×2 | 33×7 | → 237
four | (nil) | 44×7 | → 308

SortedSetは、`Score`で常にソートされるので、以下のようになります。

```bash
ZRANGE union2 0 -1 WITHSCORES
1) "two"
2) "4"
3) "one"
4) "79"
5) "three"
6) "237"
7) "four"
8) "308"
```

## AGGREGATEオプション
「基本」の説明で、以下のように書きました。

> デフォだと、各レコードにあるScoreを足し算します。

この挙動を変更するオプションです。  
このオプションの文法は、以下の通りです。

> ZINTERSTORE destination numkeys key [key ...] [AGGREGATE SUM|MIN|MAX]
> ZUNIONSTORE destination numkeys key [key ...] [AGGREGATE SUM|MIN|MAX]

例によって、同じです。

#### AGGREGATEオプションを使うとどうなるか
以下のようにやってみましょう。

```bash
ZUNIONSTORE union3 2 zset1 zset2 AGGREGATE MIN
```

`AGGREGATE MIN` としているので、小さいほうが選ばれます。

↓member＼key→ | zset1（×2） | zset2（×7） | union2
:-- | --: | --: | :-- |
one | **1** | 11 | → 1
two | **2** | (nil) | → 2
three | **3** | 33 | → 3
four | (nil) | **44** | → 44

```bash
ZRANGE union3 0 -1 WITHSCORES
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
7) "four"
8) "44"
```

# 全部合わせてみる
最後に、4つの`SortedSet`に対して、適当に`WEIGHTS`と`AGGREGATE MAX`を使った場合の例を載せておきます。

```bash
ZADD zset1 1 "one"
ZADD zset1 2 "two"
ZADD zset1 3 "three"

ZADD zset2 11 "one"
ZADD zset2 33 "three"
ZADD zset2 44 "four"

ZADD zset3 12 "one"
ZADD zset3 inf "three"
ZADD zset3 0 "four"

ZADD zset4 inf "one"
ZADD zset4 33 "three"


ZINTERSTORE inter4 4 zset1 zset2 zset3 zset4 WEIGHTS 13 1 1 0 AGGREGATE MAX
```

↓member＼key→ | zset1（×13） | zset2（×1） | zset3（×1） | zset4（×0） | inter4
:-- | --: | --: | --: | --: | :--
one | **1×13** | 11×1 | 12×1 | inf×0 | →13
two | 2×13 | (nil) | (nil) | (nil) | →(nil)
three | 3×13 | 33×1 | **inf×1** | 33×0 | → inf
four | (nil) | 44×13 | 0×1 | (nil) | →(nil)

```bash
ZRANGE inter4 0 -1 WITHSCORES
1) "one"
2) "13"
3) "three"
4) "inf"
```

なるほど、`WEIGHTS`の演算結果で、`Score`の大きさを比較するんですね。


# おわり