5.6 범위 쿼리 - Range Query

이 문서의 허가되지 않은 무단 복제나 배포 및 출판을 금지합니다. 본 문서의 내용 및 도표 등을 인용하고자 하는 경우 출처를 명시하고 김종민(kimjmin@gmail.com)에게 사용 내용을 알려주시기 바랍니다.

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

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

bulk 명령으로 phone 인덱스 추가
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"}

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

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

  • gte (Greater-than or equal to) - 이상 (같거나 큼)

  • gt (Greater-than) – 초과 (큼)

  • lte (Less-than or equal to) - 이하 (같거나 작음)

  • lt (Less-than) - 미만 (작음)

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

request
response
request
price 값이 700 이상 900 미만인 데이터 검색
GET phones/_search
{
"query": {
"range": {
"price": {
"gte": 700,
"lt": 900
}
}
}
}
response
price 값이 700 이상 900 미만인 데이터 검색 결과
{
"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"
}
}
]
}
}

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

날짜 검색

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

request
response
request
date 값이 2016-01-01 이후인 데이터 검색
GET phones/_search
{
"query": {
"range": {
"date": {
"gt": "2016-01-01"
}
}
}
}
response
date 값이 2016-01-01 이후인 데이터 검색 결과
{
"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"
}
}
]
}
}

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

request
response
request
date 값이 2016-01-01 ~ 2018-01-01 사이의 데이터 검색
GET phones/_search
{
"query": {
"range": {
"date": {
"gt": "31/12/2015",
"lt": "2018",
"format": "dd/MM/yyyy||yyyy"
}
}
}
}
response
date 값이 2016-01-01 ~ 2018-01-01 사이의 데이터 검색 결과
{
"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"
}
}
]
}
}

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

request
response
request
date 값이 2016-01-01 의 6개월 후 부터 오늘 (2019-09-03) 의 365일 이전 사이 값 검색
GET phones/_search
{
"query": {
"range": {
"date": {
"gt": "2016-01-01||+6M",
"lt": "now-365d"
}
}
}
}
response
date 값이 2016-01-01 의 6개월 후 부터 오늘 (2019-09-03) 의 365일 이전 사이 값 검색 결과
{
"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"
}
}
]
}
}

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

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

경우에 따라 range 쿼리에 기준점을 주고 기준점과 가깝거나 멀어질 수록 스코어를 적용할 필요가 있다면 function_score 쿼리를 사용해서 조정이 가능합니다. function_score 쿼리는 이 책에서는 다루지 않으니 공식 도큐먼트를 참고하시기 바랍니다.

정리

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

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

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