LoginSignup
50
50

More than 3 years have passed since last update.

Elasticsearch 備忘録

Last updated at Posted at 2019-08-16

Elasticsearchを学習したのでその備忘録

自分用の備忘録のため、詳しい説明は省いています。
環境構築は、Elastic Cloudを使用しました。
https://www.elastic.co/

学習教材は、Udemy Elastic Course になります。とてもわかりやす教材でした。おすすめです。
https://www.udemy.com/elasticsearch-complete-guide/
github: https://github.com/codingexplained/complete-guide-to-elasticsearch

🌻Elasticsearch のアーキテクチャー

🌝 ノードとクラスター

クラスター { ノードA, ノードB, ノードC }
※ ノードには、データが含まれる。
※ ノードには、マスターノードが存在する。マスターノードは、クラスターの状態を更新する。
※ ノードのIDは、UUIDが使われる。
※ ノードが、データを操作する。

🌝 Indices と Documents

※ クラスターに格納されたデータはドキュメントと呼ばれる。
※ ドキュメントは、Indices に追加される。
※ Indicesは、ドキュメントのコレクションである。
※ ドキュメントは、JSONオブジェクトである。
※ ドキュメントは、インデックス化される。

🌝 シャーディング - Sharding

シャードとは、インデックスを断片化したもの。
INDEX -> SHARD A
      -> SHARD B
      -> SHARD C
      -> SHARD D
    ※ シャードには、インデックスのサブセットデータが含まれる。
    ※ シャードは、独立したインデックスである。(ある意味)
    ※ インデックス化されたドキュメントは、どれか一つのシャードに格納される。
    ※ これらのシャードは、クラスタ内のいずれかのノードに配置される。
    ex) 以下のような関係になる。
    クラスター {
        NODE A {
            SHARD A,
            SHARD B
        },
        NODE B {
            SHARD C,
            SHARD D
        }
    }
    ※ シャードをノードに分散することで、平行処理を実現する。
    ※ インデックスがすでに作られているなら、シャードを増やすことはできない。

🌝 レプリケーション - Replication

シャードは、各ノードにコピーされる。
NODE A {
    SHARD   A,      ⏩ REPLICA A (REPLICA SHARD)
    SHARD   B,      ⏩ REPLICA B
    REPLICA C,      ⏪ SHARD   C
    REPLICA D       ⏪ SHARD   D
},
NODE B {
    REPLICA A,      ⏩ SHARD   A (PRIMARY SHARD)
    REPLICA B,      ⏩ SHARD   B
    SHARD   C,      ⏩ REPLICA C
    SHARD   D       ⏩ REPLICA D
}
※ ノードが一つなくなっても、問題ない。
※ NODEごとに検索が実施される。つまり、REPLICAも、検索対象。
※ 同じSHARDがノードの存在することはない。

🌝 レプリカを同期させる仕組み

例)
1. ドキュメントを削除する
2. Client(server) から Routting に連携する
3. Routing から シャードに連携する
4. シャードは、レプリカ(レプリカグループ)に連携する。
5. レプリカ(レプリカグループ)は、完了をシャードに伝える
6. シャードは、Clientに完了を伝える。
※ レプリカがない場合は、シャードのみでオペレーションが完結する。

🌝 データの検索

クライアント(サーバー)
↓ 検索クエリ  ↑ 結果
Elasticsearch クラスター

例)以下の構成を考える
クラスター {
    NODE A {
        SHARD A,
        REPICA C1
        REPLICA B2
    },
    NODE B {
        SHARD B,
        REPLICA A1,
        REPLICA C2
    },
    NODE C {
        SHARD C,
        REPLICA B1,
        REPLICA A2
    }
}

● 手順
------
1. クライアントから、検索クエリをかける。
2. 「SHARD B」に検索がかかり、これをコーディネートシャードとする。
3. 「SHARD B」は、「SHARD A」と「SHARD C」に連携(ブロードキャスト)する。
4. 「SHARD A」と「SHARD C」は、結果をコーディネートシャードに回答する。
5. 結果をクライアントに送る。

🌝 シャードをまたぐドキュメントの配布 - Routing が実現する。

Elasticsearchで、ドキュメントを配布する際、シャードはどのように決められるのだろうか?

-----------------------------------------------
🌈 shard = hash(routing) % total_primary_shards
-----------------------------------------------
クラスタ {
    NODE A {
        SHARD A,
        SHARD B,
        SHARD C
    },
    NODE B {
        SHARD D,
        SHARD E,
        SHARD F
    }
}
※ ドキュメントが追加されると、Routingに連携され、公式によりシャードを特定する。

🌻ドキュメントの管理

🌝 ファーストコンタクト

curl -u elastic:xxxxxx https://zzzzzz.asia-northeast1.gcp.cloud.es.io:9243

● 似た概念
Relational DB ⇒ Databases ⇒ Tables ⇒ Rows ⇒ Columns
Elasticsearch ⇒ Indices ⇒ Types ⇒ Documents ⇒ Fields

🌝 クラスターへのクエリー発行

GET /myindex/mytype/_search

curl -XGET "http://zzzzzz.containerhost:9244/myindex/mytype/_search"
curl -u elastic:xxxxxx -XGET "https://zzzzzz.asia-northeast1.gcp.cloud.es.io:9243/myindex/mytype/_search"
curl -u elastic:xxxxxx -XGET "https://zzzzzz.asia-northeast1.gcp.cloud.es.io:9243/myindex/mytype/_search" -H 'Content-Type: application/json' -d'{  }'

🌝 クラスタにインデックスを作成する(KibanaのDevTool もしくは、Postman で可能)

Kibanaへ接続 - 👀 https://zzzzzz.asia-northeast1.gcp.cloud.es.io:9243/app/kibana#/dev_tools/console?_g=()
PUT /product?pretty
PUT /productaaaaa?pretty
PUT /[index name]?pretty

🌝 クラスタにドキュメントを作成する

POST /product/default
{
  "name": "Processing Events with Logstash",
  "instructor": {
    "firstName": "Bo",
    "lastName": "Andersen"
  }
}

POST /product/default/1
{
  "name": "Complete Guide to Elasticsearch",
  "instructor": {
    "firstName": "Bo",
    "lastName": "Andersen"
  }
}

🌝 ドキュメントの取得(Retrieve)

GET /product/_doc/1

🌝 ドキュメントの置き換え (Peplace)

    PUT /product/_doc/1
    {
      "name": "Complete Guide to Elasticsearch",
      "instructor": {
        "firstName": "Bo",
        "lastName": "Andersen"
      },
      "price": 195
    }

🌝 ドキュメントの更新 (Update)

POST /product/default/1/_update
{
  "doc": {"price": 95, "tags":["Elasticsearch"]}
}
GET /product/_doc/1

🌝 スクリプトからの更新 - scripted updates -

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html

POST /product/_doc/1/_update
{
  "script": "ctx._source.price += 10"
}
GET /product/_doc/1

🌝 Upserts (更新と削除)

DELETE /product/_doc/1

POST /product/_doc/1/_update
{
  "script": "ctx._source.price += 5",
  "upsert": {
    "price": 100
  }
}

GET /product/_doc/1

🌝 削除 (クエリによる削除)

DELETE /product/_doc/1

POST /product/default
{
  "name": "Processing Events with Logstash",
  "category": "course"
}

POST /product/default
{
  "name": "The Art of Scalability",
  "category": "book"

}

POST /product/_delete_by_query
{
  "query": {
    "match" : {
      "category":"book"
    }
  }
}

🌝 indices の削除

indexの削除
DELETE /product

🌝 バッチプロセス

POST /product/default/_bulk
{ "index" : {"_id": "100"} }
{ "price" : 100 }
{ "index" : {"_id": "101"} }
{ "price" : 101 }


POST /product/default/_bulk
{ "update": { "_id": 100 } }
{ "doc": { "price": 1000 }}
{ "delete" : {"_id": 101 }}

GET /product/default/100            -  success
GET /product/default/101            -  fail

🌝 cURL からテストデータをインポートする

👀 https://curl.haxx.se/download.html
jsondata: 

1. サーバーに対して、読み込みファイルの書き込み
curl -u elastic:xxxxxxx -H "Content-Type: application/json" -XPOST "https://zzzzzz.asia-northeast1.gcp.cloud.es.io:9243/product/default/_bulk?pretty" --data-binary "@test-data.json"
※ test-data.json はテストデータ。コマンドを配下のディレクトリに配置する。

🌝 クラスターについて

GET /_cat/health?v
epoch      timestamp cluster                          status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1565431305 10:01:45  zzzzzz green           3         2     12   6    0    0        0             0                  -                100.0%

GET /_cat/nodes?v
ip           heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
10.46.32.57            13          58   0    0.38    0.31     0.21 dim       -      instance-0000000001
10.46.47.211           12          94   1    0.32    0.48     0.48 dim       -      instance-0000000000
10.46.32.42            27          85   7    1.68    2.78     2.70 m         *      tiebreaker-0000000002

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-nodes.html

GET /_cat/indices?v
health status index                           uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   .security-7                     pcF96KjyTuOzzqk2XuBFAg   1   1         38            0    152.9kb         97.3kb
green  open   .kibana_task_manager            148MpPN1Qja7jR5Q6dlryA   1   1          2            0     28.5kb         12.7kb
green  open   product                         XZ21k5TcR9-e1rrSczgITg   1   1       1000            0    771.6kb        385.8kb
green  open   apm-7.3.0-onboarding-2019.08.10 Xkp95nZLQW2oVxkAmnrx5g   1   1          1            0     12.5kb          6.2kb
green  open   kibana_sample_data_ecommerce    DgqwRhhAQbyZBvR-QAfx5w   1   1       4675            0     10.2mb          5.1mb
green  open   .kibana_1                       dOu_LXCwTeaXLxtxib2F4g   1   1         52            1      1.9mb        992.8kb

GET /_cat/allocation?v
shards disk.indices disk.used disk.avail disk.total disk.percent host         ip           node
     6        6.6mb    28.9mb    119.9gb      120gb            0 10.46.32.57  10.46.32.57  instance-0000000001
     6        6.4mb    21.3mb    119.9gb      120gb            0 10.46.47.211 10.46.47.211 instance-0000000000

GET /_cat/shards?v
index                           shard prirep state   docs   store ip           node
product                         0     p      STARTED 1000 385.8kb 10.46.32.57  instance-0000000001
product                         0     r      STARTED 1000 385.8kb 10.46.47.211 instance-0000000000
apm-7.3.0-onboarding-2019.08.10 0     p      STARTED    1   6.2kb 10.46.32.57  instance-0000000001
apm-7.3.0-onboarding-2019.08.10 0     r      STARTED    1   6.3kb 10.46.47.211 instance-0000000000
kibana_sample_data_ecommerce    0     p      STARTED 4675   5.1mb 10.46.32.57  instance-0000000001
kibana_sample_data_ecommerce    0     r      STARTED 4675     5mb 10.46.47.211 instance-0000000000
.security-7                     0     p      STARTED   38  97.3kb 10.46.32.57  instance-0000000001
.security-7                     0     r      STARTED   38  55.6kb 10.46.47.211 instance-0000000000
.kibana_1                       0     p      STARTED   52 992.8kb 10.46.32.57  instance-0000000001
.kibana_1                       0     r      STARTED   52 987.8kb 10.46.47.211 instance-0000000000
.kibana_task_manager            0     p      STARTED    2  12.7kb 10.46.32.57  instance-0000000001
.kibana_task_manager            0     r      STARTED    2  15.7kb 10.46.47.211 instance-0000000000

🌻マッピング - mapping

🌝 ダイナミックマッピング - elasticsearch は、データ型を自動的に割り当ててくれる

GET /product/_mapping

🌝 メタフィールド - Meta Fields

_index       : ドキュメントが属するインデックスの名前に含まれる
_id          : ドキュメントのIDを格納する
_source      : ドキュメントをインデックス化する際に使用される元のJSONを含む
_field_names : non-null値を含む全てのフィールドの名前を含む
_routing     : シャードに対するドキュメントをルーティングする値を格納する
_version     : ドキュメントの内部バージョンを格納する
_meta        : Elasticsearchによって変更されたくないカスタムデータを格納するために使用されるかも

🌝 フィールドデータ型 - Field data type

1. Core Data Types
    text      : ディスクリプションのようなテキストをインデックス化するために使用される。値は解析される。
    keyword   : [tag, categories, email] のような構造データに使用される。値は解析されない。フィルターや集約に使用される。
    numeric   : long, integer, byte, half_float など
    date, Boolean
    binary    : Base64エンコードバイナリを受け付ける。デフォルトでは格納されない。
    range     : データ範囲や数値範囲などの値を特定するために使用される。ex) 10 < x < 20 "gte": 10, "lte": 20 data_range
2. Complex Data Types
    object    : JSONオブジェクトとして追加される。入れ子になったオブジェクトも含まれるが、内部では単一階層のキーバリューペアとして格納される。
                {
                    "name" : {
                        "firstName": "Bo",
                        "lastName" : "Andersen"
                    },
                    "profession": "Software Engineer"
                }
                ↓ 内部では下記のように扱われる
                {
                    "name.firstName": "Bo",
                    "name.lastName" : "Andersen",
                    "profession": "Software Engineer"
                }
    array     : [1,2,3] ["one", "two", "three"]
    array of Object: Objectと似ている
                {
                    "persons": {
                        { "name": "Bo Anderson", "age": 28 },
                        { "name": "Fogell Maclovin", "age": 20 },
                    }
                }
                ↓ 内部では下記のようになる
                {
                    "persons.name": [ "Bo Anderson", "Fogell Maclovin" ],
                    "persons.age": [ 20, 28]
                }
    Nested   : Objectデータ型の特別なバージョン。Arrayオブジェクト型の入れ子を可能にする。
3. Geo Data Types : 地理データのために使用される
    geo_point : 軽度と緯度のペアを受け付ける。地理的な操作のために使用される。
    geo_shape : point, polygon, linestring, multipoint, multilinestring, multipolygon, geometrycollection, envelope, circle
4. Specialized Data Types
    ip   : IPv4 や IPv6 IPアドレスを格納するために使用される
    completion : オートコンプリート検索機能を提供するために使用される。クイックルックアップを最適化する。
    Attachment : Ingest Attachment プロセッサ プラグインが必要。PPTやPDF,RTFなど様々なドキュメントの検索が可能となる。
                テキスト認識のために「Apache Tika」で使用されている。
                sudo bin/elasticsearch-plugin install ingest-attachment

🌝 既存の「indeces」に「mapping」を追加する

PUT /product/_mapping
{
  "properties": {
    "discount": {
      "type": "double"
    }
  }
}
- 確認
GET /product/_mapping

🌝 マッピングの更新クエリ

DELETE /product     // 更新するには、一旦、削除しなければいけない。しかし、このやり方は、既存のインデックスを削除するのでダメ!

PUT /product
{
  "mappings": {
    "dynamic": false,
    "properties": {
      "in_stock": {
        "type": "integer"
      },
      "is_active": {
        "type": "boolean"
      },
      "price": {
        "type": "integer"
      },
      "sold": {
        "type": "long"
      }
    }
  }
}

🌝 マッピング変数 - mapping parameters

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-params.html
👀 https://www.joda.org/joda-time/apidocs/org/joda/time/format/DateTimeFormat.html
👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats

coerce : coerce(強制)を無効化するために使われる(自動的に値をクリアする)
"5" → 5, "5.0" → 5, 5.0 → 5

copy_to : カスタムフィールドの作成を有効にする。任意のフィールドにフィールドの値をコピーする。値をコピーするが、termsをコピーするわけではない。

{
"first_name": {
"type": "text",
"copy_to": "full_name"
},
"last_name": {
"type": "text",
"copy_to": "full_name"
},
"full_name": {
"type": "text"
}
}

dynamic : ドキュメントや内部オブジェクトへフィールド追加を動的に有効・無効化する。

{
"mappings": {
"defualt": {
"dynamic": false,
"properties": {
"name": {
"dynamic": true,
"properties": {}
}
}
}
}
}

properties : フィールドマッピング、トップレベルのドキュメント、内部オブジェクトを含む。

{
"mappings": {
"default": {
"properties": {
"name": {
"properties": {
"first_name": { "type": "text" },
"last_name": { "type": "text" }
}
}
}
}
}
}

norms : norm(関連スコアに使われる)格納を有効化、無効化する

{
"properties": {
"full_name": {
"type": "text",
"normes": false
}
}
}

format : dateフィールドのためのフォーマットを定義する。

"yyyy-MM-dd", "epoch_millis", "epoch_second", ...

null_value : NULL値を特別な値に置換する

{
"properties": {
"discount": {
"type": "integer",
"null_value": 0
}
}
}

field : フィールドをインデックス化するために使われる

🌝 multi-fieldマッピングを追加する

GET /product/_mapping

PUT /product/_mapping
{
  "properties": {
    "description": {
      "type": "text"
    },
    "name": {
      "type": "text",
      "fields": {
        "keyword": {
          "type": "keyword"
        }
      }
    },
    "tags": {
      "type": "text",
      "fields": {
        "keyword": {
          "type": "keyword"
        }
      }
    }
  }
}

GET /product/_mapping

🌝 カスタムDate型を定義する

GET /product/_mapping

PUT /product/_mapping
{
  "properties": {
    "created": {
      "type": "date",
      "format": "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd"
    }
  }
}

GET /product/_mapping


curl -u elastic:xxxxxx -H "Content-Type: application/json" -XPOST "https://zzzzzz.asia-northeast1.gcp.cloud.es.io:9243/product/default/_bulk?pretty" --data-binary "@test-data.json"

🌝 ダイナミックマッピングなしに新しいフィールドをピックアップする。

POST /product/_doc/2000
{
  "description": "Test",
  "discount": 20
}

PUT /product/_mapping
{
  "properties": {
    "discount": {
      "type": "integer"
    }
  }
}

GET /product/_search
{
  "query": {
    "match": {
      "description": "Test"
    }
  }
}

GET /product/_search
{
  "query": {
    "term": {
      "discount": 20
    }
  }
}
👉 このクエリは失敗する。インデックス化されていないため。以下のコマンドを打ち込みインデックス化する。ダイナミックマッピングを無効化している。
POST /product/_update_by_query?conflicts=proceed

GET /product/_search
{
  "query": {
    "term": {
      "discount": 20
    }
  }
}
👉 これは、成功する。👀

🌻 解析とアナライザー - Analysis & Analyzers

🌝 アナライザーについて

📝 アナライザーは、キャラクターフィルター、トークナイザー、トークンフィルターのセット
📝 アナライザーは、テキストフィールドをインデックス化するために使われる。

🌀 解析のステップ
---------------
① キャラクターフィルター(「text」を操作する)
    <strong>Two</strong>words!   → Two words!
② トークナイザー(「text」を「terms」に分割する)
    Two words!                   → [Two, words]
③ トークンフィルター(転置インデックスに追加する前に「terms」を操作する)
    [Two, words]                 → [two, words]

🌝 アナライズAPIの使用

POST _analyze
{
  "tokenizer": "standard",
  "text": "I'm in the mood for drinking semi-dry red wine!"
}

POST _analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase"],
  "text": "I'm in the mood for drinking semi-dry red wine!"
}
-> 小文字で解析

POST _analyze
{
  "analyzer": "standard",
  "text": "I'm in the mood for drinking semi-dry red wine!"
}
-> 小文字で解析

🌝 転置インデックスを理解する。- inverted index

クラスターには転置インデックスが一つは必要

📝 転置インデックス は、「Field」の「terms」のマッピングであり、それぞれの「term」を含むドキュメントである。 
ある単語がドキュメントに含まれるかを串刺しでインデックスする形態
📝 転置インデックス は、「text field」ごとに追加される。
------------------------------------------
term         | documents#1   | documents#2
------------------------------------------
best         | ✔             | 
------------------------------------------
● delicious  |               | ✅
------------------------------------------
pasta        | ✔             | ✔
------------------------------------------
pesto        | ✔             | 
------------------------------------------
● recipe     | ✅            | ✅
------------------------------------------
the          |               | ✔
------------------------------------------

🌝 キャラクターフィルターの概要

HTML Strip Character Filter (html_strip)   テキスト中のHTMLタグを取り除く。
Mapping Character Filter (mapping)   キーと値のマップに基づき、値を置き換える。ex) _sad_ → :<
Pattern Replace (pattern_replace)   キャラクターをマッチするための正規表現、及び、特定のキャラクターへの置き換え。

🌝 トークナイザーの概要

1. Word Oriented Tokenizers
    📝 全文を個別の単語にトークン化するために使用される
    📍 Standard (standard) 📎 テキストを「term」に分割する。ほとんどのシンボルを除外する。
    📍 Letter  (letter) 📎 文字ではないキャラクターに出くわした際に「terms」に分割する。
    📍 Lowercase (lowercase) 📎小文字にする
    📍 Whitespace (whitespace)
    📍 UAX URL Email (uax_url_email) "contact us at info@sample.com or visit http://sample.com" → [contact, us, info@sample.com,,,,]
2. Partial Word Tokenizers
    📝 テキスト、または、文章を小さな断片に分割する。部分ワードマッチングに利用される。
    📍 N-Gram (ngram)
        "Red wine"   ⏩   [ Re, Red, ed, wi, win, wine, in, ine, ne ]
    📍 Edge N-Gram (edge_ngram)
        "Red wine"   ⏩   [ Re, Red, wi, win, wine ]
3. Structured Text Tokenizers
    📝 EmailやZipコード、個人番号などを構造化されたテキストに使用される
    📍 Keyword (keyword) 📎 単一の「term」として全く同じテキストを出力するトークナイザー。(何もしない。)
    📍 Pattern (pattern) 📎 単語のセパレーターにマッチする時に「text」を「term」に分割するために正規表現を使う
        "I, like, red, wine!"   ⏩   [I,like,red,wine!]
    📍  Path (path_hiearchy) 📎 階層を分割する。
        /path/to/some/directory   ⏩  [/path,/path/to,path/to/some,/path/to/some/directory]

🌝 トークンフィルターの概要

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html
Standard (standard)
Lowercase (lowercase)
Uppercase (uppercase)
NGram (nGram)
Edge NGram (edgeNGram)
Stop (stop)
Word Delimiter (word_delimiter)
Stemmer (stemmer)  ex) drinking → drink
Keyword Marker (keyword_marker) ex) drinking → drinking 変更を保護する
Snowball (snowball)
Synonym (synonym) ex) happy → happy/delighted

🌝 ビルトイン・アナライザーの概要

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html
Simple (simple) 文字ではないキャラクターに遭遇した際に「text」を「term」に分割する
    ex) "semi-dry" → [sem,dry]
Stop (stop) 📎 単語のストップワードを除外して、「term」化する。
Language (english, ...) 📎 言語に特化したアナライザー
    👀 日本語、中国語、韓国語
    - https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lang-analyzer.html
    - https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html
    - https://www.elastic.co/blog/how-to-search-ch-jp-kr-part-1
Keyword (keyword) 📎 何もしない
Pattern (pattern)
Whitespace (whitespace)

🌝 ビルトイン・アナライザーとトークンフィルターの構成

PUT /existing_analyzer_config
{
  "settings": {
    "analysis": {
      "analyzer": {
        "english_stop": {
          "type": "standard",
          "stopwords": "_english_"
        }
      },
      "filter": {
        "my_stemmer": {
          "type": "stemmer",
          "name": "english"
        }
      }
    }
  }
}

● 通常のクエリ
POST /existing_analyzer_config/_analyze
{
  "analyzer": "english_stop",
  "text": "I'm in the mood for drinking semi-dry red wine!"
}

● 定義したmy_stemmerを使用したクエリ
POST /existing_analyzer_config/_analyze
{
  "tokenizer": "standard",
  "filter": ["my_stemmer"],
  "text": "I'm in the mood for drinking semi-dry red wine!"
}

🌝 カスタム・アナライザーの作成方法

PUT /analyzers_test_2
{
  "settings": {
    "analysis": {
      "analyzer": {
        "english_stop": {
          "type": "standard",
          "stopwords": "_english_"
        },
        "my_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "char_filter": [
            "html_strip"
          ],
          "filter" :[
            "lowercase",
            "trim",
            "my_stemmer"
          ]
        }
      },
      "filter": {
        "my_stemmer": {
          "type": "stemmer",
          "name": "english"
        }
      }
    }
  }
}

POST /analyzers_test_2/_analyze
{
  "analyzer": "my_analyzer",
  "text": "I'm in the mood for drinking semi-dry red wine!"
}

🌝 マッピングにおけるアナライザーの使い方

● アナライザーをマッピングする
PUT /analyzers_test_2/_mapping
{
  "properties": {
    "description": {
      "type": "text",
      "analyzer": "my_analyzer"
    },
    "teaser": {
      "type": "text",
      "analyzer": "standard"
    }
  }
}

● ドキュメントに追加する
POST /analyzers_test_2/_doc/1
{
  "description": "drinking",
  "teaser": "drinking"
}

● アナライザーを介してクエリをかける
GET /analyzers_test_2/_search
{
  "query": {
    "term": {
      "teaser": {
        "value": "drinking"
      }
    }
  }
}

🌝 既存の「indices」にアナライザーを追加する

User Settings - https://www.elastic.co/guide/en/cloud-enterprise/2.2/ece-add-user-settings.html

① 一旦、クローズする
POST /analyzers_test_2/_close

② 新規にアナライザーを設定する 
PUT /analyzers_test_2/_settings
{
  "analysis": {
    "analyzer": {
      "french_stop": {
        "type": "standard",
        "stopwords": "_french_"
      }
    }
  }
}

③ 再度、オープンする
POST /analyzers_test_2/_open

🌻 検索の概要

🌝 Request URI を使って検索する

GET /product/_search?q=*
GET /product/_search?q=name:Lobster
GET /product/_search?q=tags:Meat
GET /product/_search?q=tags:Meat AND name:Tuna

🌝 クエリDSL の概要

クエリDSL
    1. Leaf Query
    2. Compound Query

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html

GET /product/_search
{
  "query": {
    "match_all": {

    }
  }
}

🌝 クエリの結果を理解する

{
  "took" : 1,               // クエリの時間(ミリセカンド)
  "timed_out" : false,      // クエリがタイムアウトしたかどうか
  "_shards" : {
    "total" : 1,            // 検索したシャードの合計数
    "successful" : 1,       // 成功した数
    "skipped" : 0,          // スキップ数
    "failed" : 0            // 失敗した数
  },
  "hits" : {
    "total" : {
      "value" : 1000,       // 検索対象となるドキュメントのトータル数
      "relation" : "eq"
    },
    "max_score" : 1.0,      // 検索の中で、最も高いスコア値が返却される。
    "hits" : [              // ヒットしたドキュメントの10件を返す。(デフォルト)
      {
        "_index" : "product",
        "_type" : "default",
        "_id" : "1",
        "_score" : 1.0,     // マッチしたスコア値。このクエリは、「match_all」を使用したためすべて「1」と設定される。
        "_source" : {
          "name" : "Wine - Maipo Valle Cabernet",
          "price" : 152,
          "in_stock" : 38,
          "sold" : 47,
          "tags" : [
            "Alcohol",
            "Wine"
          ],
          "description" : "Aliquam augue quam, sollicitudin vitae, consectetuer eget, rutrum at, lorem. Integer tincidunt ante vel ipsum. Praesent blandit lacinia erat. Vestibulum sed magna at nunc commodo placerat. Praesent blandit. Nam nulla. Integer pede justo, lacinia eget, tincidunt eget, tempus vel, pede. Morbi porttitor lorem id ligula.",
          "is_active" : true,
          "created" : "2004/05/13"
        }
      },

🌝 関連スコアを理解する - relevance scores

検索には、以前は、「TF/IDF」が使用されていたが、現在は「Okapi BM25」 アルゴリズムが使用されている。

「TF/IDF」は、
・ Term Frequency (TF) : 与えられたドキュメントのフィールドの中で、何回、対象の「Term」が出現したか?
・ Inverse Document Frequency (IDF) : インデックス中にどれくらいの頻度で、対象の「Term」が出現したか?(すべてのドキュメントで)
・ Field-length norm : どれくらフィールドは長いか?
を考慮したアルゴリズムとなっている。

「BM25アルゴリズム」は、「TF/IDF」の特徴に加えて、
・「stop words」のハンドリングが、TF/IDFよりも優れている。
・ field-lenght norm factor を改良
・ パラメータを設定できる

🌟 スコアの詳細を調査する
GET /product/_search
{
  "explain": true,  // 🌟 追加
  "query": {
    "term": {
      "name": "lobster"
    }
  }
}

"_explanation" : {
  "value" : 6.035804,
  "description" : "weight(name:lobster in 18) [PerFieldSimilarity], result of:",
  "details" : [
    {
      "value" : 6.035804,
      "description" : "score(freq=1.0), product of:",
      "details" : [
        {
          "value" : 2.2,
          "description" : "boost",
          "details" : [ ]
        },
        {
          "value" : 5.2040067,
          "description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:",
          "details" : [
            {
              "value" : 5,
              "description" : "n, number of documents containing term",
              "details" : [ ]
            },
            {
              "value" : 1000,
              "description" : "N, total number of documents with field",
              "details" : [ ]
            }
          ]
        },

🌝 予期しない検索結果をデバッグする - _explain を使う

GET /product/_doc/1/_explain
{
  "query": {
    "term": {
      "name": "lobster"
    }
  }
}

#! Deprecation: [types removal] Specifying a type in explain requests is deprecated.
{
  "_index" : "product",
  "_type" : "_doc",
  "_id" : "1",
  "matched" : false,
  "explanation" : {
    "value" : 0.0,
    "description" : "no matching term",
    "details" : [ ]
  }
}

🌝 クエリーコンテキスト

🌸 クエリーコンテキスト     -> ドキュメントはどれくらいの関連度でマッチししているか?(関連度を計算する)
🌺 フィルターコンテキスト  -> ドキュメントはマッチしているか?(関連スコアは計算しない)ex) Date,Status,Rangesなどに使用される

🌝 「フルテキスト」クエリ VS 「TERM レベル」クエリ

● 検索結果を返す
GET /product/_search
{
  "query": {
    "term": {
      "name": "lobster" // 🌟
    }
  }
}

❌ 検索結果をさない
GET /product/_search
{
  "query": {
    "term": {
      "name": "Lobster" // 🌟
    }
  }
}

● 検索結果を返す
GET /product/_search
{
  "query": {
    "match": {          // 🌟
      "name": "Lobster"
    }
  }
}

🌟 なぜ「term」の"Lobster"は、検索結果が0件なのか?
elasticsearchで格納する際には、すべて小文字でインデックス化される。
「Term」クエリを実施する際は、ドキュメントにではなく、転置インデックスに検索をかける。
🌟 ではなぜ、「match」クエリでは、結果を返したのだろうか?
「Match」クエリ(フルテキスト) → 「解析」 → 「転置インデックス」の手順を踏むから。
対して、「Term」クエリは、「Match」クエリ(フルテキスト) → 「転置インデックス」の手順で検索する。

🌻 「Term」レベル検索

🌝 「Term」レベル検索

GET /product/_search
{
  "query": {
    "term": {
      "is_active": true
    }
  }
}

GET /product/_search
{
  "query": {
    "term": {
      "is_active": {
        "value": true
      }
    }
  }
}

🌝 「Multipule Term」レベル検索

GET /product/_search
{
  "query": {
    "terms": {
      "tags.keyword": [
        "Soup",
        "Cake"
      ]
    }
  }
}

🌝 IDsによるドキュメント検索

GET /product/_search
{
  "query": {
    "ids": {
      "values": [1,2,3]
    }
  }
}

🌝 範囲検索

GET /product/_search
{
  "query": {
    "range": {
      "in_stock": {
        "gte": 1,
        "lte": 5
      }
    }
  }
}

GET /product/_search
{
  "query": {
    "range": {
      "created": {
        "gte": "2010/01/01",
        "lte": "2010/12/31"
      }
    }
  }
}

GET /product/_search
{
  "query": {
    "range": {
      "created": {
        "gte": "01-01-2010",
        "lte": "31-12-2010",
        "format": "dd-MM-yyyy"
      }
    }
  }
}

🌝 デート型に関連したマッチング手法

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#date-math

GET /product/_search
{
  "query": {
    "range": {
      "created": {
        "gte": "2017/01/01||-1y"
      }
    }
  }
}

GET /product/_search
{
  "query": {
    "range": {
      "created": {
        "gte": "2017/01/01||-1y-1d"
      }
    }
  }
}


| Operator         |Rounding direction |Before              |After
|:-----------------|------------------:|:------------------:|:------------------:|
| gt               | up                | 2010-01-20         | 2010-01-31         |
| gte              | down              | 2010-01-20         | 2010-01-01         |
| lt               | down              | 2010-01-20         | 2010-01-01         |
| lte              | up                | 2010-01-20         | 2010-01-31         |

GET /product/_search
{
  "query": {
    "range": {
      "created": {
        "gte": "2017/01/01||-1y/M"
      }
    }
  }
}

GET /product/_search
{
  "query": {
    "range": {
      "created": {
        "gte": "now/M-2y"
      }
    }
  }
}

🌝 Non-Null 値のドキュメント検索

GET /product/_search
{
  "query": {
    "exists": {
      "field": "tags"           // 🌿 タグがあるデータを検索するパターン
    }
  }
}

🌝 prefix に基づく検索パターン

GET /product/_search
{
  "query": {
    "prefix": {
      "tags.keyword": "Vege"    // 🌿 Vegeから始まるタグを検索
    }
  }
}

🌝 ワイルドカードによる検索

GET /product/_search
{
  "query": {
    "wildcard": {
      "tags.keyword": "Veg*ble" // 🌿 ワイルドカード
    }
  }
}

GET /product/_search
{
  "query": {
    "wildcard": {
      "tags.keyword": "Vegi?able" // 🌿 ?を使ったパターン。文字数は同じでなければならい。
    }
  }
}

🌝 正規表現による検索

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#regexp-syntax

GET /product/_search
{
  "query": {
    "regexp": {
      "tags.keyword": "Veget[a-zA-Z]+ble" // 🌿 正規表現
    }
  }
}

🌻 「フルテキスト」レベル検索

● 準備
curl -u elastic:xxxxxx -H "Content-Type: application/json" -XPOST "https://zzzzzz.asia-northeast1.gcp.cloud.es.io:9243/recipe/default/_bulk?pretty" --data-binary @recipe-data.json

🌝 マッチクエリによるフレキシブルなマッチング

GET /recipe/_search
{
  "query": {
    "match": {
      "title": "Recipes with pasta or spagetti"
    }
  }
}

GET /recipe/_search
{
  "query": {
    "match": {
      "title": {
        "query": "pasta or spaghetti",
        "operator": "and"
      }
    }
  }
}

GET /recipe/_search
{
  "query": {
    "match": {
      "title": {
        "query": "pasta spaghetti",
        "operator": "and"
      }
    }
  }
}

🌝 マッチングフレーズ - match_phrase

GET /recipe/_search
{
  "query": {
    "match_phrase": {
      "title": "spaghetti puttanesca"
    }
  }
}

🌝 マルチフィールド検索 - multi_match

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html#multi-match-types

GET /recipe/_search
{
  "query": {
    "multi_match": {
      "query": "pasta",
      "fields": ["title", "description"]
    }
  }
}

🌻 ブーリアンロジックを検索に追加する ( Compound Query )

🌝 ブーリアンロジックによる検索

GET /recipe/_search
{
  "query": {
    "bool" :{
      "must": [
        {
          "match": {
            "ingredients.name": "parmesan"
          }
        },
        {
          "range": {
            "preparation_time_minutes": {
              "gte": 15
            }
          }
        }
      ]
    }
  }
}


GET /recipe/_search
{
  "query": {
    "bool" :{
      "must": [
        {
          "match": {
            "ingredients.name": "parmesan"
          }
        }
      ],
      "filter": [
        {
          "range": {
            "preparation_time_minutes": {
              "gte": 15
            }
          }
        }
      ]
    }
  }
}

GET /recipe/_search
{
  "query": {
    "bool" :{
      "must": [
        {
          "match": {
            "ingredients.name": "parmesan"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "ingredients.name": "tuna"
          }
        }
      ],
      "filter": [
        {
          "range": {
            "preparation_time_minutes": {
              "gte": 15
            }
          }
        }
      ]
    }
  }
}

GET /recipe/_search
{
  "query": {
    "bool" :{
      "must": [
        {
          "match": {
            "ingredients.name": "parmesan"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "ingredients.name": "tuna"
          }
        }
      ],
      "should":[
        {
          "match": {
            "ingredients.name": "parsley"
          }
        }
      ],
      "filter": [
        {
          "range": {
            "preparation_time_minutes": {
              "gte": 15
            }
          }
        }
      ]
    }
  }
}


GET /recipe/_search
{
  "query": {
    "bool" :{
      "must": [
        {
          "match": {
            "ingredients.name": "pasta"
          }
        }
      ],
      "should":[
        {
          "match": {
            "ingredients.name": "parmesan"
          }
        }
      ]
    }
  }
}


GET /recipe/_search
{
  "query": {
    "bool" :{
      "should":[
        {
          "match": {
            "ingredients.name": "parmesan"
          }
        }
      ]
    }
  }
}

🌝 「named」クエリによるデバッグ

GET /recipe/_search
{
  "query": {
    "bool" :{
      "must": [
        {
          "match": {
            "ingredients.name": {
              "query": "parmesan",
              "_name": "parmesan_must"
            }
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "ingredients.name": {
              "query": "tuna",
              "_name": "tuna_must"
            }
          }
        }
      ],
      "should":[
        {
          "match": {
            "ingredients.name": {
              "query": "parsley",
              "_name": "parsley_must"
            }
          }
        }
      ],
      "filter": [
        {
          "range": {
            "preparation_time_minutes": {
              "gte": 15,
              "_name": "prep_time_filter"
            }
          }
        }
      ]
    }
  }
}

🌝 どのように「matched」クエリは動作するのだろうか?

以下のクエリは同じ結果

GET /recipe/_search
{
  "query": {
    "match": {
      "title": "pasta carbonara"
    }
  }
}

GET /recipe/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "title": "pasta"
          }
        },
        {
          "term": {
            "title": "carbonara"
          }
        }
      ]
    }
  }
}

GET /recipe/_search
{
  "query": {
    "match": {
      "title": {
        "query": "pasta carbonara",
        "operator": "and"
      }
    }
  }
}

GET /recipe/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "title": "pasta"
          }
        },
        {
          "term": {
            "title": "carbonara"
          }
        }
      ]
    }
  }
}

🌻 クエリのジョイン

🌝 入れ子構造のオブジェクトをクエリする

① 入れ子構造のインデックスのマッピング
PUT /department
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      },
      "employees": {
        "type": "nested" // 🌿 入れ子
      }
    }
  }
}

② 「department」インデックスへ、「employees」データを格納
POST /department/_doc/1
{
  "name": "Development",
  "employees": [
    {
      "name": "Eric Green",
      "age": 39,
      "gender": "M",
      "position": "Software Developer"
    },
    {
      "name": "James Taylor",
      "age": 27,
      "gender": "M",
      "position": "Intern"
    },
    {
      "name": "Gray Jenkins",
      "age": 21,
      "gender": "M",
      "position": "Intern"
    },
    {
      "name": "Julie Powell",
      "age": 26,
      "gender": "F",
      "position": "Intern"
    },
    {
      "name": "Benjamin Smith",
      "age": 46,
      "gender": "M",
      "position": "Senior Software Engineer"
    }
  ]
}
POST /department/_doc/2
{
  "name": "HR & Marketing",
  "employees": [
    {
      "name": "Patricia Lewis",
      "age": 42,
      "gender": "F",
      "position": "Sinir Marketing Manager"
    },
    {
      "name": "Maria Anderson",
      "age": 56,
      "gender": "F",
      "position": "Head of HR"
    },
    {
      "name": "Maargaret Harris",
      "age": 19,
      "gender": "F",
      "position": "Intern"
    },
    {
      "name": "Ryan Nelson",
      "age": 31,
      "gender": "M",
      "position": "Marketing Manager"
    },
    {
      "name": "Kathy Williams",
      "age": 49,
      "gender": "MF",
      "position": "SEM Specialist"
    }
  ]
}

② 入れ子構造の検索
GET /department/_search
{
  "query": {
    "nested": {
      "path": "employees",
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "employees.position": "intern"
              }
            },
            {
              "term": {
                "employees.gender.keyword": {
                  "value": "F"
                }
              }
            }
          ]
        }
      }
    }
  }
}

🌝 入れ子構造の内部検索

GET /department/_search
{
  "_source": "false",      // 🌿 _source
  "query": {
    "nested": {
      "path": "employees",
      "inner_hits": {},    // 🌿 inner_hits
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "employees.position": "intern"
              }
            },
            {
              "term": {
                "employees.gender.keyword": {
                  "value": "F"
                }
              }
            }
          ]
        }
      }
    }
  }
}

🌝 ドキュメント同士の関係性のマッピング - データの準備

DELETE /department

PUT /department
{
"mappings": {
"properties": {
"join_field": {
"type": "join",
"relations": {
"department": "employee"
}
}
}
}
}

🌝 ドキュメントを追加する(上記の続き)- データの準備

PUT /department/_doc/1
{
  "name": "Development",
  "join_field": "department"
}

PUT /department/_doc/2
{
  "name": "Marketing",
  "join_field": "department"
}

PUT /department/_doc/3?routing=1  // 🌿 シャードに認識させる ?routing=1
{
  "name": "Bo Anderson",
  "age": 28,
  "gender": "M",
  "join_field": {
    "name": "employee",
    "parent": 1
  }
}

PUT /department/_doc/4?routing=2
{
  "name": "John Doe",
  "age": 44,
  "gender": "M",
  "join_field": {
    "name": "employee",
    "parent": 2
  }
}

PUT /department/_doc/5?routing=1
{
  "name": "James Evance",
  "age": 32,
  "gender": "M",
  "join_field": {
    "name": "employee",
    "parent": 2
  }
}

PUT /department/_doc/6?routing=1
{
  "name": "Doniel Harris",
  "age": 52,
  "gender": "M",
  "join_field": {
    "name": "employee",
    "parent": 2
  }
}

PUT /department/_doc/7?routing=2
{
  "name": "Jone Park",
  "age": 23,
  "gender": "F",
  "join_field": {
    "name": "employee",
    "parent": 2
  }
}

PUT /department/_doc/8?routing=1
{
  "name": "Christina Parker",
  "age": 29,
  "gender": "F",
  "join_field": {
    "name": "employee",
    "parent": 1
  }
}

🌝 Parent ID で検索する

GET /department/_search
{
  "query": {
    "parent_id": {
      "type": "employee",
      "id": 1
    }
  }
}

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-has-parent-query.html#_sorting_2

GET /department/_search
{
  "query": {
    "has_parent": {
      "parent_type": "department",
      "query": {
        "term": {
          "name.keyword": "Development"
        }
      }
    }
  }
}

GET /department/_search
{
  "query": {
    "has_parent": {
      "parent_type": "department",
      "score": true,  // 🌿 Scoreに影響
      "query": {
        "term": {
          "name.keyword": "Development"
        }
      }
    }
  }
}

🌝 Parent から「Child」をクエリする

GET /department/_search
{
  "query": {
    "has_parent": {
      "parent_type": "department",
      "score": true,
      "query": {
        "term": {
          "name.keyword": "Development"
        }
      }
    }
  }
}

🌝 「Child」ドキュメント から「Parent」をクエリする

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-has-child-query.html#_sorting

GET /department/_search
{
  "query": {
    "has_child": {
      "type": "employee",
      "query": {
        "bool": {
          "must": [
            {
              "range": {
                "age": {
                  "gte": 50
                }
              }
            }
          ],
          "should": [
            {
              "term": {
                "gender.keyword": "M"
              }
            }
          ]
        }
      }
    }
  }
}
Score Mode Explanation
min 「Child」ドキュメントの最低スコアが「Parent」にマッピングされる
max 「Child」ドキュメントの最高スコアが「Parent」にマッピングされる
sum 「Child」スコアのマッチングが、合計され、「Parent」にマッピングされる
avg 「Child」ドキュメントのマッチングに基づく平均スコアが「Parent」にマッピングされる
none 「Child」ドキュメントのスコアは無視される。デフォルトの設定。
GET /department/_search
{
  "query": {
    "has_child": {
      "type": "employee",
      "score_mode": "sum", // 🌿 mode
      "min_children": 2,   // 🌿 min
      "max_children": 5,   // 🌿 max
      "query": {
        "bool": {
          "must": [
            {
              "range": {
                "age": {
                  "gte": 50
                }
              }
            }
          ],
          "should": [
            {
              "term": {
                "gender.keyword": "M"
              }
            }
          ]
        }
      }
    }
  }
}

🌝 多階層のリレーション - multi level relations

PUT /company
{
  "mappings": {
    "properties": {
      "join_field": {
        "type": "join",
        "relations": {
          "company": ["department", "supplier"],
          "department": "employee"
        }
      }
    }
  }
}

PUT /company/_doc/1
{
  "name": "My Comopany Inc.",
  "join_field": "company"
}

PUT /company/_doc/2?routing=1
{
  "name": "development",
  "join_field": {
    "name": "department",
    "parent": 1
  }
}

PUT /company/_doc/3?routing=1
{
  "name": "Bo Andersen",
  "join_field": {
    "name": "employee",
    "parent": 2
  }
}

PUT /company/_doc/4
{
  "name": "Another Comopany Inc.",
  "join_field": "company"
}

PUT /company/_doc/5?routing=4
{
  "name": "Marketing",
  "join_field": {
    "name": "department",
    "parent": 4
  }
}

PUT /company/_doc/6?routing=4
{
  "name": "John Doe",
  "join_field": {
    "name": "employee",
    "parent": 5
  }
}


🌿🌿🌿
GET /company/_search
{
  "query": {
    "has_child": {
      "type": "department",
      "query": {
        "has_child": {
          "type": "employee",
          "query": {
            "term": {
              "name.keyword": "John Doe"
            }
          }
        }
      }
    }
  }
}

🌝 「parent」「child」内部ヒット

GET /department/_search
{
  "query": {
    "has_child": {
      "type": "employee",
      "inner_hits": {},      // 🌿 inner_hits
      "query": {
        "bool": {
          "must": [
            {
              "range": {
                "age": {
                  "gte": 50
                }
              }
            }
          ],
          "should": [
            {
              "term": {
                "gender.keyword": "M"
              }
            }
          ]
        }
      }
    }
  }
}


GET /department/_search
{
  "query": {
    "has_parent": {
      "parent_type": "department",
      "inner_hits": {},
      "query": {
        "term": {
          "name.keyword": "Development"
        }
      }
    }
  }
}

🌝 Lookup メカニズム

👀 https://github.com/codingexplained/complete-guide-to-elasticsearch/blob/master/Joining%20Queries/terms-lookup-mechanism.md

🌝 「Join」の制限
----------------------------------------------------------------------------------------------
・ ドキュメントは、同じインデックスに格納されなければならない。
・ Parent & Child は、同じシャードにいなければならない
・ インデックスにつき一つのJoinフィールド
・ Join フィールドは、好きなだけ持つことができる。
・ インデックスを作成したとは、新しいリレーションを追加する事ができる。
・ Child リレーションは、存在するParentにのみ追加することができる。
・ 親ドキュメントは一つだけ(Childはいくらでも持てる)

🌻クエリのジョイン

👀 https://github.com/codingexplained/complete-guide-to-elasticsearch/tree/master/Controlling%20Query%20Results

🌝 クエリ結果のフォーマット

GET /recipe/_search?format=yaml
{
  "query": {
    "match": {
      "title": "pasta"
    }
  }
}

curl -u elastic:xxxxxx -XGET "https://zzzzzz.asia-northeast1.gcp.cloud.es.io:9243/recipe/_search?format=yaml" -H 'Content-Type: application/json' -d'{  "query": {    "match": {      "title": "pasta"    }  }}'
curl -u elastic:xxxxxx -XGET "https://zzzzzz.asia-northeast1.gcp.cloud.es.io:9243/recipe/_search?pretty" -H 'Content-Type: application/json' -d'{  "query": {    "match": {      "title": "pasta"    }  }}'

🌝 「source」フィルター

GET /recipe/_search
{
  "_source": false, 
  "query": {
    "match": {
      "title": "pasta"
  }
}

GET /recipe/_search
{
  "_source": "created", 
  "query": {
    "match": {
      "title": "pasta"
    }
  }
}

GET /recipe/_search
{
  "_source": "ingredients.name", 
  "query": {
    "match": {
      "title": "pasta"
    }
  }
}

GET /recipe/_search
{
  "_source": ["ingredients.*", "servings"], 
  "query": {
    "match": {
      "title": "pasta"
    }
  }
}

GET /recipe/_search
{
  "_source": {
    "includes": "ingredients.*",
    "excludes": "ingredients.name"
  }, 
  "query": {
    "match": {
      "title": "pasta"
    }
  }
}

🌝 クエリ結果の操作

GET /recipe/_search?size=2
{
  "_source": false, 
  "query": {
    "match": {
      "title": "pasta"
    }
  }
}

GET /recipe/_search
{
  "size": 2,
  "_source": false, 
  "query": {
    "match": {
      "title": "pasta"
    }
  }
}

🌝 オフセット

GET /recipe/_search
{
  "_source": false, 
  "size": 2,
  "from": 2, 
  "query": {
    "match": {
      "title": "pasta"
    }
  }
}

🌝 ページネーション

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-search-after.html

total_pages = ceil ( total_hits / page_size )
from = (page_size * ( page_number - 1 ))

🌝 クエリ結果のソート

GET /recipe/_search
{
  "_source": false, 
  "query": {
    "match_all": {}
  },
  "sort": [
    "preparation_time_minutes"
  ]
}

GET /recipe/_search
{
  "_source": "created", 
  "query": {
    "match_all": {}
  },
  "sort": [
    { "created": "desc" }
  ]
}

GET /recipe/_search
{
  "_source": [ "preparation_time_minutes", "created" ], 
  "query": {
    "match_all": {}
  },
  "sort": [
    { "preparation_time_minutes": "asc" },
    { "created": "desc" }
  ]
}

🌝 「multi-value」フィールドのソート

GET /recipe/_search
{
  "_source": "ratings", 
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "ratings": {
        "order": "desc",
        "mode": "avg"
      }
    }
  ]
}

🌝 フィルター

GET /recipe/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "pasta"
          }
        }
      ],
      "filter": [
        {
          "range": {
            "preparation_time_minutes": {
              "lte": 15
            }
          }
        }
      ]
    }
  }
}

🌻 アグリゲーション

$ curl -u elastic:xxxxxx -H "Content-Type: application/json" -XPOST "https://zzzzzz.asia-northeast1.gcp.cloud.es.io:9243/order/default/_bulk?pretty" --data-binary @orders-bulk.json

GET /order/_mapping

🌝 メトリック・アグリゲーション

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics.html

GET /order/_search
{
  "size": 0,
  "aggs": {
    "total_sales": {
      "sum": {
        "field": "total_amount"
      }
    },
    "avg_sale": {
      "avg": {
        "field": "total_amount"
      }
    },
    "min_sale": {
      "min": {
        "field": "total_amount"
      }
    },
    "max_sale": {
      "max": {
        "field": "total_amount"
      }
    }
  }
}

GET /order/_search
{
  "size": 0,
  "aggs": {
    "total_salesmen": {
      "cardinality": {
        "field": "salesman.id"
      }
    }
  }
}

GET /order/_search
{
  "size": 0,
  "aggs": {
    "value_count": {
      "value_count": {
      "field": "total_amount"
      }
    }
  }
}

GET /order/_search
{
  "size": 0,
  "aggs": {
    "amount_stats": {
      "stats": {
        "field": "total_amount"
      }
    }
  }
}

🌝 バケットアグリゲーション

GET /order/_search
{
  "size": 0,
  "aggs": {
    "status_terms": {
      "terms": {
        "field": "status.keyword"
      }
    }
  }
}

GET /order/_search
{
  "size": 0,
  "aggs": {
    "status_terms": {
      "terms": {
        "field": "status.keyword",
        "missing": "N/A",
        "min_doc_count": 0
      }
    }
  }
}

GET /order/_search
{
  "size": 0,
  "aggs": {
    "status_terms": {
      "terms": {
        "field": "status.keyword",
        "missing": "N/A",
        "min_doc_count": 0,
        "order": {
          "_key": "asc"
        }
      }
    }
  }
}

🌝 ドキュメント・カウントは、おおよその結果

→ ドキュメントがシャードに分散されているから。

🌝 アグリゲーションの入れ子

GET /order/_search
{
  "size": 0,
  "aggs": {
    "status_terms": {
      "terms": {
          "field": "status.keyword"
      },
      "aggs": {
        "status_stats": {
          "stats": {
            "field": "total_amount"
          }
        }
      }
    }
  }
}

🌝 アグリゲーションを使ったドキュメントのフィルタリング

GET /order/_search
{
  "size": 0,
  "aggs": {
    "low_value": {
      "filter": {
        "range": {
          "total_amount": {
            "lt": 100
          }
        }
      },
      "aggs": {
        "avg_amount": {
          "avg": {
            "field": "total_amount"
          }
        }
      }
    }
  }
}

🌝 バケットルールの定義

GET /recipe/_search
{
  "size": 0,
  "aggs": {
    "my_filter": {
      "filters": {
        "filters": {
          "pasta": {
            "match": {
              "title": "pasta"
            }
          },
          "spaghetti": {
            "match": {
              "title": "spaghetti"
            }
          }
        }
      }
    }
  }
}

GET /recipe/_search
{
  "size": 0,
  "aggs": {
    "my_filter": {
      "filters": {
        "filters": {
          "pasta": {
            "match": {
              "title": "pasta"
            }
          },
          "spaghetti": {
            "match": {
              "title": "spaghetti"
            }
          }
        }
      },
      "aggs": {
        "avg_rating": {
          "avg": {
            "field": "ratings"
          }
        }
      }
    }
  }
}

🌝 範囲指定のアグリゲーション

GET /order/_search
{
  "size": 0,
  "aggs": {
    "amount_distribution": {
      "range": {
        "field": "total_amount",
        "ranges": [
          {
            "to": 50
          },
          {
            "from": 50,
            "to": 100
          },
          {
            "from": 100
          }
        ]
      }
    }
  }
}

GET /order/_search
{
  "size": 0,
  "aggs": {
    "purchased_ranges": {
      "date_range": {
        "field": "purchased_at",
        "format": "yyyy-MM-dd",
        "ranges": [
          {
            "from": "2016-01-01",
            "to": "2016-01-01||+6M"
          },
          {
            "from": "2016-01-01||+6M",
            "to": "2016-01-01||+1y"
          }
        ]
      }
    }
  }
}

GET /order/_search
{
  "size": 0,
  "aggs": {
    "purchased_ranges": {
      "date_range": {
        "field": "purchased_at",
        "format": "yyyy-MM-dd",
        "keyed": true, 
        "ranges": [
          {
            "from": "2016-01-01",
            "to": "2016-01-01||+6M",
            "key": "first_half"
          },
          {
            "from": "2016-01-01||+6M",
            "to": "2016-01-01||+1y",
            "key": "second_half"
          }
        ]
      },
      "aggs": {
        "bucket_stats": {
          "stats": {
            "field": "total_amount"
          }
        }
      }
    }
  }
}

🌝 ヒストグラム

GET /order/_search
{
  "size": 0,
  "aggs": {
    "amount_distribution": {
      "histogram": {
        "field": "total_amount",
        "interval": 25
      }
    }
  }
}

GET /order/_search
{
  "size": 0,
  "query": {
    "range": {
      "total_amount": {
        "gte": 100
      }
    }
  }, 
  "aggs": {
    "amount_distribution": {
      "histogram": {
        "field": "total_amount",
        "interval": 25,
        "min_doc_count": 1
      }
    }
  }
}

GET /order/_search
{
  "size": 0,
  "query": {
    "range": {
      "total_amount": {
        "gte": 100
      }
    }
  }, 
  "aggs": {
    "amount_distribution": {
      "histogram": {
        "field": "total_amount",
        "interval": 25,
        "min_doc_count": 1,
        "extended_bounds": {
          "min": 0,
          "max": 500
        }
      }
    }
  }
}

GET /order/_search
{
  "size": 0,
  "aggs": {
    "orders_over_time": {
      "date_histogram": {
        "field": "purchased_at",
        "calendar_interval": "month"
      }
    }
  }
}

🌝 グローバル・アグリゲーション

GET /order/_search
{
  "query": {
    "range": {
      "total_amount": {
        "gte": 100
      }
    }
  },
  "size": 0,
  "aggs": {
    "all_orders": {
      "global": {},
      "aggs": {
        "stats_amount": {
          "stats": {
            "field": "total_amount"
          }
        }
      }
    }
  }
}

GET /order/_search
{
  "query": {
    "range": {
      "total_amount": {
        "gte": 100
      }
    }
  },
  "size": 0,
  "aggs": {
    "stats_expeinsive": {
      "stats": {
        "field": "total_amount"
      }
    },
    "all_orders": {
      "global": {},
      "aggs": {
        "stats_amount": {
          "stats": {
            "field": "total_amount"
          }
        }
      }
    }
  }
}

🌝 ミッシング・フィールド

https://github.com/codingexplained/complete-guide-to-elasticsearch/blob/master/Aggregations/missing-field-values.md

POST /order/1001
{
    "total_amount": 100
}

🌝 入れ子オブジェクトのアグリゲーション

https://github.com/codingexplained/complete-guide-to-elasticsearch/blob/master/Aggregations/nested-aggregations.md

GET /department/_search
{
  "size": 0,
  "aggs": {
    "employees": {
      "nested": {
        "path": "employees"
      }
    }
  }
}

GET /department/_doc/_search
{
  "size": 0,
  "aggs": {
    "employees": {
      "nested": {
        "path": "employees"
      },
      "aggs": {
        "minimum_age": {
          "min": {
            "field": "employees.age"
          }
        }
      }
    }
  }
}

🌻 検索結果の改善

🌝 「Proximity」サーチ

https://github.com/codingexplained/complete-guide-to-elasticsearch/blob/master/Aggregations/nested-aggregations.md

● データ
PUT /proximity/_doc/1
{
  "title": "Spicy Sauce"
}
PUT /proximity/_doc/2
{
  "title": "Spicy Tomato Sauce"
}
PUT /proximity/_doc/3
{
  "title": "Spicy Tomato and Garlic Sauce"
}
PUT /proximity/_doc/4
{
  "title": "Tomato Sauce (spicy)"
}
PUT /proximity/_doc/5
{
  "title": "Spicy and very delicious Tomato Sauce"
}

● クエリ
GET /proximity/_doc/_search
{
  "query": {
    "match_phrase": {
      "title": {
        "query": "spicy sauce",
        "slop": 1       // 🌿 slop
      }
    }
  }
}
GET /proximity/_doc/_search
{
  "query": {
    "match_phrase": {
      "title": {
        "query": "spicy sauce",
        "slop": 2
      }
    }
  }
}

🌿 slop
| | Position 1 | Position 2 | Position 3 |
|:-----------------|------------------:|:------------------:|:------------------:|
| Document | spicy | tomato | sauce |
| Query | spicy | sauce | |
| Slop 1 | spicy | → | sauce |

Position 1 Position 2 Position 3
Document tomato sauce spicy
Query spicy sauce
Slop 1 spicy sauce
Slop 2 sauce → spicy

🌝 「Proximity」による関連スコアの影響

GET /proximity/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": {
              "query": "spicy sauce"
            }
          }
        }
      ]
    }
  }
}
#Boosting relevance based on proximity
GET /proximity/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": {
              "query": "spicy sauce"
            }
          }
        }
      ],
      "should": [
        {
          "match_phrase": {
            "title": {
              "query": "spicy sauce"
            }
          }
        }
      ]
    }
  }
}
# Adding the slop parameter
GET /proximity/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": {
              "query": "spicy sauce"
            }
          }
        }
      ],
      "should": [
        {
          "match_phrase": {
            "title": {
              "query": "spicy sauce",
              "slop": 5
            }
          }
        }
      ]
    }
  }
}

🌝 あいまいマッチングクエリ

AUTOMATIC FUZZINESS

Term Length Maximum Edit Distance
1-2 0
3-5 1
>5 2

# Searching with fuzziness set to auto
GET /product/_search
{
  "query": {
    "match": {
      "name": {
        "query": "l0bster",
        "fuzziness": "auto"
      }
    }
  }
}
GET /product/_search
{
  "query": {
    "match": {
      "name": {
        "query": "lobster",
        "fuzziness": "auto"
      }
    }
  }
}

# Fuzziness is per term (and specifying an integer)
GET /product/_search
{
  "query": {
    "match": {
      "name": {
        "query": "l0bster love",
        "operator": "and",
        "fuzziness": 1
      }
    }
  }
}

# Switching letters around with transpositions (AB ⇔ BA)
GET /product/_search
{
  "query": {
    "match": {
      "name": {
        "query": "lvie",
        "fuzziness": 1
      }
    }
  }
}

# Disabling transpositions
GET /product/_search
{
  "query": {
    "match": {
      "name": {
        "query": "lvie",
        "fuzziness": 1,
        "fuzzy_transpositions": false
      }
    }
  }
}

🌝 あいまいクエリ ( Term レベルクエリ )

・ アナライザーは起動しないやり方

GET /product/_search
{
  "query": {
    "fuzzy": {
      "name": {
        "value": "LOBSTER",
        "fuzziness": "auto"
      }
    }
  }
}
GET /product/_search
{
  "query": {
    "fuzzy": {
      "name": {
        "value": "lobster",
        "fuzziness": "auto"
      }
    }
  }
}

🌝 シノニム (類義語)

PUT /synonyms
{
  "settings": {
    "analysis": {
      "filter": {
        "synonym_test": {
          "type": "synonym", 
          "synonyms": [
            "awful => terrible",
            "awesome => great, super",
            "elasticsearch, logstash, kibana => elk",
            "weird, strange"
          ]
        }
      },
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "synonym_test"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "description": {
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}

# Testing the analyzer (with synonyms)
POST /synonyms/_analyze
{
  "analyzer": "my_analyzer",
  "text": "awesome"
}
POST /synonyms/_analyze
{
  "analyzer": "my_analyzer",
  "text": "Elasticsearch"
}
POST /synonyms/_analyze
{
  "analyzer": "my_analyzer",
  "text": "weird"
}
POST /synonyms/_analyze
{
  "analyzer": "my_analyzer",
  "text": "Elasticsearch is awesome, but can also seem weird sometimes."
}

# Adding a test document
POST /synonyms/_doc
{
  "description": "Elasticsearch is awesome, but can also seem weird sometimes."
}

# Searching the index for synonyms
GET /synonyms/_doc/_search
{
  "query": {
    "match": {
      "description": "great"
    }
  }
}
GET /synonyms/_doc/_search
{
  "query": {
    "match": {
      "description": "awesome"
    }
  }
}

🌝 ファイルからシノニムを追加する

Adding index with custom analyzer
PUT /synonyms
{
  "settings": {
    "analysis": {
      "filter": {
        "synonym_test": {
          "type": "synonym",
          "synonyms_path": "analysis/synonyms.txt" // 🌿 ファイルから設定
        }
      },
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "synonym_test"
          ]
        }
      }
    }
  },
  "mappings": {
    "_doc": {
      "properties": {
        "description": {
          "type": "text",
          "analyzer": "my_analyzer"
        }
      }
    }
  }
}


# Synonyms file (config/analysis/synonyms.txt)
----------------------------------------------
# This is a comment
awful => terrible
awesome => great, super
elasticsearch, logstash, kibana => elk
weird, strange
----------------------------------------------



#Testing the analyzer
POST /synonyms/_analyze
{
  "analyzer": "my_analyzer",
  "text": "Elasticsearch"
}

🌝 マッチングのハイライト

👀 https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-highlighting

POST /highlighting/_doc/1
{
  "description": "Let me tell you a story about Elasticsearch. It's a full-text search engine that is built on Apache Lucene. It's really easy to use, but also packs lots of advanced features that you can use to tweak its searching capabilities. Lots of well-known and established companies use Elasticsearch, and so should you!"
}

# Highlighting matches within the description field
GET /highlighting/_search
{
  "_source": false,
  "query": {
    "match": { "description": "Elasticsearch story" }
  },
  "highlight": {
    "fields": {
      "description" : {}
    }
  }
}

# Specifying a custom tag
GET /highlighting/_search
{
  "_source": false,
  "query": {
    "match": { "description": "Elasticsearch story" }
  },
  "highlight": {
    "pre_tags": [ "<strong>" ],
    "post_tags": [ "</strong>" ],
    "fields": {
      "description" : {}
    }
  }
}

🌝 ステミング - stemming

# Creating a test index
PUT /stemming_test
{
  "settings": {
    "analysis": {
      "filter": {
        "synonym_test": {
          "type": "synonym",
          "synonyms": [
            "firm => company",
            "love, enjoy"
          ]
        },
        "stemmer_test" : {
          "type" : "stemmer",
          "name" : "english"
        }
      },
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "synonym_test",
            "stemmer_test"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "description": {
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}

# Adding a test document
POST /stemming_test/_doc/1
{
  "description": "I love working for my firm!"
}

# Matching the document with the base word (work)
GET /stemming_test/_search
{
  "query": {
    "match": {
      "description": "enjoy work"
    }
  }
}

# The query is stemmed, so the document still matches
GET /stemming_test/_search
{
  "query": {
    "match": {
      "description": "love working"
    }
  }
}

# Synonyms and stemmed words are still highlighted
GET /stemming_test/_doc/_search
{
  "query": {
    "match": {
      "description": "enjoy work"
    }
  },
  "highlight": {
    "fields": {
      "description": {}
    }
  }
}
50
50
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
50
50