はじめての Elasticsearch

この記事は全文検索エンジン「[Elasticsearch](https://www.elastic.co/jp/products/elasticsearch)」の入門チュートリアルです。

## Elasticsearch とは
**Elasticsearch** は Elastic 社が開発しているオープンソースの全文検索エンジンです。
大量のドキュメントから目的の単語を含むドキュメントを高速に抽出することができます。

Elasticsearch では RESTful インターフェースを使って操作しますが、「[Elasticsearch SQL](https://www.elastic.co/jp/products/stack/elasticsearch-sql)」を使って SQL 文でクエリを記述することもできます。

Oracle や MySQL などのリレーショナルデータベースに慣れている人にとっては、最初はとっつきにくいと感じるかもしれません。

しかし、Elasticsearch の API はとてもシンプルなので、心配しなくても大丈夫です。

## Elastic Stack とは
**Elastic Stack** は Elasticsearch 関連製品の総称です。
バージョン 2.x 以前は「ELK」と呼ばれていましたが、バージョン 5.0 からは名称を改め「Elastic Stack」となりました。

| 製品名         | 機能            |
|:--------------|:-------------- |
| Elasticsearch | ドキュメントを保存・検索します。 |
| Kibana        | データを可視化します。 |
| Logstash      | データソースからデータを取り込み・変換します。 |
| Beats         | データソースからデータを取り込みます。 |
| X-Pack        | セキュリティ、モニタリング、ウォッチ、レポート、グラフの機能を拡張します。 |

※Logstash については、別記事「[はじめての Logstash](https://qiita.com/nskydiving/items/0cb598de7ffb5c22424d)」を参照してください。

## 動作環境
- Mac OS X 10.14.6
- Elasticsearch 8.1.0
- Kibana 8.1.0


## インストール
Mac のターミナルを開いて、必要なソフトウェアをインストールします。

Elasticsearch をダウンロードするのに wget コマンドを使用しますので、インストールしておきます。

```
$ brew install wget
```

Elasticsearch と日本語検索プラグイン「 [kuromoji](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html) 」をインストールします。

バージョン 8.0 以降では M1 Mac に対応していますが、ダウンロード URL が分かれているのでご注意ください。
・Intel Mac 用のダウンロード URL
https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.1.0-darwin-x86_64.tar.gz
・M1 Mac 用のダウンロード URL
https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.1.0-darwin-aarch64.tar.gz

```
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.1.0-darwin-x86_64.tar.gz
$ tar -xzf elasticsearch-8.1.0-darwin-x86_64.tar.gz
$ cd elasticsearch-8.1.0
$ bin/elasticsearch-plugin install analysis-kuromoji
```

バージョン 8.0 以降では、デフォルトでセキュリティが有効になっていますが、このチュートリアルでは一時的に無効にします。
config/elasticsearch.yml を編集して、「xpack.security.enabled」に false を設定してください。
```
# Enable security features
xpack.security.enabled: false
```

Elasticsearch を起動します。

```
$ bin/elasticsearch
```

ブラウザを開いて、http://localhost:9200/ にアクセスしてみましょう。
以下の JSON が表示されれば、起動は成功しています。

```json

  "name" : "username.local",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "qJnOX-ukSU-nX6hjUViLnA",
  "version" : {
    "number" : "8.1.0",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "3700f7679f7d95e36da0b43762189bab189bc53a",
    "build_date" : "2022-03-03T14:20:00.690422633Z",
    "build_snapshot" : false,
    "lucene_version" : "9.0.0",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}
```

もしエラー「ERR_CONNECTION_REFUSED」が表示される場合は、config/elasticsearch.yml を開いて、「xpack.security.enabled」に false が設定されているか確認してみてください。

続いて、**Kibana** をインストールします。

```
wget https://artifacts.elastic.co/downloads/kibana/kibana-8.1.0-darwin-x86_64.tar.gz
tar -xzf kibana-8.1.0-darwin-x86_64.tar.gz
cd kibana-8.1.0-darwin-x86_64/
```

Kibana を起動します。

```
$ bin/kibana
```

ブラウザを開いて、http://localhost:5601/ にアクセスしてみましょう。
以下のような画面が表示されれば、起動は成功しています。

![kibana_8_1_0_home.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127532/4ebb26cd-0b1d-b8d9-68d0-7c0c9426ca1e.png)

## チュートリアルをはじめる前に

この記事で使用しているコマンドは GitHub で公開していますので参考にしてください。
https://github.com/nskydiving/elasticsearch_examples

Elasticsearch は RESTful インターフェースで操作できますが、このチュートリアルでは Kibana の「Dev Tools」を使用します。

Kibana のメニューの中から「Dev Tools」を選択してください。

![kibana_8_1_0_dev_tools_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127532/3cb00876-70b7-9e07-8891-e0afcb9147d7.png)

以下のような画面が表示されます。

![kibana_8_1_0_dev_tools_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127532/c32707f8-d501-0d0e-183f-79a929762c06.png)


Console の左側エリアにコマンドを入力して実行ボタン（緑色の再生ボタン）をクリックすると、選択されたコマンドが実行され、右側エリアに実行結果が表示されます。

試しに以下のコマンドを実行してみましょう。

```json
GET _search
{
  "query": {
    "match_all": {}
  }
}
```

右側エリアに実行結果が表示されれば成功です。

※Mac 以外の環境の人は以下のリンクを参照してください。
https://www.elastic.co/jp/downloads/elasticsearch
https://www.elastic.co/jp/downloads/kibana

## CRUDオペレーション（RESTful API）

Elasticsearch ではリレーショナルデータベースとは異なる用語を使用しますが、概ね以下のように理解しておけば良いでしょう。

| Elasticsearch | リレーショナルデータベース |
|:--------------|:-------------- |
| Index    | データベース |
| Type     | テーブル |
| Document | レコード |

※バージョン 6.0 以降では、「Type」の指定が**非推奨**となり、タイプ名の代わりに「_doc」を指定するようになりました。

###　ドキュメントを作成

ドキュメントを作成するには、PUT で「/インデックス/タイプ/ドキュメントID」にアクセスし、ドキュメントの中身を JSON で渡します。

コマンド

```json
#    +--- Index name
#    |       +--- Type name
#    |       |     +--- Document ID
#    |       |     |
#    V       V     V
PUT /library/_doc/1
{
  "title": "Norwegian Wood",
  "name": {
    "first": "Haruki",
    "last": "Murakami"
  },
  "publish_date": "1987-09-04T00:00:00+0900",
  "price": 19.95
}
```

実行結果

```json
{
  "_index" : "library",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 15,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 22,
  "_primary_term" : 1
}
```

###　ドキュメントを取得

ドキュメントを取得するには、GET で「/インデックス/タイプ/ドキュメントID」にアクセスします。

コマンド

```json
GET /library/_doc/1
```

実行結果

```json
{
  "_index" : "library",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 15,
  "_seq_no" : 22,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "title" : "Norwegian Wood",
    "name" : {
      "first" : "Haruki",
      "last" : "Murakami"
    },
    "publish_date" : "1987-09-04T00:00:00+0900",
    "price" : 19.95
  }
}
```

###　ドキュメントIDを指定せずにドキュメントを作成

ドキュメントIDを指定せずにドキュメントを作成すると、自動的にドキュメントIDが割り当てられます。
自動的に割り当てられたドキュメントIDは実行結果で確認できます。

コマンド

```json
POST /library/_doc/
{
  "title": "Kafka on the Shore",
  "name": {
    "first": "Haruki",
    "last": "Murakami"
  },
  "publish_date": "2002-09-12T00:00:00+0900",
  "price": 19.95
}
```

実行結果

```json
{
  "_index" : "library",
  "_type" : "_doc",
  "_id" : "q2aZVmoBFWFSqRl8nY0k",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 23,
  "_primary_term" : 1
}
```

###　「ドキュメントIDを指定せずにドキュメントを作成」の確認

コマンド

```json
# POST /library/_doc/ で取得した id を指定してください
GET /library/_doc/q2aZVmoBFWFSqRl8nY0k
```

実行結果

```json
{
  "_index" : "library",
  "_type" : "_doc",
  "_id" : "q2aZVmoBFWFSqRl8nY0k",
  "_version" : 1,
  "_seq_no" : 23,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "title" : "Kafka on the Shore",
    "name" : {
      "first" : "Haruki",
      "last" : "Murakami"
    },
    "publish_date" : "2002-09-12T00:00:00+0900",
    "price" : 19.95
  }
}
```

###　ドキュメントを上書き更新

ドキュメントを上書き更新するには、PUT で「/インデックス/タイプ/ドキュメントID」にアクセスします。

コマンド

```json
PUT /library/_doc/1
{
  "title": "Norwegian Wood",
  "name": {
    "first": "Haruki",
    "last": "Murakami"
  },
  "publish_date": "1987-09-04T00:00:00+0900",
  "price": 29.95
}
```

実行結果

```json
{
  "_index" : "library",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 18,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 26,
  "_primary_term" : 1
}
```

###　「ドキュメントを上書き更新」の確認

コマンド

```json
GET /library/_doc/1
```

実行結果

```json
{
  "_index" : "library",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 18,
  "_seq_no" : 26,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "title" : "Norwegian Wood",
    "name" : {
      "first" : "Haruki",
      "last" : "Murakami"
    },
    "publish_date" : "1987-09-04T00:00:00+0900",
    "price" : 29.95
  }
}
```

###　ドキュメントを部分的に更新

ドキュメントを部分的に更新するには、POST で「/インデックス/_update/ドキュメントID」にアクセスし、 JSON で "doc" クエリを指定します。

コマンド

```json
POST /library/_update/1
{
  "doc": {
    "price": 10
  }
}
```

実行結果

```json
{
  "_index" : "library",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 19,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 27,
  "_primary_term" : 1
}
```

###　「ドキュメントを部分的に更新」の確認

コマンド

```json
GET /library/_doc/1
```

実行結果

```json
{
  "_index" : "library",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 19,
  "_seq_no" : 27,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "title" : "Norwegian Wood",
    "name" : {
      "first" : "Haruki",
      "last" : "Murakami"
    },
    "publish_date" : "1987-09-04T00:00:00+0900",
    "price" : 10
  }
}
```

###　ドキュメントに項目を追加

コマンド

ドキュメントに項目を追加するには、POST で「/インデックス/_update/ドキュメントID」にアクセスし、JSON で "doc" クエリを指定します。

```json
POST /library/_update/1
{
  "doc": {
    "price_jpy": 1800
  }
}
```

実行結果

```json
{
  "_index" : "library",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 20,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 28,
  "_primary_term" : 1
}
```

###　「ドキュメントに項目を追加」の確認

コマンド

```json
GET /library/_doc/1
```

実行結果

```json
{
  "_index" : "library",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 20,
  "_seq_no" : 28,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "title" : "Norwegian Wood",
    "name" : {
      "first" : "Haruki",
      "last" : "Murakami"
    },
    "publish_date" : "1987-09-04T00:00:00+0900",
    "price" : 10,
    "price_jpy" : 1800
  }
}
```

###　ドキュメントを削除

ドキュメントを削除するには、DELETE で「/インデックス/タイプ/ドキュメントID」にアクセスします。

コマンド

```json
DELETE /library/_doc/1
```

実行結果

```json
{
  "_index" : "library",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 21,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 29,
  "_primary_term" : 1
}
```

###　「ドキュメントを削除」の確認

コマンド

```json
GET /library/_doc/1
```

実行結果

```json
{
  "_index" : "library",
  "_type" : "_doc",
  "_id" : "1",
  "found" : false
}
```

###　インデックスを削除

インデックスを削除するには、DELETE で「/インデックス」にアクセスします。

コマンド

```json
DELETE /library
```

実行結果

```json
{
  "acknowledged" : true
}
```

###　「インデックスを削除」の確認

コマンド

```json
GET /libray/_doc/2
```

実行結果

```json
{
  "error" : {
    "root_cause" : [
      {
        "type" : "index_not_found_exception",
        "reason" : "no such index [libray]",
        "resource.type" : "index_expression",
        "resource.id" : "libray",
        "index_uuid" : "_na_",
        "index" : "libray"
      }
    ],
    "type" : "index_not_found_exception",
    "reason" : "no such index [libray]",
    "resource.type" : "index_expression",
    "resource.id" : "libray",
    "index_uuid" : "_na_",
    "index" : "libray"
  },
  "status" : 404
}
```

## ドキュメントの検索

インデックスに保存されたドキュメントを検索します。

下準備として、インデックスを一度削除して、テストデータのドキュメントを作成します。

複数のドキュメントを一気に作成するには、POST で「/インデックス/タイプ/_bulk」にアクセスします。

コマンド

```json
DELETE /library

POST /library/_bulk
{"index": {"_id": 1}}
{"title": "The quick brown fox", "price": 5}
{"index": {"_id": 2}}
{"title": "The quick brown fox jumps over the lazy dog", "price": 15}
{"index": {"_id": 3}}
{"title": "The quick brown fox jumps over the quick dog", "price": 8}
{"index": {"_id": 4}}
{"title": "Brown fox and brown dog", "price": 2}
{"index": {"_id": 5}}
{"title": "Lazy dog", "price": 9}
```

###　すべてのドキュメントを検索

すべてのドキュメントを検索するには、GET で「/インデックス/タイプ/_search」にアクセスします。

また、「/library/_search?size=3」のように size パラメータで検索件数を指定することもできます。

コマンド

```json
GET /library/_search
```

実行結果

```json
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 5,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "title" : "The quick brown fox",
          "price" : 5
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "title" : "The quick brown fox jumps over the lazy dog",
          "price" : 15
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "title" : "The quick brown fox jumps over the quick dog",
          "price" : 8
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "title" : "Brown fox and brown dog",
          "price" : 2
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 1.0,
        "_source" : {
          "title" : "Lazy dog",
          "price" : 9
        }
      }
    ]
  }
}
```

###　指定した単語を含むドキュメントを検索

指定した単語を含むドキュメントを検索するには、GET で「/インデックス/_search」にアクセスし、JSON で "match" クエリを指定します。

ここでは "title" に "fox" を含むドキュメントを検索しています。

コマンド

```json
GET /library/_search
{
  "query": {
    "match": {
      "title": "fox"
    }
  }
}
```

実行結果

```json
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 0.32951736,
    "hits" : [
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.32951736,
        "_source" : {
          "title" : "The quick brown fox",
          "price" : 5
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 0.30488566,
        "_source" : {
          "title" : "Brown fox and brown dog",
          "price" : 2
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.23470737,
        "_source" : {
          "title" : "The quick brown fox jumps over the lazy dog",
          "price" : 15
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.23470737,
        "_source" : {
          "title" : "The quick brown fox jumps over the quick dog",
          "price" : 8
        }
      }
    ]
  }
}
```

###　OR条件でドキュメントを検索

OR条件でドキュメントを検索するには、GET で「/インデックス/_search」にアクセスし、JSON で "match" クエリを指定します。

ここでは "title" に "quick" または "dog" を含むドキュメントを検索しています。

コマンド

```json
GET /library/_search
{
  "query": {
    "match": {
      "title": "quick dog"
    }
  }
}
```

検索結果

```json
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 5,
      "relation" : "eq"
    },
    "max_score" : 0.8762741,
    "hits" : [
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.8762741,
        "_source" : {
          "title" : "The quick brown fox jumps over the quick dog",
          "price" : 8
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.6744513,
        "_source" : {
          "title" : "The quick brown fox jumps over the lazy dog",
          "price" : 15
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6173784,
        "_source" : {
          "title" : "The quick brown fox",
          "price" : 5
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 0.3930218,
        "_source" : {
          "title" : "Lazy dog",
          "price" : 9
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 0.30488566,
        "_source" : {
          "title" : "Brown fox and brown dog",
          "price" : 2
        }
      }
    ]
  }
}
```

###　空白を含む単語を含むドキュメントを検索

空白を含む単語を含むドキュメントを検索するには、GET で「/インデックス/\_serach」にアクセスし、JSON で 「match_phrase」 クエリを指定します。

ここでは "title" に "quick dog" を含むドキュメントを検索しています。

コマンド

```json
GET /library/_search
{
  "query": {
    "match_phrase": {
      "title": "quick dog"
    }
  }
}
```

実行結果

```json
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.67445135,
    "hits" : [
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.67445135,
        "_source" : {
          "title" : "The quick brown fox jumps over the quick dog",
          "price" : 8
        }
      }
    ]
  }
}
```

###　ドキュメント検索のスコアを表示

ドキュメントを検索するときに、指定された単語との関連性がスコアとして計算されます。

コマンドに explain パラメータを指定することで、どのようにスコアが計算されたか確認することができます。

コマンド

```json
GET /library/_search?explain
{
  "query": {
    "match": {
      "title": "quick"
    }
  }
}
```

実行結果

```json
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 0.64156675,
    "hits" : [
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.64156675,
        "_source" : {
          "title" : "The quick brown fox jumps over the quick dog",
          "price" : 8
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6173784,
        "_source" : {
          "title" : "The quick brown fox",
          "price" : 5
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.43974394,
        "_source" : {
          "title" : "The quick brown fox jumps over the lazy dog",
          "price" : 15
        }
      }
    ]
  }
}
```

###　AND条件でドキュメントを検索

AND条件でドキュメントを検索するには、GET で「/インデックス/_search」にアクセスし、JSON で "bool" クエリを指定します。

ここでは、"title" に "quick" と "lazy dog" の両方を含むドキュメントを検索しています。

コマンド

```json
GET /library/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "quick"
          }
        },
        {
          "match_phrase": {
            "title": "lazy dog"
          }
        }
      ]
    }
  }
}
```

実行結果

```json
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.3887084,
    "hits" : [
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.3887084,
        "_source" : {
          "title" : "The quick brown fox jumps over the lazy dog",
          "price" : 15
        }
      }
    ]
  }
}
```

###　ドキュメント検索のスコアに重み付け

ドキュメント検索のスコアに重み付けするには、GET で「/インデックス/_search」にアクセスし、JSON で "boost" クエリを指定します。

ここでは、"title" に "quick dog" を含むドキュメントのスコアを半分にしています。

コマンド

```json
GET /library/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match_phrase": {
            "title": {
              "query": "quick dog",
              "boost": 0.5
            }
          }
        },
        {
          "match_phrase": {
            "title": {
              "query": "lazy dog"
            }
          }
        }
      ]
    }
  }
}
```

実行結果

```json
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 1.5890584,
    "hits" : [
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 1.5890584,
        "_source" : {
          "title" : "Lazy dog",
          "price" : 9
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.9489645,
        "_source" : {
          "title" : "The quick brown fox jumps over the lazy dog",
          "price" : 15
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.33722568,
        "_source" : {
          "title" : "The quick brown fox jumps over the quick dog",
          "price" : 8
        }
      }
    ]
  }
}
```

###　ドキュメント検索結果をハイライト表示

ドキュメント検索結果をハイライト表示するには、GET で「/インデックス/_search」にアクセスし、JSON で "highlight" クエリを指定します。

検索でヒットした文字列が em タグで囲われて出力されます。

コマンド

```json
GET /library/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match_phrase": {
            "title": {
              "query": "quick dog",
              "boost": 0.5
            }
          }
        },
        {
          "match_phrase": {
            "title": {
              "query": "lazy dog"
            }
          }
        }
      ]
    }
  },
  "highlight": {
    "fields": {
      "title": {}
    }
  }
}
```

実行結果

```json
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 1.5890584,
    "hits" : [
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 1.5890584,
        "_source" : {
          "title" : "Lazy dog",
          "price" : 9
        },
        "highlight" : {
          "title" : [
            "<em>Lazy</em> <em>dog</em>"
          ]
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.9489645,
        "_source" : {
          "title" : "The quick brown fox jumps over the lazy dog",
          "price" : 15
        },
        "highlight" : {
          "title" : [
            "The quick brown fox jumps over the <em>lazy</em> <em>dog</em>"
          ]
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.33722568,
        "_source" : {
          "title" : "The quick brown fox jumps over the quick dog",
          "price" : 8
        },
        "highlight" : {
          "title" : [
            "The quick brown fox jumps over the <em>quick</em> <em>dog</em>"
          ]
        }
      }
    ]
  }
}
```

###　フィルタリングしてドキュメントを検索

フィルタリングしてドキュメントを検索するには、GET で「/インデックス/_search」にアクセスし、JSON で "filter" クエリを指定します。

ここでは、"price" が ５ 〜 10 のドキュメントを検索しています。

コマンド

```json
GET /library/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "price": {
            "gte": 5,
            "lte": 10
          }
        }
      }
    }
  }
}
```

実行結果

```json
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 0.0,
    "hits" : [
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.0,
        "_source" : {
          "title" : "The quick brown fox",
          "price" : 5
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.0,
        "_source" : {
          "title" : "The quick brown fox jumps over the quick dog",
          "price" : 8
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 0.0,
        "_source" : {
          "title" : "Lazy dog",
          "price" : 9
        }
      }
    ]
  }
}
```

###　他のクエリとフィルタリングを組み合わせてドキュメントを検索

他のクエリとフィルタリングを組み合わせてドキュメントを検索するには、GET で「/インデックス/_search」にアクセスし、JSON で 他のクエリと "filter" クエリを合わせて指定します。

ここでは、"title" に "lazy dog" を含み、"price" が ５ 以上のドキュメントを検索しています。

コマンド

```json
GET /library/_search
{
  "query": {
    "bool": {
      "must": [{
        "match_phrase": {
          "title": "lazy dog"
        }
        }],
      "filter": {
        "range": {
          "price": {
            "gte": 5
          }
        }
      }
    }
  }
}
```

実行結果

```json
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.5890584,
    "hits" : [
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 1.5890584,
        "_source" : {
          "title" : "Lazy dog",
          "price" : 9
        }
      },
      {
        "_index" : "library",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.9489645,
        "_source" : {
          "title" : "The quick brown fox jumps over the lazy dog",
          "price" : 15
        }
      }
    ]
  }
}
```

## Mapping

Elasticsearch はスキーマレスのデータベースですが、あらかじめマッピングを設定しておくこともできます。

下準備として、インデックスを一度削除して、テストデータのドキュメントを作成します。

コマンド

```json
DELETE /library

POST /library/_bulk
{"index": {"_id": 1}}
{"title": "The quick brown fox", "price": 5}
{"index": {"_id": 2}}
{"title": "The quick brown fox jumps over the lazy dog", "price": 15}
{"index": {"_id": 3}}
{"title": "The quick brown fox jumps over the quick dog", "price": 8}
{"index": {"_id": 4}}
{"title": "Brown fox and brown dog", "price": 2}
{"index": {"_id": 5}}
{"title": "Lazy dog", "price": 9}
```

###　マッピングを取得

コマンド

```json
GET /library/_mapping
```

実行結果

```json
{
  "library" : {
    "mappings" : {
      "properties" : {
        "price" : {
          "type" : "long"
        },
        "title" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
  }
}
```

###　マッピングを追加

新しいマッピング "my_new_field" を追加します。

コマンド

```json
PUT /library/_mapping
{
  "properties": {
    "my_new_field": {
      "type": "text"
    }
  }
}
```

実行結果

```json
{
  "acknowledged" : true
}
```

###　「マッピングを追加」の確認

コマンド

```json
GET /library/_mapping
```

実行結果

```json
{
  "library" : {
    "mappings" : {
      "properties" : {
        "my_new_field" : {
          "type" : "text"
        },
        "price" : {
          "type" : "long"
        },
        "title" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
  }
}
```

###　アナライザを設定してマッピングを追加

マッピングに設定するアナライザは "analyzer" クエリを指定します。

コマンド

```json
PUT /library/_mapping
{
  "properties": {
    "english_field": {
      "type": "text",
      "analyzer": "english"
    }
  }
}
```

実行結果

```json
{
  "acknowledged" : true
}
```

###　「アナライザを設定してマッピングを追加」の確認

コマンド

```json
GET /library/_mapping
```

実行結果

```json
{
  "library" : {
    "mappings" : {
      "properties" : {
        "english_field" : {
          "type" : "text",
          "analyzer" : "english"
        },
        "my_new_field" : {
          "type" : "text"
        },
        "price" : {
          "type" : "long"
        },
        "title" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
  }
}
```

###　マッピングは変更することはできない

一度追加したマッピングは変更することはできないため、以下のコマンドはエラーとなります。

コマンド

```json
PUT /library/_mapping
{
  "properties": {
    "english_field": {
      "type": "double"
    }
  }
}
```

実行結果

```json
{
  "error": {
    "root_cause": [
      {
        "type": "illegal_argument_exception",
        "reason": "mapper [english_field] of different type, current_type [text], merged_type [double]"
      }
    ],
    "type": "illegal_argument_exception",
    "reason": "mapper [english_field] of different type, current_type [text], merged_type [double]"
  },
  "status": 400
}
```

###　型の違いによる検索結果への影響

「/log」へ id が「234571」と「1392.223」の２つのドキュメントを追加し、検索条件に「id が 1392 以上」を指定して検索を実行します。

「234571」と「1392.223」の両方が検索にヒットすることが期待されますが、実際には「234571」しかヒットしません。

コマンド

```json
POST /log/_doc
{
  "id": 234571
}

POST /log/_doc
{
  "id": 1392.223
}

GET /log/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "id": {
            "gt": 1392
          }
        }
      }
    }
  }
}
```

実行結果

```json
# POST /log/_doc
{
  "_index" : "log",
  "_type" : "_doc",
  "_id" : "r2axVmoBFWFSqRl86Y1N",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

# POST /log/_doc
{
  "_index" : "log",
  "_type" : "_doc",
  "_id" : "sGayVmoBFWFSqRl8S414",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}

# GET /log/_search
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.0,
    "hits" : [
      {
        "_index" : "log",
        "_type" : "_doc",
        "_id" : "r2axVmoBFWFSqRl86Y1N",
        "_score" : 0.0,
        "_source" : {
          "id" : 234571
        }
      }
    ]
  }
}
```

###　「型の違いによる検索結果への影響」の確認

マッピングを取得すると "id" の型は long 型です。

また、すべての "log" インデックスを検索すると、"id" の型が合わない「1392.223」のドキュメントも追加されていることが分かります。

コマンド

```json
GET /log/_mapping

GET /log/_search
```

実行結果

```json
# GET /log/_mapping
{
  "log" : {
    "mappings" : {
      "properties" : {
        "id" : {
          "type" : "long"
        }
      }
    }
  }
}

# GET /log/_search
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "log",
        "_type" : "_doc",
        "_id" : "r2axVmoBFWFSqRl86Y1N",
        "_score" : 1.0,
        "_source" : {
          "id" : 234571
        }
      },
      {
        "_index" : "log",
        "_type" : "_doc",
        "_id" : "sGayVmoBFWFSqRl8S414",
        "_score" : 1.0,
        "_source" : {
          "id" : 1392.223
        }
      }
    ]
  }
}
```

## Analysis

Elasticsearch がどのように文字列を分析しているのか確認することができます。

下準備として、インデックスを一度削除して、テストデータのドキュメントを作成します。

コマンド

```json
DELETE /library

POST /library/_bulk
{"index": {"_id": 1}}
{"title": "The quick brown fox", "price": 5}
{"index": {"_id": 2}}
{"title": "The quick brown fox jumps over the lazy dog", "price": 15}
{"index": {"_id": 3}}
{"title": "The quick brown fox jumps over the quick dog", "price": 8}
{"index": {"_id": 4}}
{"title": "Brown fox and brown dog", "price": 2}
{"index": {"_id": 5}}
{"title": "Lazy dog", "price": 9}
```

###　文字列の分析結果を表示

"Brown fox brown dog"　の分析結果を表示します。

"Brown" "fox" "brown" "dog" の ４ つの単語に分解されていることが分かります。　

コマンド

```json
GET /library/_analyze
{
  "tokenizer": "standard",
  "text": "Brown fox brown dog"
}
```

実行結果

```json
{
  "tokens" : [
    {
      "token" : "Brown",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "fox",
      "start_offset" : 6,
      "end_offset" : 9,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "brown",
      "start_offset" : 10,
      "end_offset" : 15,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "dog",
      "start_offset" : 16,
      "end_offset" : 19,
      "type" : "<ALPHANUM>",
      "position" : 3
    }
  ]
}
```

###　フィルターを指定して文字列の分析結果を表示

"filter" クエリに "lowercase" を指定すると、文字列を小文字に変換して分析します。

最初の "Brown" が "brown" として分析されていることが分かります。　

コマンド

```json
GET /library/_analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase"],
  "text": "Brown fox brown dog"
}
```

実行結果

```json
{
  "tokens" : [
    {
      "token" : "brown",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "fox",
      "start_offset" : 6,
      "end_offset" : 9,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "brown",
      "start_offset" : 10,
      "end_offset" : 15,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "dog",
      "start_offset" : 16,
      "end_offset" : 19,
      "type" : "<ALPHANUM>",
      "position" : 3
    }
  ]
}
```

###　複数のフィルターを指定して文字列の分析結果を表示

"filter" クエリに "lowercase"　と "unique"　を指定すると、文字列を小文字に変換し、さらに重複する単語を削除して分析します。

"Brown" が小文字に変換され、重複した 2 つ目の "brown" は削除されていることが分かります。

コマンド

```json
GET /library/_analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase","unique"],
  "text": "Brown brown brown fox brown dog"
}
```

実行結果

```json
{
  "tokens" : [
    {
      "token" : "brown",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "fox",
      "start_offset" : 18,
      "end_offset" : 21,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "dog",
      "start_offset" : 28,
      "end_offset" : 31,
      "type" : "<ALPHANUM>",
      "position" : 2
    }
  ]
}
```

###　トークナイザの違いによる分析結果を表示

トークナイザに "standard" を指定した場合と、"letter" を指定した場合で、分析結果を比較します。

"quick.brown_fox" の部分の分析結果が異なることが分かります。

コマンド

```json
GET /library/_analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase"],
  "text": "THE quick.brown_FOx Jumped! $19.95 @ 3.0"
}

GET /library/_analyze
{
  "tokenizer": "letter",
  "filter": ["lowercase"],
  "text": "THE quick.brown_FOx Jumped! $19.95 @ 3.0"
}
```

実行結果

```json
# GET /library/_analyze
{
  "tokens" : [
    {
      "token" : "the",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "quick.brown_fox",
      "start_offset" : 4,
      "end_offset" : 19,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "jumped",
      "start_offset" : 20,
      "end_offset" : 26,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "19.95",
      "start_offset" : 29,
      "end_offset" : 34,
      "type" : "<NUM>",
      "position" : 3
    },
    {
      "token" : "3.0",
      "start_offset" : 37,
      "end_offset" : 40,
      "type" : "<NUM>",
      "position" : 4
    }
  ]
}

# GET /library/_analyze
{
  "tokens" : [
    {
      "token" : "the",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "quick",
      "start_offset" : 4,
      "end_offset" : 9,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "brown",
      "start_offset" : 10,
      "end_offset" : 15,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "fox",
      "start_offset" : 16,
      "end_offset" : 19,
      "type" : "word",
      "position" : 3
    },
    {
      "token" : "jumped",
      "start_offset" : 20,
      "end_offset" : 26,
      "type" : "word",
      "position" : 4
    }
  ]
}
```

###　日本語を含むドキュメントの分析結果を表示

Elasticsearch は標準で日本語に対応したトークナイザがインストールされないため 、[kuromoji](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html) プラグインをインストールしておく必要があります。

コマンド

```json
GET /library/_analyze
{
  "tokenizer": "kuromoji_tokenizer",
  "text": "記者が汽車で帰社した"
}
```

実行結果

```json
{
  "tokens" : [
    {
      "token" : "記者",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "が",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "汽車",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "で",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "word",
      "position" : 3
    },
    {
      "token" : "帰社",
      "start_offset" : 6,
      "end_offset" : 8,
      "type" : "word",
      "position" : 4
    },
    {
      "token" : "し",
      "start_offset" : 8,
      "end_offset" : 9,
      "type" : "word",
      "position" : 5
    },
    {
      "token" : "た",
      "start_offset" : 9,
      "end_offset" : 10,
      "type" : "word",
      "position" : 6
    }
  ]
}
```