どうも、今年もそろそろ終わりということで、やり残していた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ポートで立ち上がりました!
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なので、これより目標として掲げたもの遂行していきます!
目標1 テキスト検索
テキスト検索は下記のように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" : [
[
"日本海に浮かぶレストラン"
]
]
}
他にも論理式や否定、どの単語に一致したかわかるハイライト検索もできるようですが、目標2へ先を急ぎます
目標2 緯度経度検索
まず先ほどと同じ要領で緯度経度情報をもつ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になっていることが確認できます!
"location" : {
"type" : "geo_point"
}
準備が整ったので、これで緯度経度検索できます!
現在地を日本海にして検索してみると(近い順にソートさせてみると)
GET /restaurants/_search
{
"sort": [
{
"_geo_distance": {
"location": {
"lat": 38.956739,
"lon": 134.230957
},
"order": "asc"
}
}
]
}
意図通り日本海のレストランが先に取得できています!
目標2完了!
"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
]
}
]
目標3 時間検索
時間検索も緯度経度検索と同じ要領で進めていけます!
まずは営業時間として、開始時間の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"
}
}
]
まとめ
基本的な操作はこれで抑えれます!
より複雑な検索をしたいときは公式ドキュメントを参考にすればできるはず
明日は@tsun3さんの「こわくないJavaScript」です。お楽しみに!