LoginSignup
0
0

More than 3 years have passed since last update.

Elasticsearch 7.2.1 で join datatype を使って親子関係を実現した際に、子データが無い親を検索する

Posted at

Elasticsearch で親子関係のデータをインデクシングした際に、子データが無い親のデータを検索したい時があります。

手元の環境は以下になります。

  • Ubuntu 18.04 LTS
  • Elasticsearch 7.2.1

最初にインデックスの設定とマッピングを定義します。( こちらの記事と同じものです。)

次に、以下のようなデータで json ファイルを作成します。

{"index": {"_index": "shop", "_type": "_doc","_id": "goods_id.1"}}
{"type": "goods","name": "三岳","text": "もののけ姫のモデルにもなった、鹿児島県屋久島の焼酎です。屋久島は水が美味しく、その水を使って作られています。ロックで飲むことをオススメします。","my_join_field": "goods"}
{"index": {"_index": "shop", "_type": "_doc","_id": "review_id.1", "routing": "goods_id.1"}}
{"type": "review","user": "nettle0010","text": "何度も購入させていただいてます。以前はレア物でなかなか飲めませんでしたが、こちらで購入できるようになって晩酌で楽しんでます。","my_join_field": {"name": "review", "parent": "goods_id.1"}}
{"index": {"_index": "shop", "_type": "_doc","_id": "review_id.2", "routing": "goods_id.1"}}
{"type": "review","user": "udfhsudadb","text": "注文してから2〜3日で届きました。ありがとうございます!","my_join_field": {"name": "review", "parent": "goods_id.1"}}
{"index": {"_index": "shop", "_type": "_doc","_id": "goods_id.2"}}
{"type": "goods","name": "屋久の島","text": "もののけ姫のモデルにもなった、鹿児島県屋久島の焼酎です。三岳と違ってあまりメジャーではありませんが、なかなか美味しいです。ロックがオススメです。","my_join_field": "goods"}
{"index": {"_index": "shop", "_type": "_doc","_id": "review_id.3", "routing": "goods_id.2"}}
{"type": "review","user": "okodshywegfej","text": "昨年購入しました。両親からも喜ばれています。","my_join_field": {"name": "review", "parent": "goods_id.2"}}
{"index": {"_index": "shop", "_type": "_doc","_id": "goods_id.3"}}
{"type": "goods","name": "原酒三岳","text": "もののけ姫のモデルにもなった、鹿児島県屋久島の焼酎です。三岳の原酒です。ロックがオススメです。","my_join_field": "goods"}

そして、json ファイルを使ってインデクシングします。

$ curl -H "Content-type: application/x-ndjson" -X POST http://localhost:9200/_bulk?refresh=false --data-binary @request_bulk.json | jq
$ curl -X POST 'localhost:9200/shop/_refresh' | jq

子データが2つのもの、1つのもの、ひとつも無いもの、の3つの親データを作成しています。

子データが無い親を検索してみます。

$ curl -X POST 'localhost:9200/shop/_search' -H 'Content-Type: application/json' -d'
{
    "from": 0,
    "size": 10,
    "query": {
        "bool": {
            "filter": [
                { "term": { "type": "goods" } }
            ],
            "must_not": {
                "has_child": {
                    "type": "review",
                    "query": {
                        "match_all": {}
                    }
                }
            }
        }
    }
}
' | jq

以下のような結果が返ってきます。

{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 0,
    "hits": [
      {
        "_index": "shop",
        "_type": "_doc",
        "_id": "goods_id.3",
        "_score": 0,
        "_source": {
          "type": "goods",
          "name": "原酒三岳",
          "text": "もののけ姫のモデルにもなった、鹿児島県屋久島の焼酎です。三岳の原酒です。ロックがオススメです。",
          "my_join_field": "goods"
        }
      }
    ]
  }
}

ポイントは以下になります。

  • must_not に全件取得の has_child を指定する。

なお、以下のようなクエリを実行すると0件も含めた親ごとの子データの数を取得できます。

$ curl -X POST 'localhost:9200/shop/_search' -H 'Content-Type: application/json' -d'
{
    "from": 0,
    "size": 10,
    "query": {
        "bool": {
            "should": [
                {
                    "has_child": {
                        "type": "review",
                        "score_mode": "sum",
                        "query": {
                            "match_all": {}
                        }
                    }
                },
                {
                    "bool": {
                        "filter": [
                            { "term": { "type": "goods" } }
                        ],
                        "must_not": {
                            "has_child": {
                                "type": "review",
                                "score_mode": "sum",
                                "query": {
                                    "match_all": {}
                                }
                            }
                        }
                    }
                }
            ]
        }
    }
}
' | jq

以下のような結果が返ってきます。

{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 3,
      "relation": "eq"
    },
    "max_score": 2,
    "hits": [
      {
        "_index": "shop",
        "_type": "_doc",
        "_id": "goods_id.1",
        "_score": 2,
        "_source": {
          "type": "goods",
          "name": "三岳",
          "text": "もののけ姫のモデルにもなった、鹿児島県屋久島の焼酎です。屋久島は水が美味しく、その水を使って作られています。ロックで飲むことをオススメします。",
          "my_join_field": "goods"
        }
      },
      {
        "_index": "shop",
        "_type": "_doc",
        "_id": "goods_id.2",
        "_score": 1,
        "_source": {
          "type": "goods",
          "name": "屋久の島",
          "text": "もののけ姫のモデルにもなった、鹿児島県屋久島の焼酎です。三岳と違ってあまりメジャーではありませんが、なかなか美味しいです。ロックがオススメです。",
          "my_join_field": "goods"
        }
      },
      {
        "_index": "shop",
        "_type": "_doc",
        "_id": "goods_id.3",
        "_score": 0,
        "_source": {
          "type": "goods",
          "name": "原酒三岳",
          "text": "もののけ姫のモデルにもなった、鹿児島県屋久島の焼酎です。三岳の原酒です。ロックがオススメです。",
          "my_join_field": "goods"
        }
      }
    ]
  }
}

ポイントは以下になります。

  • score_mode に sum を指定する。(全件取得のクエリなので、それぞれの子データのスコアが 1 になり、sum を指定することで結果的に子データの数になる)

参考になった記事

Has child query
Elasticsearch get all parents with no children
How to sort parents by number of children in Elasticsearch

0
0
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
0
0