【Elasticsearch&Python】AND検索とOR検索を組み合わせた複雑な検索クエリの書き方

ElasticsearchでAND検索かつ、一部をOR検索にする。という複雑な検索クエリを書いたがちょっと書きにくかったので備忘録。

## データ例
名前|カナ|メールアドレス|部署|役職
-|-|-|-|-
佐藤一郎|サトウイチロウ|i.sato@xxx.com|総務部|部長
田中二郎|タナカジロウ|j.tanaka@xxx.com|総務部|室長
中村三郎|ナカムラサブロウ|s.nakamura@xxx.com|営業部|部長
鈴木雄太|スズキユウタ|y.suzuki@xxx.com|経理部|-
佐藤花子|サトウハナコ|h.sato@xxx.com|経理部|-
山田太郎|ヤマダタロウ|t.yamada@xxx.com|管理本部|本部長


## 条件詳細
例：名前が佐藤かつ、部署が総務部または経理部のユーザーを検索

上記のようにユーザー検索にて、名前、カナ、メールアドレス、部署、役職のAND検索に加え、部署と役職については複数項目でのOR検索をする。
また、名前は漢字、カナの両方で検索が入るようにしたい。ワタナベさんとかサイトウさんとか、漢字が複数ある方もいらっしゃいますからね、世の中。


## クエリ
mustがAND検索、shouldがOR検索
shouldの場合は`minimum_should_match`を設定する（最低n件一致しているか）

クエリの例（部署と役職は例として2件で検索する想定）
```
{
    "query": {
        "bool": {
            "must": [
                {
                    "bool": {
                        "should": [
                            "wildcard": {
                                "name.keyword": {"value": "*検索ワード*"}
                            },
                            "wildcard": {
                                "kana.keyword": {"value": "*検索ワード*"}
                            }
                        ]
                    }
                },
                {
                    "wildcard": {
                        "mail.keyword": {"value": "*検索ワード*"}
                    }
                },
                {
                    "bool": {
                        "should": [
                            "wildcard": {
                                "department.keyword": {"value": "*検索ワード*"}
                            },
                            "wildcard": {
                                "department.keyword": {"value": "*検索ワード*"}
                            }
                        ],
                        "minimum_should_match": 1
                    }
                },
                {
                    "bool": {
                        "should": [
                            "wildcard": {
                                "title.keyword": {"value": "*検索ワード*"}
                            },
                            "wildcard": {
                                "title.keyword": {"value": "*検索ワード*"}
                            }
                        ],
                        "minimum_should_match": 1
                    }
                },
            ]
        }
    }
}
```

## pythonで関数にする
上記クエリをpythonで関数化すると以下のようになる。

```python
# userインデックスのmanager.pyを想定

def search_user(self, name: str='', mail: str='', dep_list: List[str] = [], title_list: List[str] = []) -> list:
    # 空のクエリを作成
    query = {"query": {"bool": {}}}
    add_query = []

    # mustに入る中身を作成していく
    if name:
        add_query.append({
                    "bool": {
                        "should": [
                            "wildcard": {
                                "name.keyword": {"value": f"*{name}*"}
                            },
                            "wildcard": {
                                "kana.keyword": {"value": f"*{name}*"}
                            }
                        ]
                    }
                })
    if mail:
        add_query.append({
                    "wildcard": {
                        "mail.keyword": {"value": f"*{mail}*"}
                    }
                })
    if dep_list:
        dep_query = {"bool": {"should": {}}}
        dep_should = []
        for dep in dep_list:
            dep_should.append({"wildcard": {
                                "department.keyword": {"value": f"*{dep}*"}}})
        dep_query["bool"]["should"] = dep_should
        dep_query["bool"]["minimum_should_match"] = 1
    if title_list:
        title_query = {"bool": {"should": {}}}
        title_should = []
        for title in title_list:
            title_should.append({"wildcard": {
                                "title.keyword": {"value": f"*{title}*"}}})
        title_query["bool"]["should"] = title_should
        title_query["bool"]["minimum_should_match"] = 1

    # elasticsearchに接続して検索
    return self.es.search('user', query)
```

## おわり
wildcardが便利、でも処理遅くなるからあんまり使わない方がいいらしいです。