프로젝트/여행지 오픈 API

[ElasticSearch] 7. 쿼리

블랑v 2023. 10. 25. 13:51

개요

엘라스틱서치는 검색을 위해 리프 쿼리와 복합 쿼리를 지원한다.

리프 쿼리는 특정 필드에서 용어를 찾는 쿼리로, match / term / range 쿼리로 나뉜다.

복합 쿼리는 쿼리들을 조합해 사용하고 대표적으로 이전 포스팅에서 설명했던 논리 쿼리 등이 존재한다.

 

전문 쿼리와 용어 수준 쿼리

이전 시간의 keyword와 text의 차이에 대해 생각해보자.

- 전문 쿼리 : 전문 검색을 위해 사용하며 필드는 매핑 시 text 타입으로 선언해야 한다.

- 용어 수준 쿼리는 정확히 일치하는 용어를 찾기 위해 사용하며, keyword로 선언한다.

 

전문 쿼리

매치 쿼리 - 전문 검색 실행(text)

매치 쿼리는 대표적인 전문 쿼리로, 특정 필드의 용어를 검색하는데 사용한다.

//1. 특정 필드 검색하기
GET kibana_sample_data_ecommerce/_search
{
  "_source": ["customer_full_name"], //해당 필드만 포함하여 결과 탐색(없을 경우 전체 문서 반환)
  "query": {
    "match": {
      "customer_full_name": "Mary"
    }
  }
}

//2. 복수 개의 용어를 검색할 수도 있다.
GET kibana_sample_data_ecommerce/_search
{
  "_source": ["customer_full_name"],
  "query": {
    "match": {
      "customer_full_name": "Mary bailey" //복수 탐색. 이 경우 공백은 OR 연산자로 동작한다.
    }
  }
}

//3. AND 연산 수행하기
GET kibana_sample_data_ecommerce/_search
{
  "_source": ["customer_full_name"],
  "query": {
    "match": {
      "customer_full_name": {
      	"query": "Mary bailey", //복수 탐색
      	"operator": "and"		//AND 연산자 운용
      }
    }
  }
}

 

매치 프레이즈 쿼리 - 여러 요소들을 '순서에 맞게'

매치 프레이즈 쿼리는 동사가 아닌 2개 이상의 단어가 연결된 요소를 찾는다.

"똑똑한 아이", "노란 잠수함" 등의 연결 요소를 뜻한다.

중요한 점은 우리는 "똑똑한 아이"를 찾는 것이지, "아이 똑똑한"을 찾는 것이 아니라는 것이다. 단어의 순서 역시 중요하다.

 

//매치 페이즈. 이전 match와 mary, bailey가 토큰화되는 것은 동일하지만
//검색 단어가 모두 포함되면서 해당 순서여야 한다.
//예를 들어, 'mary kim bailey'나, 'bailey mary' 등은 매칭되지 않는다.
GET kibana_sample_data_ecommerce/_search
{
  "_source": ["customer_full_name"],
  "query": {
      "match_phrase": {
        "customer_full_name": "mary bailey"
    }
  }
}

 

 

용어 쿼리

용어 쿼리 - term을 위해 사용하자.

 

용어 쿼리는 용어 수준 쿼리의 대표적인 쿼리다. 

이전의 매치 쿼리는 '전문 쿼리' 에 속하기에 내용물이 분석기에 토큰화되고 매칭이 되었다. (mary bailey -> [mary, bailey])

하지만 용어 검색은 용어 수준이기에 검색기에 의해 토큰화되지 않는다.

 

//기존 코드, 3개의 결과가 나왔다. (매치 쿼리)
//이는 분석 과정에서 분석기에 의해 토크나이저 되어 검색했기 때문이다.
GET kibana_sample_data_ecommerce/_search
{
  "_source": ["customer_full_name"],
  "query": {
      "match_phrase": {
        "customer_full_name": "mary bailey"
    }
  }
}

//term 검색을 통해 완전히 매칭되는 단어만 찾게 되었다.
//분석기를 거치지 않았기에 대소문자가 완벽히 맞아야 한다.
GET kibana_sample_data_ecommerce/_search
{
  "_source": ["customer_full_name"],
  "query": {
    "term": {
      "customer_full_name": "Mary bailey"
    }
  }
}

//용어 쿼리가 'mary' 라는 단어를 찾으면 154개가 나온다.
//대문자 'Mary'는 찾지 못한다.
//이는 해당 kibana_sample_date_ecommerce가 
//분석기에 의해 lowercase 변환되어 [mary, bailey]로 분리되었기 때문이다.
GET kibana_sample_data_ecommerce/_search
{
  "_source": ["customer_full_name"],
  "query": {
    "term": {
      "customer_full_name": "mary"
    }
  }
}

//사실 강제하지는 않지만, 용어 쿼리는 keyword 타입으로 매핑된 필드에서 사용해야 한다.
//'customer_full_name' 필드는 텍스트와 키워드를 가지는 멀티 필드이다.
GET kibana_sample_data_ecommerce/_search
{
  "_source": ["customer_full_name"],
  "query": {
    "term": {
      "customer_full_name.keyword": "Mary Bailey" //키워드 타입에 맞추어 요청해보자.
    }
  }
}

 

용어들 쿼리 - 여러 용어들을 동시검색해보자.

용어 수준 쿼리의 일종, 여러 용어들을 검색해준다.

- 분석기를 거치지 않아 대소문자를 신경써야 한다!

//여러 용어들을 동시에 검색해보자.
//월요일 혹은 일요일 혹은 토요일의 1929개의 검색 결과가 나온다.
GET kibana_sample_data_ecommerce/_search
{
  "_source": ["day_of_week"],
  "query": {
    "terms": {
      "day_of_week": ["Monday", "Sunday", "Saturday"]
    }
  }
}

//다음과 같이 소문자로 작성할 경우 제대로 쿼리가 검색되지 않는 것을 확인할 수 있었다.
GET kibana_sample_data_ecommerce/_search
{
  "_source": ["day_of_week"],
  "query": {
    "terms": {
      "day_of_week": ["monday", "sunday", "saturday"]
    }
  }
}

 

중간 결론

결과적으로 말하고자 하는 핵심은 '용어 쿼리'와 '매치 쿼리'의 용도록 명확히 하는 것이 좋다는 것이다.

- 용어 쿼리를 포함한 용어 수준 쿼리 : 키워드 타입으로 매핑된 필드를 대상으로 (키워드 검색 혹은 범주형 데이터 검색)

- 매치 쿼리를 포함한 전문 쿼리 : 텍스트 타입으로 매핑된 필드를 대상으로 검색

 

그 외에 자주 쓰이는 쿼리

멀티 매치 쿼리

무언가를 검색하고 싶은데, 그게 어디 필드인지를 모르면 어떻게 하지?
예를 들면 '트럼프' 를 검색하고 싶은데, 그게 뉴스 제목일지 본문일지는 아무도 모른다.

멀티 매치 쿼리는 여러 필드에 대해 동일한 쿼리 문자열을 검색할 때 사용된다. 이는 사용자가 어떤 필드에 쿼리 문자열이 위치하는지 정확히 모를 때 유용하다.

 

//여러 필드에서 원하는 쿼리문을 추출해보자.
//쿼리파람으로 explain = true 설정 시 개별 필드의 스코어 계산값을 알 수 있다.
//필드가 너무 많다면 * 와일드카드를 응용할 수도 있다.
GET kibana_sample_data_ecommerce/_search/?explain=true
{
  "_source": ["customer_first_name", "customer_last_name", "customer_full_name"],
  "query": {
    "multi_match": {
      "query": "mary",
      "fields": [
        "customer_full_name",
        "customer_first_name",
        "customer_last_name"
        ex) customer_*_name 등으로 응용 가능
      ]
    }
  }
}

 

필드 가중치

여러 필드 검색 중 특정 필드에 가중치를 둘 수도 있다. 이를 부스팅이라고 한다.

예를 들어 '엘라스틱' 이라는 단어를 검색할 때, 제목과 본문에 들어있는 것은 느낌이 다를 것이다. 제목이 훨씬 중요할 테니 말이다. 여러 검색 필드 중 중요한 필드가 있다면 가중치를 사용해 보자.

 

//full name에 2배의 가중치를 두었다.
//이러면 대표 스코어(검색 결과)가 변경될 가능성이 크다.
GET kibana_sample_data_ecommerce/_search/?explain=true
{
  "_source": ["customer_first_name", "customer_last_name", "customer_full_name"],
  "query": {
    "multi_match": {
      "query": "mary",
      "fields": [
        "customer_full_name^2",
        "customer_first_name",
        "customer_last_name"
      ]
    }
  }
}

 

범위 쿼리

특정 날짜나 숫자의 범위를 지정한 데이터들을 검색 시 사용한다.

예제를 직접 살펴보자.

 

GET kibana_sample_data_flights/_search
{
  "query": {
    "range": {
      "timestamp": {
        "gte": "2019-12-15",
        "lt": "2024-12-16"
      }
    }
  }
}

/*
gte : 이상 ex) gte : 10 10이거나 10보다 큰..
gt : 초과
lte : 이하
lt : 미만
*/

//혹은 다음과 같은 형식도 존재 : 지금부터 한달 전까지의 모든 데이터는?
GET kibana_sample_data_flights/_search
{
  "query": {
    "range": {
      "timestamp": {
        "gte": "now-1M"
      }
    }
  }
}

 

논리 쿼리

오늘 생성된 것들 중 불량품을 찾고 싶다던가, 성이 Mary이지만 이름은 Bailey가 아닌 사람들이라던가..

논리 쿼리는 여러 개의 서브 쿼리를 결합하여 하나의 복합 쿼리를 형성하는 역할을 한다.

SQL의 AND, OR, NOT 연산자와 유사한 역할을 한다. 논리 쿼리는 검색 엔진이나 문서 데이터베이스에서 사용되며, 주로 다음과 같은 연산자들로 구성된다

 

Bool 쿼리: 여러 쿼리 조건을 결합할 때 사용된다.

  • must: 모든 조건을 만족하는 문서를 반환한다. (AND와 유사)
  • should: 하나 이상의 조건을 만족하는 문서를 반환한다. (OR와 유사)
  • must_not: 지정한 조건을 만족하지 않는 문서를 반환한다. (NOT와 유사)
  • filter: 조건을 만족하는 문서를 반환하되, 스코어에 영향을 주지 않는다.
//사과와 바나나를 포함하지만, 딸기가 아닌 문서
{
  "bool": {
    "must": [
      {"match": {"content": "사과"}},
      {"match": {"content": "바나나"}}
    ],
    "must_not": [
      {"match": {"content": "딸기"}}
    ]
  }
}

//실제 예제
GET kibana_sample_data_ecommerce/_search
{
  "_source": ["customer_full_name"],
  "query": {
    "bool": {
      "should": [
        {"match" : {"customer_full_name": "mary"}},
        {"match" : {"customer_full_name": "kim"}}
      ]
    }
  }
}

//should를 통해 월요일인 곳이 더 우선순위가 높다.
GET kibana_sample_data_ecommerce/_search
{
  "_source": ["customer_full_name"],
  "query": {
    "bool": {
      "must": {"match" : {"customer_full_name": "mary"}},
      "should": {"term": {"day_of_week": "Monday"}}
    }
  }
}

//filter는 스코어 유사도와 관련없이 참 거짓만을 판단한다.
GET kibana_sample_data_ecommerce/_search
{
  "_source": ["customer_full_name", "day_of_week"],
  "query": {
    "bool": {
      "filter": 
        {
          "range": {
            "products.base_price": {
              "gte": 10,
              "lte": 20
            }
          }
        }
      
    }
  }
}
타입 설명
must 쿼리를 실행하여 참인 도큐먼트를 찾는다.
복수 쿼리 실행시 AND 연산
must_not 거짓인 도큐먼트 탐색
다른 타입과 사용시 도큐먼트에서 제외
should 단독 사용시 참인 도큐먼트를 탐색
복수 쿼리 실행시 OR 연산
다른 타입의 경우 스코어에만 활용(예제3)
filter 쿼리 실행후 Y/N의 필터 컨텍스트 수행
필터는 must와 같지만, 필터 컨텍스트로 동작하기에 스코어에 영향을 미치지 않는다. (boolean만 수행)

 

패턴 검색과 정규식

검색어의 대략적인 키워드나 몇 개의 알파벳만 알고 있을 경우, 패턴을 이용해 검색할 수 있다.

패턴 검색은 정규 표현식을 사용하여 텍스트 내에서 특정 패턴과 일치하는 값들을 찾아내는 검색 방식이다. Elasticsearch에서는 regexp 쿼리를 사용하여 패턴 검색을 수행할 수 있다.

regexp 쿼리는 특정 필드의 텍스트 값이 주어진 정규 표현식과 일치하는지 검사한다. 이를 통해 복잡한 문자열 패턴을 기반으로 검색할 수 있다.

 

예제

//name 필드 값이 "Jo"로 시작하는 모든 문서를 찾아내기
{
  "query": {
    "regexp": {
      "name.keyword": "Jo.*"
    }
  }
}

//email 필드에서 ".com"으로 끝나는 모든 문서를 찾아내기
{
  "query": {
    "regexp": {
      "email.keyword": ".*\\.com$"
    }
  }
}

//code 필드에서 숫자만 포함하는 모든 문서를 찾아내기
{
  "query": {
    "regexp": {
      "code.keyword": "^[0-9]+$"
    }
  }
}

 

이러한 정규 표현식을 사용하여 복잡한 패턴의 문자열을 검색할 수 있다. 하지만, 패턴 검색은 리소스를 많이 사용하므로 대량의 데이터에서는 성능 문제가 발생할 수 있다. 따라서 실제 환경에서 사용할 때는 최적화된 설정과 적절한 리소스 할당이 필요하다.