Elasticsearch 6.3.0 リリース記念　X-Pack SQL Accessをやってみよう

# 概要

Elasticsearchに検索条件を投げるとき、Elasticsearchのクエリを組み立てる必要がありました。
このクエリを（みんなが書き慣れてるだろう）SQLで書けないか、というような位置づけなんだろうと思いますが、
SQLをクエリとして発行したり、Elasticsearchのクエリに変換できる機能が6.3.0でX-Pack）に入りました。

それを試してみます。

# 環境

|product| version |
|----|---|
| Elasticsearch | 6.3.0 |
| Kibana | 6.3.0 |

Dockerで起動しますと、X-Pack(Basic)で使えるモジュールが入っているので、それを使うこととします。
起動時のログでsqlのモジュールがロードされているのが分かります。

![kidou.png](https://qiita-image-store.s3.amazonaws.com/0/111676/ba62e878-c506-b234-e629-337ffd9aa8c6.png)


# テストデータ

## 日経225

数値やMAXなど集計関数を試す用。　stockというindexに入れます。

|項目| フィールド |
|----|---|
| 始値 | start_value |
| 終値 | end_value |
| 安値 | low_value |
| 高値 | high_value |
| 日付 | timestamp |

日経225のデータを突っ込んでます。

## テキストデータ

文字列の一致 like や　= 　を試す用。　texttestというindexに入れます。

```
PUT texttest/doc/1
{
 "content": "I have a pen."
}
```

|項目| フィールド |
|----|---|
| テキスト | content |

# いざ確認

マニュアルはこちら
https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-getting-started.html

## SQL REST API

### 1. format txt

SQL REST APIでは、SQLクエリを発行すると結果が得られます。

![f1a15b12-483a-48d9-afb6-15d7e22a5fdc.png](https://qiita-image-store.s3.amazonaws.com/0/111676/8c1189c9-8075-6cd3-407a-103bc99c10d3.png)

```
POST /_xpack/sql?format=txt
{
  "query": "SELECT * FROM stock order by timestamp desc limit 5"
}

```

のようなクエリを発行すると、結果がテキスト形式でテーブルが返ってきます。

```
   end_value   |  high_value   |   low_value   |  start_value  |       timestamp        
---------------+---------------+---------------+---------------+------------------------
22966.380859375|22993.259765625|22895.310546875|22896.169921875|2018-06-13T00:00:00.000Z
22878.349609375|23011.5703125  |22797.73046875 |22977.220703125|2018-06-12T00:00:00.000Z
22804.0390625  |22856.080078125|22667.30078125 |22686.94921875 |2018-06-11T00:00:00.000Z
22694.5        |22879.0        |22694.5        |22799.380859375|2018-06-08T00:00:00.000Z
22823.259765625|22856.369140625|22732.1796875  |22748.720703125|2018-06-07T00:00:00.000Z

```

### 2. format json

フォーマットをjsonにしてみましょう。

![32844254-77b9-4b9a-aa93-a2b543cc72d6.png](https://qiita-image-store.s3.amazonaws.com/0/111676/9cd195aa-1291-e366-9796-7bd290ecfea6.png)

```
POST /_xpack/sql?format=json
{
  "query": "SELECT * FROM stock order by timestamp desc limit 5"
}
```

結果がjsonで返されますが、従来のElasticsearchのクエリを発行したときの結果のjsonとは違うので、そこは注意が必要です。

SQLでよくある while (結果が無くなるまで) みたいなカーソルを使ったアクセスだと、fetch_sizeを指定すると良いです。


![b6a4e72f-8157-49c4-a336-fae03e2f71b3.png](https://qiita-image-store.s3.amazonaws.com/0/111676/47db69b6-06de-a00c-f791-f0f8a6baf88c.png)

```
POST /_xpack/sql?format=json
{
  "query": "SELECT * FROM stock order by timestamp desc",
  "fetch_size": 1
}

```


### 3. format csv

フォーマットをcsvにしてみましょう。

![ca8d7c4c-7ee1-46b9-9007-11306bde569b.png](https://qiita-image-store.s3.amazonaws.com/0/111676/c42ef631-75b3-b5f1-17e0-bf1d9af55e51.png)

cliからも同様のことができるので、Excelでｸﾚ！というような方にcsvをさっと渡すときなどに向いてそうです。

### 4. csvの列名をasで変えてみる

```
POST /_xpack/sql?format=csv
{
  "query": """SELECT 
     high_value as hoge,
     low_value as bar,
     start_value as piyo,
     end_value as fuga
     FROM stock order by timestamp desc
  """,
  "fetch_size": 1
}

```

![f60ac7f6-dd65-4746-8631-2293f4c99a17.png](https://qiita-image-store.s3.amazonaws.com/0/111676/3a0b8fce-5d7b-057d-10a9-7f31736ffc90.png)

asも有効に効いて、列名が変わっていることが確認できました。

### 5. 集計関数 MAX, MIN, AVG

よく使うMAX, MIN, AVGなどの関数も、もちろん使えます。

![9cac4ad8-499e-4e89-8d57-a986283e8f4e.png](https://qiita-image-store.s3.amazonaws.com/0/111676/cef390c3-308b-dcfd-b1ac-2a2ee92186c9.png)

```
POST /_xpack/sql?format=json
{
  "query": "SELECT MAX(high_value), MIN(low_value), AVG(start_value) FROM stock "  
}

```

うっかり、ExcelのつもりでAVGをAverageなんたら？？？　そんな関数はない、とエラーが返ってきます。


```json
{
  "error": {
    "root_cause": [
      {
        "type": "verification_exception",
        "reason": "Found 1 problem(s)\nline 1:41: Unknown function [AVERAGE]"
      }
    ],
    "type": "verification_exception",
    "reason": "Found 1 problem(s)\nline 1:41: Unknown function [AVERAGE]"
  },
  "status": 400
}
```

たとえば、AVGを間違えて、AVEなんてしてしまった場合は、もっと親切です。
ちょっとした優しさが身に沁みます。

```
{
  "error": {
    "root_cause": [
      {
        "type": "verification_exception",
        "reason": "Found 1 problem(s)\nline 1:41: Unknown function [AVE], did you mean [AVG]?"
      }
    ],
    "type": "verification_exception",
    "reason": "Found 1 problem(s)\nline 1:41: Unknown function [AVE], did you mean [AVG]?"
  },
  "status": 400
}
```
### 6. = とか like

文字列の場合は、SQLのときと同様シングルクオーテーションで囲むのを忘れないようにしましょう。

```
POST /_xpack/sql?format=json
{
  "query": "SELECT * from texttest WHERE content = 'I have a pen.'"  
}
```

```
POST /_xpack/sql?format=json
{
  "query": "SELECT * from texttest WHERE content like  'I have %'"  
}
```

![b663ef42-b1e5-469e-b965-502ecaa11947.png](https://qiita-image-store.s3.amazonaws.com/0/111676/3271f1e5-c178-eb8c-9eac-499f139a8791.png)

もし、''で囲まなかったらこういうエラーが出ます。

```json
{
  "error": {
    "root_cause": [
      {
        "type": "parsing_exception",
        "reason": "line 1:44: mismatched input ''' expecting {'?', STRING}"
      }
    ],
    "type": "parsing_exception",
    "reason": "line 1:44: mismatched input ''' expecting {'?', STRING}",
    "caused_by": {
      "type": "input_mismatch_exception",
      "reason": null
    }
  },
  "status": 400
}
```

likeで結果が0件になるようなクエリを投げてみたところ、結果１件返ってきてしまいました。

```
POST /_xpack/sql?format=json
{
  "query": "SELECT * from texttest WHERE content like  'I have % a book.'"
}

```

![16ee2d3e-b084-4a75-9db0-e61968af2d2c.png](https://qiita-image-store.s3.amazonaws.com/0/111676/c84fd99f-9617-ed41-30e2-e92babcb8de6.png)


あれ？　mappingがよくなかった？　クエリ書き間違えた？　SQLだとどういうクエリを実際に投げてんの？　という疑問がここで生まれます。
そこで、Translate APIです。

## SQL Translate API

SQLをElasticsearchのクエリに変換してくれるAPIです。
実際にどういうクエリになっているかが見ることができれば、期待する検索結果にならなかったときなどの調査に役立つこともあるでしょう。

```
POST /_xpack/sql/translate
{
  "query": "SELECT * from texttest WHERE content like  'I have % a book.'"
}
```

```json

{
  "size": 1000,
  "query": {
    "query_string": {
      "query": "I have * a book.",
      "fields": [
        "content^1.0"
      ],
      "type": "best_fields",
      "default_operator": "or",
      "max_determinized_states": 10000,
      "enable_position_increments": true,
      "fuzziness": "AUTO",
      "fuzzy_prefix_length": 0,
      "fuzzy_max_expansions": 50,
      "phrase_slop": 0,
      "escape": false,
      "auto_generate_synonyms_phrase_query": true,
      "fuzzy_transpositions": true,
      "boost": 1
    }
  },
  "_source": {
    "includes": [
      "content"
    ],
    "excludes": []
  },
  "sort": [
    {
      "_doc": {
        "order": "asc"
      }
    }
  ]
}
```

![637729b4-20cc-4d11-8210-5af938ef1c39.png](https://qiita-image-store.s3.amazonaws.com/0/111676/d1aaedfb-687e-56ad-9ac6-75131012061b.png)

 
馴染みのクエリがでてきましたね！
今回のは default_operatorがORになっていたことが良くなかったんだ！　ということに、ここで気づくことができます。

返されたjsonを、searchのクエリにそのまま書くと、いつもの形式で結果がとれます。

![998b3e43-9266-4eee-843f-d2863ebcc0da.png](https://qiita-image-store.s3.amazonaws.com/0/111676/fd29cade-ade2-c165-d146-a4693da7d87b.png)

SQLでのクエリを書いた際のデバッグ用途に活躍しそうです。



## その他　遭遇したエラーや試したこと

### FROM違い

FROMの後は、indexを指定します。ないindexを指定すると、エラーとなります。

```
POST /_xpack/sql/translate
{
  "query": "SELECT * from hogehoge"
}
```

```
{
  "error": {
    "root_cause": [
      {
        "type": "verification_exception",
        "reason": "Found 1 problem(s)\nline 1:15: Unknown index [hogehoge]"
      }
    ],
    "type": "verification_exception",
    "reason": "Found 1 problem(s)\nline 1:15: Unknown index [hogehoge]"
  },
  "status": 400
}
```


### JOINしてみる

```
POST /_xpack/sql/translate
{
  "query": "SELECT * from stock as a LEFT OUTER JOIN stock as s ON  a.timestamp = s.timestamp"
}
```

```
{
  "error": {
    "root_cause": [
      {
        "type": "parsing_exception",
        "reason": "line 1:27: Queries with JOIN are not yet supported"
      }
    ],
    "type": "parsing_exception",
    "reason": "line 1:27: Queries with JOIN are not yet supported"
  },
  "status": 400
}
```

**JOIN are not yet supported** ということは、そのうちサポートされるのか！？　と期待は膨らみますが、現時点では未サポートのようです。


### SQLコメント

-- 文字列　の形式で書かれるSQLコメントはエラーとなりません。

```
POST /_xpack/sql?format=csv
{
  "query": """SELECT 
     high_value as hoge,  -- hogeです
     low_value as bar,    -- barです
     start_value as piyo, -- piyoです
     end_value as fuga    -- 以下ry
     FROM stock order by timestamp desc
  """,
  "fetch_size": 1
}

```

![54c6d624-bb94-4e44-aa54-612057e6c578.png](https://qiita-image-store.s3.amazonaws.com/0/111676/220f2368-d978-56a2-7f30-09177e95152d.png)


# その他

SQL Accessが、どのサブスクリプションでどこまで使えるのか？　は、以下をご確認ください。
https://www.elastic.co/jp/subscriptions



# まとめに代えての雑感

Elasticsearchのクエリ、ましてやLuceneのクエリは分からないが、SQLなら分かる！　という数は一定数いると思いますが、
そういった方でも使えるようになる、というのは裾野が広がって良いと思います。

また上位のサブスクリプションで有効なJDBC Clientも、いろんなものとのデータ連携を考えると使えると喜ぶ人が多そうですね。
6.3では他にもいろいろな改良が入っているようなので、おいおい試していきたいと思います。

最近、エラーといえばスタックトレースしか出ないような製品に囲まれていたので、エラー内容がちゃんと返ってくる、
しかもこういう間違いではないです？と指摘してくれる機能に触れただけでも、心が和みます。ありがとうございました。