이번에는 Elasticsearch 검색 API 및 Query DSL를 사용해서 다양한 형태의 검색 명령을 수행해 보자.
이번 데이터셋도 wikimedia kowiki로 wikimedia에서 제공하는 한국어 데이터셋을 사용해 볼 것이다.
환경설정하고 ES 데몬 실행하고 wikimedia 데이터셋 가져와서 nori 분석기로 데이터 색인까지 저번글에 작성되어 있으니 그 부분 참고하면 좋을 것 같다.
👇 이전 글 👇
Query DSL 활용
match 쿼리
먼저 가장 기본적인 full-text query를 사용해 보자.
match 쿼리를 사용해서 text로부터 query를 검색해 보자.
size는 5로 정해주고 실행해 보자.
# 기본 full-text query
query_body = {
"query": {
"match": {
"text": {
"query": "대한민국 대통령 노무현"
}
}
}
}
res = es.search(index="test", body=query_body, size=5, sort="_score")
# 결과 출력
for rst in res['hits']['hits']:
print('score:', rst['_score'], 'source::', rst['_source'])
결과를 잘 보면 text에 '대한민국 대통령'같은 단어가 상위에 나오는 걸 볼 수 있다.
match_phrase
위와 유사하지만 아까와 다르게 query에 해당하는 문구가 정확히 일치하는 문서를 찾는다.
# full-text query - match_phrase
query_body = {
"query": {
"match_phrase": {
"text": {
"query": "대한민국 대통령"
}
}
}
}
res = es.search(index="test", body=query_body, size=5, sort="_score")
# 결과 출력
for rst in res['hits']['hits']:
print('score:', rst['_score'], 'source::', rst['_source'])
아까는 5개가 다 나온 반면에 이번에는 하나만 출력된 걸 확인할 수 있다. (지금은 잘 안 보이지만 쭉 스크롤하다 보면 '대한민국 대통령'이 붙어있게 나오는 문구가 나온 걸 확인할 수 있다.)
full-text query (match) + operator 변경
기본적으로 ES는 검색할 때 or 조건을 통해서 검색한다. 즉, 쿼리가 반으로 분리됐을 때 각각의 단어들이 하나라도 있으면 추출된다. 그런데 모아져 있으면 relevence score 가 좋아지기 때문에 상위에 노출될 가능성이 높아진다.
operator를 and로 수정하면 '대한민국', '대통령', '노무현'이 전부 나와야 한다.
# 기본 full-text query + operator 변경
query_body = {
"query": {
"match": {
"text": {
"query": "대한민국 대통령 노무현",
# 기본값은 "OR"
"operator": "AND"
}
}
}
}
res = es.search(index="test", body=query_body, size=5, sort="_score")
# 결과 출력
for rst in res['hits']['hits']:
print('score:', rst['_score'], 'source::', rst['_source'])
실제 우리가 사용하는 포털 검색 같은 경우 기본적으로 and 검색으로 진행된다. 검색 결과가 매우 많기 때문에 or로 하는 것보다 and로 하는 것이 더 효과적인 검색 결과를 도출할 수 있기 때문이다.
full-text query (match) + 멀티 필드 지원
이제는 멀티 필드에서도 검색해 보자.
위에 query_body1은 이전과 동일한데 아래 query_body2는 multi_match를 사용한다. 이건 기존 match와는 다르게 query를 지정하고 fields에 원하는 필드를 작성하면 된다.
query_body1은 특정 필드에서만(title) 찾겠다.라는 의미고 query_body2는 작성한 필드 전체에서 찾겠다. 하는 뜻이다.
# full-text query - 멀티 필드 지원
query_body1 = {
"query": {
"match": {
"title": {
"query": "지미"
}
}
}
}
res = es.search(index="test", body=query_body1, size=5, sort="_score")
# 결과 출력
for rst in res['hits']['hits']:
print('score:', rst['_score'], 'source::', rst['_source'])
print("---")
query_body2 = {
"query": {
"multi_match": {
"query": "지미",
"fields": ["title", "text"]
}
}
}
res = es.search(index="test", body=query_body2, size=5, sort="_score")
# 결과 출력
for rst in res['hits']['hits']:
print('score:', rst['_score'], 'source::', rst['_source'])
--- 위쪽에는 title에만 해당하기 때문에 하나 출력되고 --- 아래쪽은 역시나 하나다... 아마 text에 해당하는 검색 결과가 없는 모양이다,,ㅎㅎ
Compound query + filter
Compound query는 조금 복잡한데 bool 키워드를 만들고 그 아래 여러 leaf node를 달아줄 수 있다.
현재 같은 경우에는 must 하나밖에 없는 상황이고, must 조건에 match를 사용해서 위에서 본 것처럼 특정 필드에서 검색하는 경우다.
지금과 같은 경우는 위와 검색 결과가 달라지지 않을 것 같다. 검색 결과에 '대한민국'과 '대통령'이 나오는 문서를 추출해 준 걸 확인할 수 있다.
query_body = {
"query": {
"bool": {
"must": {
"match": {
"text": {
"query": "대한민국 대통령"
}
}
}
}
}
}
res = es.search(index="test", body=query_body, size=5, sort="_score")
# 결과 출력
for rst in res['hits']['hits']:
print('score:', rst['_score'], 'source::', rst['_source'])
조금 더 다양하게 시도해 보자면 bool 아래 must와 filter 노드 두 개가 있다. 지금은 text 필드에 쿼리를 검색하고 있는데 filter 조건을 보면 term이 있는데 이건 키워드로 title에 '지미'라는 키워드가 있으면 필터링하겠다. ('지미'가 나타나지 않는 데이터는 제거하고 '지미'가 나타난 데이터중에서만 찾겠다.)라는 의미다.
여기서 filter안에 'term'인 이유는 키워드를 의미하고 형태소 분석기나 analyzer를 적용하지 않고 단지 공백으로 나뉜 키워드들 중에서 '지미'가 있으면 되는 방식이다.
# Compound query + filter 예시
query_body = {
"query": {
"bool": {
"must": {
"match": {
"text": {
"query": "대한민국 대통령"
}
}
},
"filter": {
"term": {
"title": "지미"
}
}
}
}
}
res = es.search(index="test", body=query_body, size=5, sort="_score")
# 결과 출력
for rst in res['hits']['hits']:
print('score:', rst['_score'], 'source::', rst['_source'])
title을 잘 보면 '지미 카터'로 '지미'가 잘 들어간 걸 확인할 수 있다.
Compound query + must_not
must_not은 실제로 해당 키워드가 있으면 안 된다는 의미다.
query_body = {
"query": {
"bool": {
"must": {
"match": {
"text": {
"query": "대한민국 대통령"
}
}
},
"must_not": {
"term": {
"title": "지미"
}
}
}
}
}
res = es.search(index="test", body=query_body, size=5, sort="_score")
# 결과 출력
for rst in res['hits']['hits']:
print('score:', rst['_score'], 'source::', rst['_source'])
결과를 잘 보면 title에 '지미'가 있는 건 없는 걸 확인할 수 있다.
Compound query + should 1
should는 must랑 비슷하지만 must는 꼭 필요한 조건이고, should는 만약에 만족하면 점수를 더 주는 방식이다. 여기서는 만약에 title에 '노무현'이 나오면 점수를 주겠다는 의미다.
query_body = {
"query": {
"bool": {
"must": {
"match": {
"text": {
"query": "대한민국 대통령"
}
}
},
"should": {
"term": {
"title": "노무현"
}
}
}
}
}
res = es.search(index="test", body=query_body, size=5, sort="_score")
# 결과 출력
for rst in res['hits']['hits']:
print('score:', rst['_score'], 'source::', rst['_source'])
결과로 title에 '노무현'이 있는데 첫 번째로 나오는 걸 확인할 수 있다.
Compound query + should 2
아까는 term을 사용했는데 이번에는 text를 사용해 보자. 이것도 위와 비슷하지만 should에서 match에 있는 쿼리 '노무현'이나 '대통령' 단어가 들어간 데이터에 더 점수를 많이 주겠다는 의미다.
query_body = {
"query": {
"bool": {
"must": {
"match": {
"text": {
"query": "대한민국 대통령"
}
}
},
"should": {
"match": {
"text": {
"query": "노무현 대통령"
}
}
}
}
}
}
res = es.search(index="test", body=query_body, size=5, sort="_score")
# 결과 출력
for rst in res['hits']['hits']:
print('score:', rst['_score'], 'source::', rst['_source'])
title을 보면 '노무현', '대통령'이 들어간 데이터가 상위에 있는 걸 확인할 수 있다.
'Tools' 카테고리의 다른 글
Vector 유사도 검색 - Faiss (0) | 2024.12.05 |
---|---|
Vector 유사도 검색 - Elasticsearch (0) | 2024.12.05 |
구글 코랩에서 Elasticsearch Nori 사용해보기 (ES 공식 지원 한글 형태소 분석기) (0) | 2024.12.04 |
구글 코랩에서 Elasticsearch 설치 및 실습 (1) | 2024.12.04 |
Slack과 GitHub 연동 (2) | 2023.10.25 |