Elasticsearch 超入門

# 前書き

APIに勉強がてらElasticsearchを導入してみたいなと考え、まずElasticsearch勉強しようと思うも

Elasticearchに関する超初心者用の日本語ドキュメントはかなり少なそうで..。

https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html

やはり上記の公式ドキュメントは英語ですが分かりやすいので、ノート代わりにシンプルに内容をまとめます。

認識の違いがあったらごめんなさい。

# セットアップ

## ■ Javaのバージョン確認

* 最低でもJava7以上である必要があります。

```
java -version
echo $JAVA_HOME
```

## ■ Download

* Unix環境を前提に

適当にディレクトリ移動して、下記コマンドでダウンロード

```
curl -L -O https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/2.4.1/elasticsearch-2.4.1.tar.gz
```

下記コマンドで解凍

```
tar -xvf elasticsearch-2.4.1.tar.gz
```

## ■ 起動

解凍したら`elasticsearch-2.4.1`のようなディレクトリがあるはずなので、配下の`./bin`に移動

```
cd elasticsearch-2.4.1/bin
```

下記のコマンドで起動します。

```
./elasticsearch
```

コマンド実行するとこんな感じで最終的に```started```　が表示されます。

```
[2016-09-30 11:05:37,078][INFO ][node                     ] [Paibo] started
```
# 基本操作

## ■ Index生成

```
curl -XPUT 'localhost:9200/customer'
```

* ポート番号はデフォルトで9200
* customerというIndexを作成（MySQLでいうところのdatebaseでしょうか）
* `?pretty`をつけるとレスポンスのJSONが整形されて出力されます

`?pretty`なし

```
curl -XPUT 'localhost:9200/customer'
{"acknowledged":true}
```

`?pretty`あり

```
curl -XPUT 'localhost:9200/customer?pretty'
{
  "acknowledged" : true
}
```

### Index確認

customerが追加されてます。

```
curl 'localhost:9200/_cat/indices?v'
health status index    pri rep docs.count docs.deleted store.size pri.store.size 
yellow open   customer   5   1          0            0       260b           260b 
```

　

## ■ Type生成

```
curl -XPUT 'localhost:9200/customer/external/1?pretty' -d '
{
  "name": "John Doe"
}'
```

* externalというtypeを作成（MySQLでいうところのtableですね）
* `/1` でidを指定してレコード追加しています。

Response

```
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 1,
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "created" : true
}
```

### type確認

id=1のtypeを確認します。

```
curl -XGET 'localhost:9200/customer/external/1?pretty'
```

Response

```
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 1,
  "found" : true,
  "_source" : {
    "name" : "John Doe"
  }
}
```

### 追加方法について

id=1で、`{"name": "John Doe"}`を作成した後

```
 curl -XPUT 'localhost:9200/customer/external/1?pretty' -d '
{
  "name": "John Doe"
}'
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 1,
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "created" : true
}
```

id=1で、`{"name": "Jane Doe"}`を生成しようとすると失敗します。

```
curl -XPUT 'localhost:9200/customer/external/1?pretty' -d '
{
  "name": "Jane Doe"
}'
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 2,
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "created" : false
}
```

idを指定しないと、適当なidが付与されます。

```
curl -XPOST 'localhost:9200/customer/external?pretty' -d '
> {
>   "name": "Jane Doe"
> }'
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "AVd5vZRmkCj9N-SGg2rZ",
  "_version" : 1,
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "created" : true
}
```

　

## ■ Index削除

```
curl -XDELETE 'localhost:9200/customer?pretty'
{
  "acknowledged" : true
}
```

削除されていることを確認

```
curl 'localhost:9200/_cat/indices?v'
health status index pri rep docs.count docs.deleted store.size pri.store.size 
```

`customer`が消えています。


## ■ DocumentのUPDATE

Documentはレコードという認識でよさそうです。

このようにカラム`"age"`を追加したような、UPDATEも可能です。

```
curl -XPOST 'localhost:9200/customer/external/1/_update?pretty' -d '
{
  "doc": { "name": "Jane Doe", "age": 20 }
}'
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 3,
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  }
}
```

### 確認


```

curl -XGET 'localhost:9200/customer/external/1?pretty'
{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 3,
  "found" : true,
  "_source" : {
    "name" : "Jane Doe",
    "age" : 20
  }
}
```

　

## ■ DocumentのDELETE


id=1のDocumentを削除する例

```
curl -XDELETE 'localhost:9200/customer/external/1?pretty'
{
  "found" : true,
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 4,
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  }
}
```

　

# チュートリアル

　

## 準備

　

### ■ サンプルデータをダウンロード

https://github.com/bly2k/files/blob/master/accounts.zip?raw=true

上記URLより、サンプルデータをダウンロードした後「accounts.json」というファイルを展開。

#### accounts.json

下記のようなデータで構成されてます

```json
{
    "account_number": 0,
    "balance": 16623,
    "firstname": "Bradshaw",
    "lastname": "Mckenzie",
    "age": 29,
    "gender": "F",
    "address": "244 Columbus Place",
    "employer": "Euron",
    "email": "bradshawmckenzie@euron.com",
    "city": "Hobucken",
    "state": "CO"
}
```

### ■ Elasticsearchへ取り込み

「accounts.json」ファイルのあるディレクトリへ移動し、下記コマンドで取り込み

```
curl -XPOST 'localhost:9200/bank/account/_bulk?pretty' --data-binary "@accounts.json"
```

#### 確認

```
curl 'localhost:9200/_cat/indices?v'
health status index pri rep docs.count docs.deleted store.size pri.store.size 
yellow open   bank    5   1       1000            0    447.2kb        447.2kb 
```

* `bank`というIndexが作成されている。
* `docs.count`よりDocumentの数が1000であることが確認できる。

　

## SearchAPI

### ■ クエリの種類

SearchAPIのクエリの発行方法は二種類

全件検索を例とすると、下記のようになる。

#### クエリ（1）

```
curl 'localhost:9200/bank/_search?q=*&pretty'
```

#### クエリ（2）

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": { "match_all": {} }
}'
```

### ■ JSON使用例

#### size

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": { "match_all": {} },
  "size": 1
}'
```

* `"size": `で検索結果の件数を指定可能。例では1件。
* デフォルトは10

#### from

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": { "match_all": {} },
  "from": 10,
  "size": 10
}'
```

* `"from": `で検索開始位置の指定が可能。
* 開始番号が0なので、例では11番目から10件となる。
* デフォルトは 0。

#### sort

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": { "match_all": {} },
  "sort": { "account_number": { "order": "desc" } }
}'
```

* 例では`"account_number"`のカラムを降順に10件表示となる。

#### _source

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": { "match_all": {} },
  "_source": ["account_number", "balance"]
}'
```

* クエリの対象を指定する。
* 例では`"account_number"`と`"balance"`が表示される。

### ■ queryの使用例

#### "account_number"が20

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": { "match": { "account_number": 20 } }
}'
```

#### "address"に"mill"が含まれる

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": { "match": { "address": "mill" } }
}'
```

#### "address"に"mill"または"lane"が含まれる

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": { "match": { "address": "mill lane" } }
}'
```

#### "address"に"mill lane"が含まれる

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": { "match_phrase": { "address": "mill lane" } }
}'
```

match⇒match_phraseにすることで、"mill lane"を含むものと指定できます。


### ■ Bool Query

#### must

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": {
    "bool": {
      "must": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}'
```

* "address"に"mill"と"lane"のどちらも含まれるDocumentが選択される。

#### should

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": {
    "bool": {
      "should": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}'
```

* "address"に"mill"もしくは"lane"の含まれるDocumentが選択される。

#### must_not

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": {
    "bool": {
      "must_not": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}'
```

* "address"に"mill"と"lane"どちらも含まないDocumentが選択される。

#### mustとmust_notの組み合わせ

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": {
    "bool": {
      "must": [
        { "match": { "age": "40" } }
      ],
      "must_not": [
        { "match": { "gender": "M" } }
      ]
    }
  }
}'
```

* "age"が"40"であるが"gender"が"M"ではないDocumentが選択される。

#### filter

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": {
            "gte": 20000,
            "lte": 30000
          }
        }
      }
    }
  }
}'
```

* 文字通り検索結果にfilterが可能
* 例の場合全件検索結果に、"balance"が20000以上、300000以下というfilterをかけてDocumentが選択される。

| range      |                    意味 |
|:-----------|------------------------:|
| gte        |Greater-than or equal to |
| gt         |            Greater-than |
| lte        |   Less-than or equal to |
| lt         |               Less-than |

### ■Aggregations(グループ的な意味)

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state"
      }
    }
  }
}'
```

* ```"size": 0```としているのは検索結果の表示は必要ではないため。
* ```"terms":```はGROUP BYを意味します。
* ```"field":"state"```でGROUP BYとしてます
* GROUP BYされた```"state"```とその件数を、件数の降順に表示します
* SQLで表すと下記のようになる。

```SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC```

|range|意味|
|:----|:----|
|min|最小値|
|max|最大値|
|sum|合計|
|avg|平均値|
|terms|GROUP BY|
|range|範囲指定でGROUP BY|

```
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
  "size": 0,
  "aggs": {
    "group_by_age": {
      "range": {
        "field": "age",
        "ranges": [
          {
            "from": 20,
            "to": 30
          },
          {
            "from": 30,
            "to": 40
          },
          {
            "from": 40,
            "to": 50
          }
        ]
      },
      "aggs": {
        "group_by_gender": {
          "terms": {
            "field": "gender"
          },
          "aggs": {
            "average_balance": {
              "avg": {
                "field": "balance"
              }
            }
          }
        }
      }
    }
  }
}'
```

* ``` "range":```で```"field": "age"```が[20-30,30-40,40-50]という条件でGROUP BY。
* ```"terms":```で```"field": "gender"```をさらにGROUP BY。
* ``` "avg": ```でGROUP BYの結果から```"field": "balance"```のavg(平均値)を出力