# 5.5 정확값 쿼리 - Exact Value Query

&#x20; 지금까지 살펴본 풀 텍스트 검색은 스코어 점수 기반으로 **정확도(relevancy)**&#xAC00; 높은 결과부터 가져옵니다. Elasticsearch는 정확도를 고려하는 풀 텍스트 외에도 검색 조건의 **참 / 거짓 여부만 판별**해서 결과를 가져오는 것이 가능합니다. 풀 텍스트와 상반되는 이 특성을 **정확값(Exact Value)** 이라고 하는데 말 그대로 값이 정확히 일치 하는지의 여부 만을 따지는 검색입니다. Exact Value 에는 **term**, **range** 와 같은 쿼리들이 이 부분에 속하며, 스코어를 계산하지 않기 때문에 보통 **bool** 쿼리의 **filter** 내부에서 사용하게 됩니다.

### bool : filter

&#x20; bool쿼리의 filter 안에 하위 쿼리를 사용하면 스코어에 영향을 주지 않습니다. 다음 3개의 검색 결과를 비교 해 보도록 하겠습니다.

{% tabs %}
{% tab title="request" %}
{% code title="match 쿼리로 fox 검색" %}

```javascript
GET my_index/_search
{
  "query": {
    "match": {
      "message": "fox"
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="response" %}
{% code title="match 쿼리로 fox 검색 결과" %}

```javascript
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 0.32951736,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.32951736,
        "_source" : {
          "message" : "The quick brown fox"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 0.32951736,
        "_source" : {
          "message" : "Brown fox brown dog"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.23470737,
        "_source" : {
          "message" : "The quick brown fox jumps over the lazy dog"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.23470737,
        "_source" : {
          "message" : "The quick brown fox jumps over the quick dog"
        }
      }
    ]
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="request" %}
{% code title="match 쿼리로 fox 와 quick 검색" %}

```javascript
GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "message": "fox"
          }
        },
        {
          "match": {
            "message": "quick"
          }
        }
      ]
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="response" %}
{% code title="match 쿼리로 fox 와 quick 검색 결과" %}

```javascript
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 0.9468958,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.9468958,
        "_source" : {
          "message" : "The quick brown fox"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.8762741,
        "_source" : {
          "message" : "The quick brown fox jumps over the quick dog"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.6744513,
        "_source" : {
          "message" : "The quick brown fox jumps over the lazy dog"
        }
      }
    ]
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="request" %}
{% code title="must 로 fox 및 filter 으로 quick 검색" %}

```javascript
GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "message": "fox"
          }
        }
      ],
      "filter": [
        {
          "match": {
            "message": "quick"
          }
        }
      ]
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="response" %}
{% code title="must 로 fox 및 filter 으로 quick 검색 결과" %}

```javascript
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 0.32951736,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.32951736,
        "_source" : {
          "message" : "The quick brown fox"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.23470737,
        "_source" : {
          "message" : "The quick brown fox jumps over the lazy dog"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.23470737,
        "_source" : {
          "message" : "The quick brown fox jumps over the quick dog"
        }
      }
    ]
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

&#x20; 첫번째는 **match** 쿼리로 **fox** 를 검색했을 때 **4개**의 도큐먼트가 검색되었고 가장 높은 스코어는 `"_score" : 0.32951736` 입니다. 두번째는 검색에 **match** 쿼리로 **quick** 을 추가했을 때 **3개**의 도큐먼트가 검색되었고 가장 높은 스코어는 `"_score" : 0.9468958` 입니다. 세번째는 첫번째의 검색에 **filter** 구문 안에 **quick** 을 추가했는데 3개의 도큐먼트가 검색되었고 가장 높은 스코어는 첫번째 쿼리와 같은 `"_score" : 0.32951736` 입니다.

&#x20; 이렇게 **filter**는 검색에 조건은 추가하지만 스코어에는 영향을 주지 않도록 제어할 때 사용합니다. 보통 쇼핑몰에서 검색어로 정확도가 높은 상품명을 검색하면서 생산 업체를 다시 필터링 하는 등의 용도로 사용이 가능합니다.

&#x20; **filter** 내부에서 **must\_not** 과 같은 다른 **bool** 쿼리를 포함하려면 **filter** 내부에 **bool** 쿼리를 먼저 넣고 그 안에 다시 **must\_not** 을 넣어야 합니다. 다음은 **fox** 를 포함하면서 **dog** 는 포함하지 않는 도큐먼트를 검색하는 쿼리입니다. **dog** 를 제외하는 **must\_not** 쿼리가 **filter** 안에 있기 때문에 스코어는 **fox** 에만 영향을 받습니다.

{% tabs %}
{% tab title="request" %}
{% code title="must 로 fox 검색 후 must\_not 으로 dog 제거" %}

```javascript
GET my_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "message": "fox"
          }
        }
      ],
      "filter": [
        {
          "bool": {
            "must_not": [
              {
                "match": {
                  "message": "dog"
                }
              }
            ]
          }
        }
      ]
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="response" %}
{% code title="must 로 fox 검색 후 must\_not 으로 dog 제거 결과" %}

```javascript
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.32951736,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.32951736,
        "_source" : {
          "message" : "The quick brown fox"
        }
      }
    ]
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

&#x20; 위 검색에서 결과는 하나만 리턴 되었지만 스코어는 `"_score" : 0.32951736`으로 match 쿼리로 fox만 검색했을 때의 결과와 동일합니다.

### keyword

&#x20; 문자열 데이터는 **keyword** 형식으로 저장하여 정확값 검색이 가능합니다. **Keyword** 에 대해서는 뒤에 **매핑**에서 다시 설명 하겠습니다. 아래의 쿼리는 message 필드값이 **"Brown fox brown dog"** 문자열과 공백, 대소문자까지 정확히 일치하는 데이터만을 결과로 리턴합니다.

{% tabs %}
{% tab title="request" %}
{% code title="keyword 필드 검색" %}

```javascript
GET my_index/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "match": {
            "message.keyword": "Brown fox brown dog"
          }
        }
      ]
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="response" %}
{% code title="keyword 필드 검색 결과" %}

```javascript
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.0,
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 0.0,
        "_source" : {
          "message" : "Brown fox brown dog"
        }
      }
    ]
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

&#x20; keyword 타입으로 저장된 필드는 스코어를 계산하지 않고 정확값의 일치 여부만을 따지기 때문에 스코어가 `"_score" : 0.0` 으로 나오게 됩니다. 스코어를 계산하지 않기 때문에 **keyword** 값을 검색 할 때는 **filter** 구문 안에 넣도록 합니다.

{% hint style="info" %}
filter 안에 넣은 검색 조건들은 스코어를 계산하지 않지만 캐싱이 되기 때문에 쿼리가 더 가볍고 빠르게 실행됩니다. keyword 와 뒤에 설명할 range 쿼리와 같이 스코어 계산이 필요하지 않은 쿼리들은 모두 filter 안에 넣어서 실행하는 것이 좋습니다.
{% endhint %}
