Azure Cognitive Search - BlobストレージのJSONファイルに対するクエリ



# 目的

Azure Cognitive Searchを使用して、Azure Blob Storage内のJSONファイルにクエリを行う

# 概要

![image-20210816163959140.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/2f1ce7cf-4f65-f9a0-62d5-857fd22698f9.png)

主な構成要素： **データソース**、**インデクサー**、**インデックス**

データソースに対してインデクサーでクロールを行い、インデックスを生成する
生成されたインデックスに対してクエリを行う

![image-20210816192816635.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/a479acfb-278b-747a-71b8-b7a9580740e7.png)


# JSONデータの準備

以下のJSONデータをAzure Blob Storageに格納する

```json:test-data.json
[
  {
    "id": 1,
    "category": "category1",
    "family": "AndersenFamily",
    "lastName": "Andersen",
    "address": {
      "state": "WA",
      "county": "King",
      "city": "Seattle"
    },
    "creationDate": 1431620472,
    "timestamp": "2021-08-13T09:01:19.443224Z",
    "isRegistered": true
  },
  {
    "id": 2,
    "category": "category2",
    "family": "WakefieldFamily",
    "lastName": "Wakefield",
    "address": {
      "state": "NY",
      "county": "Manhattan",
      "city": "NY"
    },
    "creationDate": 1431610472,
    "timestamp": "2021-08-11T09:24:20.235524Z",
    "isRegistered": false
  },
  {
    "id": 3,
    "category": "category1",
    "family": "MillerFamily",
    "lastName": "Miller",
    "address": {
      "state": "WA",
      "county": "King",
      "city": "Seattle"
    },
    "creationDate": 1431600472,
    "timestamp": "2021-05-07T12:22:19.123456Z",
    "isRegistered": true
  }
]
```

コンテナ： search-source
ディレクトリ： Directory-1
ファイル： test-data.json

![image-20210816100841085.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/bf9f0a12-12fe-862f-fd6e-bed484ff76f9.png)


# データのインポート

[公式ドキュメント](https://docs.microsoft.com/ja-jp/azure/search/search-get-started-portal)

データのインポートからデータソース、インデックス、インデクサーの設定を行う

![image-20210816171707243.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/c991ae12-e674-37c4-c7ae-dd336db12921.png)


## データソース

データのインポート元（Azure Blob Storage）の情報を入力する

![image-20210815142632541.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/bdf41a17-e087-3511-1cca-c2f95718b0e6.png)


## インデックス

データソース内に格納したJSONデータの各フィールドに対して、
検索結果に含めるか、フィルター可能、検索可能等のオプションを設定する

![image-20210816101322324.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/306f5988-a2a4-101c-0f2d-fe4371250a71.png)


## インデクサー

データソースにクロールするスケジュール設定を行う

![image-20210815145009012.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/a78589d4-2321-518e-983e-56b29946f3c5.png)


ちなみにスケジュールの最短周期は5分

![image-20210816165601772.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/9263e204-9cfc-0e3b-c1f0-bfac565dc2c6.png)


クロールが完了するとデータソースに含まれるドキュメント数等が表示される

![image-20210816173100161.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/811136a9-403b-d089-da9b-194c4e2f1344.png)


生成されるインデックスを格納するストレージサイズはエディションによって異なる
[Azure Cognitive Search の価格](https://azure.microsoft.com/ja-jp/pricing/details/search/)

![image-20210816193148712.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/03600995-b4b7-56f5-3ce0-06bee620f76a.png)


# Azure Portalでのクエリ

![image-20210816173324014.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/efd9fca7-c66d-bae4-ebbf-26d991003e7d.png)


クエリ文字列を空の状態で検索ボタンをクリックすると結果に全データが返却される
ただし、インデックスの設定時に**取得可能**にチェックを入れなかった`creationDate`等のフィールドは含まれない

![image-20210815145521501.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/52fc4457-3507-53b5-ed52-954a665ac84d.png)


## 文字列検索

クエリ文字列に`search=category2`と入力し検索ボタンをクリックすると結果に`category2`が含まれるドキュメントのみが返却される
ただし、インデックスの設定時に**検索可能**にチェックを入れたフィールドのみが検索対象

![image-20210815145558636.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/726a8b3b-aeb1-7383-d371-f70f4e59d799.png)


## datetimeのフィルター

クエリ文字列に`$filter=timestamp ge 2021-08-01T00:00:00Z and timestamp le 2021-08-12T00:00:00Z`と入力し検索ボタンをクリックすると結果に`timestamp`が`2021-08-01T00:00:00Z`以上`2021-08-12T00:00:00Z`以下のドキュメントのみが返却される
filter構文については以下参照
[Azure Cognitive Search での OData $filter 構文](https://docs.microsoft.com/ja-jp/azure/search/search-query-odata-filter)
[Azure Cognitive Search の OData 比較演算子 - `eq`、`ne`、`gt`、`lt`、`ge`、`le`](https://docs.microsoft.com/ja-jp/azure/search/search-query-odata-comparison-operators)

ただし、インデックスの設定時に**フィルター可能**にチェックを入れたフィールドのみがフィルター対象

![image-20210816175232931.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/f8dc3acc-8934-634a-7556-a6999138447b.png)


# インデクサーの即時実行

データソースに追加されたデータを読み込むためには、インデクサーのスケジュール実行または、手動実行を行う

![image-20210815150452167.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/fe344fce-4a69-6388-0ee7-3cbc5a6708f7.png)


![image-20210816174506400.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/32fe0601-e1db-f454-4181-215096f65647.png)


ただし、手動による即時実行は180秒以上間隔をあける必要がある

![image-20210816111113595.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/919fffb4-5ed3-53cf-d38a-d4d15b37eb5a.png)


# Azure Functions（Python）でのクエリ

[公式ドキュメント](https://docs.microsoft.com/ja-jp/azure/search/search-get-started-python)

Azure PortalからAzure Cognitive SearchのエンドポイントURLと、キーを取得する

![image-20210816195329759.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/9d4d78f8-0fe2-96bd-d795-8bfdd6173d33.png)


```python
import os
import logging
import azure.functions as func
from azure.core.credentials import AzureKeyCredential
from azure.search.documents import SearchClient

def main(req: func.HttpRequest) -> func.HttpResponse:
  logging.info('Python HTTP trigger function processed a request.')

  endpoint = os.environ['COGNITIVE_SEARCH_ENDPOINT']
  admin_key = os.environ['COGNITIVE_SEARCH_ADMIN_KEY']
  index_name = 'azureblob-index'

  try:
    search_client = SearchClient(endpoint=endpoint,
                          index_name=index_name,
                          credential=AzureKeyCredential(admin_key))
  except Exception as err:
    logging.info(f'Connect Error: {err}')
    return func.HttpResponse(status_code=500)

  try:
    # search=*
    logging.info('search=*')
    results =  search_client.search(search_text='*', include_total_count=True)
    logging.info(f'Number of data: {results.get_count()}')
    for result in results:
      logging.info(result)

    # search=category1
    logging.info('search=category1')
    results =  search_client.search(search_text='category1', include_total_count=True)
    logging.info(f'Number of data: {results.get_count()}')
    for result in results:
      logging.info(result)

    # $filter=timestamp ge 2021-08-01T00:00:00Z
    logging.info('$filter=timestamp ge 2021-08-01T00:00:00Z')
    results =  search_client.search(search_text='*', filter='timestamp ge 2021-08-01T00:00:00Z', include_total_count=True)
    logging.info(f'Number of data: {results.get_count()}')
    for result in results:
      logging.info(result)

    # $filter=timestamp ge 2021-08-01T00:00:00Z and timestamp le 2021-08-12T00:00:00Z
    logging.info('$filter=timestamp ge 2021-08-01T00:00:00Z and timestamp le 2021-08-12T00:00:00Z')
    results =  search_client.search(search_text='*', filter='timestamp ge 2021-08-01T00:00:00Z and timestamp le 2021-08-12T00:00:00Z', include_total_count=True)
    logging.info(f'Number of data: {results.get_count()}')
    for result in results:
      logging.info(result)

  except Exception as err:
    logging.info(f'Search Error: {err}')
    return func.HttpResponse(status_code=500)

  return func.HttpResponse(
        'This HTTP triggered function executed successfully.',
        status_code=200
  )
```

`search_text`は必須パラメータのため検索が不要な場合でも`'*'`を指定している

使用クラス：[SearchClient.search](https://docs.microsoft.com/ja-jp/python/api/azure-search-documents/azure.search.documents.searchclient?view=azure-python#search-search-text----kwargs-)

[SearchIndexerClient.run_indexer](https://docs.microsoft.com/en-us/python/api/azure-search-documents/azure.search.documents.indexes.searchindexerclient?view=azure-python#run-indexer-name----kwargs-)を使用してインデクサーの実行が可能だが、
即時クロールが完了するわけではないためクエリ前に実行してもクロール結果は反映されない