はじめてのElasticsearch

どうも、今年もそろそろ終わりということで、やり残していたElasticsearchデビューしようと思います！
本業務でも導入していきたいという意気込みも込めて！

# 目標

やるからには目標を持ってやろうと思います！

- テキスト検索
- 緯度経度検索
- 時間検索

をできるようになることを目標とします！（ローカル環境のテストデータにて）


# まずは環境を整える

Elasticsearchをダウンロードします（Version: 7.5.0）
https://www.elastic.co/jp/downloads/elasticsearch

ファイルを展開して、Elasticsearch起動！

```
$ bin/elasticsearch
publish_address {127.0.0.1:9200}, bound_addresses {[::1]:9200}, {127.0.0.1:9200}
```
どうやら9200ポートで立ち上がりました！

Kibanaもダウンロードします（Version: 7.5.0）
https://www.elastic.co/jp/downloads/kibana

Kibanaも起動！
＊KibanaはElasticSearchにブラウザから接続するために必要

```
$ bin/kibana
http server running at http://localhost:5601
```
Kibanaも無事5601ポートで立ち上がりました！ :100:

# Kibanaからデータ登録＆データ取得してみる

先ほど立ち上げたKibanaを http://localhost:5601 をブラウザで確認
そしてDev toolsコンソールを開きます

コンソールから先ほど立ち上げたElasticsearch（http://localhost:9200） にリクエストできます

### まずは、データ登録

今回は2件の仮想の店舗データを登録します

```
POST /restaurants/_doc
{
  "name": "太平洋に浮かぶレストラン"
}

POST /restaurants/_doc
{
  "name": "日本海に浮かぶレストラン"
}
```

### そして、登録したデータを取得

検索条件を絞らずに検索してみます

```:リクエスト
GET /restaurants/_search
```

無事2件のデータが取得できます！

```:レスポンス
"hits" : [
  {
    "_index" : "restaurants",
    "_type" : "_doc",
    "_id" : "5idkDG8Br1D6GrZbRdpB",
    "_score" : 1.0,
    "_source" : {
      "name" : "太平洋に浮かぶレストラン"
    }
  },
  {
    "_index" : "restaurants",
    "_type" : "_doc",
    "_id" : "5ydkDG8Br1D6GrZbSdoz",
    "_score" : 1.0,
    "_source" : {
      "name" : "日本海に浮かぶレストラン"
    }
  }
]
```

これで準備はOKなので、これより目標として掲げたもの遂行していきます！

## 目標１ テキスト検索

テキスト検索は下記のようにqueryにmatchを使うことで絞り込めます
店舗名に「海」を含む店舗を取得するにはnameに「海」を指定してリクエストしてみると

```:リクエスト
GET /restaurants/_search
{
  "query": {
    "match": {
      "name": "海"
    }
  }
}
```

先ほど登録した「日本海に浮かぶレストラン」のみが取得できてます！

```:レスポンス
"hits" : [
  {
    "_index" : "restaurants",
    "_type" : "_doc",
    "_id" : "5ydkDG8Br1D6GrZbSdoz",
    "_score" : 0.6931472,
    "_source" : {
      "name" : "日本海に浮かぶレストラン"
    }
  }
]
```

また、SQLもパラメータに渡せるようなので、下記のようにリクエストしてみると

```:リクエスト
POST /_sql?format=json
{
    "query": "SELECT * FROM restaurants where name like '%海%'"
}
```

先ほどと返却値は異なりますが、日本海に浮かぶレストランが取得できています

```:レスポンス
{
  "columns" : [
    {
      "name" : "name",
      "type" : "text"
    }
  ],
  "rows" : [
    [
      "日本海に浮かぶレストラン"
    ]
  ]
}
```

他にも論理式や否定、どの単語に一致したかわかるハイライト検索もできるようですが、目標２へ先を急ぎます


## 目標２ 緯度経度検索

まず先ほどと同じ要領で緯度経度情報をもつlocationを追加してデータを登録します

```:リクエスト
POST /restaurants/_doc
{
  "name": "太平洋に浮かぶレストラン",
  "location": {
    "lat": "29.651658",
    "lon": "156.085510"
  }
}

POST /restaurants/_doc
{
  "name": "日本海に浮かぶレストラン",
  "location": {
    "lat": "38.956739",
    "lon": "134.230957"
  }
}
```

そして、緯度経度検索をできるようにするには、登録した緯度経度のデータがgeo pointのtypeでマッピングされている必要があります

現在のデータのマッピング情報を/restaurants/_mappingで確認してみると

```:リクエスト
GET /restaurants/_mapping
```

緯度経度情報として登録していたlocationがtextとして登録されてしまっています、、
これでは緯度経度検索できません、、

```:レスポンス
"location" : {
  "properties" : {
    "lat" : {
      "type" : "text",
      "fields" : {
        "keyword" : {
          "type" : "keyword",
          "ignore_above" : 256
        }
      }
    },
    "lon" : {
      "type" : "text",
      "fields" : {
        "keyword" : {
          "type" : "keyword",
          "ignore_above" : 256
        }
      }
    }
  }
}
```

気を取り直して、登録しなおします！

まずは、登録データを一旦削除

```:リクエスト
DELETE restaurants
```

そして、マッピングを定義（緯度経度を入れるlocationをgeo_typeとマッピング）

```:リクエスト
PUT /restaurants
PUT /restaurants/_mapping
{
  "properties": {
    "location": {
      "type": "geo_point"
    }
  }
}
```

そして、再登録

```:リクエスト
POST /restaurants/_doc
{
  "name": "太平洋に浮かぶレストラン",
  "location": {
    "lat": "29.651658",
    "lon": "156.085510"
  }
}

POST /restaurants/_doc
{
  "name": "日本海に浮かぶレストラン",
  "location": {
    "lat": "38.956739",
    "lon": "134.230957",
  }
}
```

マッピング情報を再度確認してみると

```:リクエスト
GET /restaurants/_mapping
```

typeがgeo_pointになっていることが確認できます！ :relaxed:

```:レスポンス
"location" : {
  "type" : "geo_point"
}
```

準備が整ったので、これで緯度経度検索できます！
現在地を日本海にして検索してみると（近い順にソートさせてみると）

```:リクエスト
GET /restaurants/_search
{
  "sort": [
    {
      "_geo_distance": {
        "location": {
          "lat":  38.956739,
          "lon": 134.230957
        },
        "order": "asc"
      }
    }
  ]
}
```

意図通り日本海のレストランが先に取得できています！
目標２完了！

```:レスポンス
"hits" : [
  {
    "_index" : "restaurants",
    "_type" : "_doc",
    "_id" : "7SdvDG8Br1D6GrZbYdoH",
    "_score" : null,
    "_source" : {
      "name" : "日本海に浮かぶレストラン",
      "location" : {
        "lat" : "38.956739",
        "lon" : "134.230957"
      }
    },
    "sort" : [
      0.0
    ]
  },
  {
    "_index" : "restaurants",
    "_type" : "_doc",
    "_id" : "7CdvDG8Br1D6GrZbWNqp",
    "_score" : null,
    "_source" : {
      "name" : "太平洋に浮かぶレストラン",
      "location" : {
        "lat" : "29.651658",
        "lon" : "156.085510"
      }
    },
    "sort" : [
      2250187.1065158164
    ]
  }
]
```



## 目標３ 時間検索

時間検索も緯度経度検索と同じ要領で進めていけます！

まずは営業時間として、開始時間のbusiness_start_time、終了時間のbusiness_end_timeのマッピング情報を定義します！

```:リクエスト
PUT /restaurants/_mapping
{
  "properties": {
    "business_start_time": {
      "type" : "date",
      "format": "yyyy/MM/dd HH:mm"
    },
    "business_end_time": {
      "type" : "date",
      "format": "yyyy/MM/dd HH:mm"
    }
  }
}
```

次に店舗登録

```:リクエスト
POST /restaurants/_doc
{
  "name": "太平洋に浮かぶレストラン",
  "business_start_time": "2019/12/22 17:00",
  "business_end_time": "2019/12/22 20:00"
}
```

マッピング情報を確認

```:リクエスト
GET /restaurants/_mapping
```

想定どおり、business_start_timeとbusiness_end_timeのtypeがdateになっています

```:レスポンス
"business_end_time" : {
  "type" : "date",
  "format" : "yyyy/MM/dd HH:mm"
},
"business_start_time" : {
  "type" : "date",
  "format" : "yyyy/MM/dd HH:mm"
}
```

18:00に営業している店舗があるかどうか検索してみると
（boolとmustを使うことでand検索ができます）

```:リクエスト
GET /restaurants/_search
{
  "query": {
    "bool": {
      "must": [
        { "range" : { "business_start_time" : { "lte" : "2019/12/22 18:00" } } },
        { "range" : { "business_end_time" : { "gte" : "2019/12/22 18:00" } } }
      ]
    }
  }
}
```

意図通り12/22 17:00 ~ 20:00 営業している太平洋に浮かぶレストランが取得できています！

```:レスポンス
"hits" : [
  {
    "_index" : "restaurants",
    "_type" : "_doc",
    "_id" : "-ieSDG8Br1D6GrZbKNrv",
    "_score" : 2.0,
    "_source" : {
      "name" : "太平洋に浮かぶレストラン",
      "business_start_time" : "2019/12/22 17:00",
      "business_end_time" : "2019/12/22 20:00"
    }
  }
]
```


# まとめ

基本的な操作はこれで抑えれます！
より複雑な検索をしたいときは[公式ドキュメント](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html)を参考にすればできるはず

明日は@tsun3さんの「こわくないJavaScript」です。お楽しみに！