> For the complete documentation index, see [llms.txt](https://esbook.kimjmin.net/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://esbook.kimjmin.net/05-search/5.6-range.md).

# 5.6 범위 쿼리 - Range Query

&#x20; 지금까지는 문자열 필드들의 검색에 대해 살펴보았습니다. Elasticsearch는 이 외에도 **숫자**나 **날짜** 형식들의 저장이 가능합니다. 숫자, 날짜 형식은 **range** 쿼리를 이용해서 검색을 합니다.

&#x20; **range** 쿼리의 예제를 위해 먼저 아래의 **phones** 인덱스를 추가하겠습니다.

{% code title="bulk 명령으로 phone 인덱스 추가" %}

```javascript
POST phones/_bulk
{"index":{"_id":1}}
{"model":"Samsung GalaxyS 5","price":475,"date":"2014-02-24"}
{"index":{"_id":2}}
{"model":"Samsung GalaxyS 6","price":795,"date":"2015-03-15"}
{"index":{"_id":3}}
{"model":"Samsung GalaxyS 7","price":859,"date":"2016-02-21"}
{"index":{"_id":4}}
{"model":"Samsung GalaxyS 8","price":959,"date":"2017-03-29"}
{"index":{"_id":5}}
{"model":"Samsung GalaxyS 9","price":1059,"date":"2018-02-25"}
```

{% endcode %}

( 🤓 위 예제 데이터는 실제 상품 정보와 아무런 관련이 없습니다 )

range 쿼리는 `range : { <필드명>: { <파라메터>:<값> } }` 으로 입력됩니다. range 쿼리 파라메터는 아래의 4가지가 있습니다.&#x20;

* **gte** (Greater-than or equal to) - 이상 (같거나 큼)
* **gt** (Greater-than) – 초과 (큼)
* **lte** (Less-than or equal to) - 이하 (같거나 작음)
* **lt** (Less-than) - 미만 (작음)

&#x20; 다음은 phone 인덱스에서 **price** 필드 값이 **700 이상**, **900 미만**인 데이터를 검색하는 쿼리입니다.

{% tabs %}
{% tab title="request" %}
{% code title="price 값이 700 이상 900 미만인 데이터 검색" %}

```javascript
GET phones/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 700,
        "lt": 900
      }
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="response" %}
{% code title="price 값이 700 이상 900 미만인 데이터 검색 결과" %}

```javascript
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "phones",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "model" : "Samsung GalaxyS 6",
          "price" : 795,
          "date" : "2015-03-15"
        }
      },
      {
        "_index" : "phones",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "model" : "Samsung GalaxyS 7",
          "price" : 859,
          "date" : "2016-02-21"
        }
      }
    ]
  }
}
```

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

&#x20; price 값이 700과 900 사이인 `"price" : 795`, `"price" : 859` 두개의 결과가 리턴 되었습니다.

### 날짜 검색

&#x20; 날짜도 숫자와 마찬가지로 **range** 쿼리의 사용이 가능합니다. 기본적으로 Elasticsearch 에서 날짜 값은 2016-01-01 또는 2016-01-01T10:15:30 과 같이 JSON 에서 일반적으로 사용되는 **ISO8601** 형식을 사용합니다. 다음은 date 필드의 날짜가 2016년 1월 1일 이후인 도큐먼트들을 검색하는 쿼리입니다.

{% tabs %}
{% tab title="request" %}
{% code title="date 값이 2016-01-01 이후인 데이터 검색" %}

```javascript
GET phones/_search
{
  "query": {
    "range": {
      "date": {
        "gt": "2016-01-01"
      }
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="response" %}
{% code title="date 값이 2016-01-01 이후인 데이터 검색 결과" %}

```javascript
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "phones",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "model" : "Samsung GalaxyS 7",
          "price" : 859,
          "date" : "2016-02-21"
        }
      },
      {
        "_index" : "phones",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "model" : "Samsung GalaxyS 8",
          "price" : 959,
          "date" : "2017-03-29"
        }
      },
      {
        "_index" : "phones",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 1.0,
        "_source" : {
          "model" : "Samsung GalaxyS 9",
          "price" : 1059,
          "date" : "2018-02-25"
        }
      }
    ]
  }
}
```

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

&#x20; 쿼리의 날짜 포맷을 다르게 하고 싶으면 `format` 옵션의 사용이 가능합니다. `||` 을 사용해서 여러 값의 입력이 가능합니다. 아래는 date 필드의 값이 **2015년 12월 31일** 부터 **2018년 이전** 사이에 있는 값들을 검색하는 쿼리입니다.

{% tabs %}
{% tab title="request" %}
{% code title="date 값이 2016-01-01 \~ 2018-01-01 사이의 데이터 검색" %}

```javascript
GET phones/_search
{
  "query": {
    "range": {
      "date": {
        "gt": "31/12/2015",
        "lt": "2018",
        "format": "dd/MM/yyyy||yyyy"
      }
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="response" %}
{% code title="date 값이 2016-01-01 \~ 2018-01-01 사이의 데이터 검색 결과" %}

```javascript
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "phones",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "model" : "Samsung GalaxyS 7",
          "price" : 859,
          "date" : "2016-02-21"
        }
      },
      {
        "_index" : "phones",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "model" : "Samsung GalaxyS 8",
          "price" : 959,
          "date" : "2017-03-29"
        }
      }
    ]
  }
}
```

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

&#x20; 날짜를 검색 할 때는 검색하는 현재 시간을 가져오는 예약어 `now`와 `y`(년), `M`(월), `d`(일), `h`(시), `m`(분), `s`(초), `w`(주) 등의 사용이 가능합니다. 다음은 **date**의 값이 **2016년 1월 1일에서 6개월 후**인 날 부터 **오늘보다 365일 전**인 날 사이의 데이터를 가져오는 쿼리입니다. 참고로 필자가 아래 예제를 실행한 날짜는 **2019년 9월 3일** 입니다.

{% tabs %}
{% tab title="request" %}
{% code title="date 값이 2016-01-01 의 6개월 후 부터 오늘 (2019-09-03) 의 365일 이전 사이 값 검색" %}

```javascript
GET phones/_search
{
  "query": {
    "range": {
      "date": {
        "gt": "2016-01-01||+6M",
        "lt": "now-365d"
      }
    }
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="response" %}
{% code title="date 값이 2016-01-01 의 6개월 후 부터 오늘 (2019-09-03) 의 365일 이전 사이 값 검색 결과" %}

```javascript
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "phones",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "model" : "Samsung GalaxyS 8",
          "price" : 959,
          "date" : "2017-03-29"
        }
      },
      {
        "_index" : "phones",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 1.0,
        "_source" : {
          "model" : "Samsung GalaxyS 9",
          "price" : 1059,
          "date" : "2018-02-25"
        }
      }
    ]
  }
}
```

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

&#x20; phones 인덱스의 전체 도큐먼트 중 2016년 1월 1일에 6개월을 더한 **2016-06-01**과 검색을 실행한 2019년 9월 3일 보다 1년 전인 **2018-09-03** 사이의 값인 `"date" : "2017-03-29"`, `"date" : "2018-02-25"` 두 개의 결과가 리턴 되었습니다.

&#x20; 지금까지 살펴본 **range** 쿼리의 스코어는 모두 `"_score" : 1.0`로 동일합니다. **range** 쿼리는 기본적으로 정확도를 계산하지 않습니다. 검색하는 조건이 1000이하라고 할 때 1000에 가까울수록 정확도가 높아지고 1000 보다 크게 낮아질수록 정확도가 떨어지는 것은 아닙니다. 오로지 필드의 값이 1000 보다 같거나 작은지 아닌지의 `true` / `false` 여부만을 판단합니다. 예를 들어 구인 시스템에 입력된 구직자의 입사 지원 조건이 나이 24세부터 55세 사이라고 가정했을 때 구직자의 나이가 35세에 가까울수록 가장 점수가 높고 20대이거나 50대 이면 점수가 낮아지거나 하지 않습니다. range 쿼리는 숫자 또는 날짜가 쿼리 조건에 부합하는지 아닌지의 여부만을 계산합니다.

&#x20; 경우에 따라 range 쿼리에 기준점을 주고 기준점과 가깝거나 멀어질 수록 스코어를 적용할 필요가 있다면 [function\_score 쿼리](https://www.elastic.co/guide/en/elasticsearch/reference/7.3/query-dsl-function-score-query.html)를 사용해서 조정이 가능합니다. function\_score 쿼리는 이 책에서는 다루지 않으니 공식 도큐먼트를 참고하시기 바랍니다.

### 정리&#x20;

&#x20; 이번 장에서는 검색의 개념과 Elasticsearch에서 주로 사용되는 **match, match\_phrase, bool, range** 등의 쿼리들에 대해 알아보았습니다. 그리고 검색에 영향을 미치는 **정확도(relevancy)**, 스코어 점수의 개념들에 대해서도 알아보았습니다.

&#x20; Elastic Stack은 검색 외에도 다양한 형태의 데이터 분석 기능들을 제공하지만, 의미 있는 데이터 분석을 위해서는 유효한 데이터의 범위를 확장하고 축소하는 것이 중요하기 때문에 기본 기능인 검색을 잘 이해하는 것 또한 필요합니다. 이번 장에서 살펴본 쿼리 외에도 **geo\_point** 나 **nested** 같은 특수한 데이터들을 검색하는 쿼리들도 있습니다. 이런 쿼리들은 뒤에서 해당 내용들을 설명하면서 같이 다루도록 하겠습니다.

&#x20; 다음 장에서는 텍스트 데이터의 분석과 색인 과정에 대해 배워보도록 하겠습니다.
