포켓몬으로 알아보는 루씬 (4) Sort query

jinwook han·2022년 3월 2일
0

이번 글은 정렬 쿼리입니다.
다음 테스트를 완성하겠습니다.
공격력이 가장 큰 불타입 포켓몬 5마리를 조회합니다.

@Test
void 공격력이_가장_큰_불타입_포켓몬_5마리() throws IOException {`
// TODO
}

조건 분석

조건은 공격력이 가장 큰 불타입 포켓몬 5마리입니다.
공격력은 DoublePoint로 저장하고, 타입(불타입)은 Field 타입으로 저장하고 있습니다.

쿼리로 넘어가겠습니다.

쿼리

쿼리는 다음과 같습니다.

Term type1Term = new Term("type1", "fire");
TermQuery type1TermQuery = new TermQuery(type1Term);
Term type2Term = new Term("type2", "fire");
TermQuery type2TermQuery = new TermQuery(type2Term);

BooleanQuery typeQuery = new BooleanQuery.Builder()
.add(type1TermQuery, BooleanClause.Occur.SHOULD)
.add(type2TermQuery, BooleanClause.Occur.SHOULD)
.build();

SortField sortField = new SortField("attack", SortField.Type.DOUBLE, true);
Sort sort = new Sort(sortField);

TopDocs search = indexSearcher.search(typeQuery, 1000, sort);

불타입인지 체크하는 BooleanQuery가 있고, 정렬을 위한SortField가 있습니다.

SortField에는 어떤 필드 기준으로 정렬할건지, 필드 타입이 무엇인지, 기본순인지 여부에 대한 정보가 들어갑니다.

SortField를 indexSearch.search의 마지막 매개변수에 넣습니다.

이제 실행하겠습니다.

실행한 후, 하지만..

실행하니 에러가 났습니다.

공격력을 저장하는 DoublePoint 타입으로는 정렬을 할 수 없기 때문에, 에러가 났습니다.

attack(공격력) 필드의 타입 DoublePoint로는 Range 검색만 가능합니다.
정렬이 되려면 새로운 필드가 필요합니다.

** 다음 블로그 글을 참고했습니다.
https://tourspace.tistory.com/239

SortedSetDocValuesField, NumericDocValuesField, SortedNumericDocValuesField 등의 필드로 정렬을 할 수 있습니다.

여기에서는 NumericDocValuesField를 사용하도록 하겠습니다.

정렬 가능한 필드 타입으로 바꾼 후 다시 실행

DoublePoint 타입의 필드를 NumericDocValuesField로 코드를 수정했습니다.

private static void indexDouble(Document document, String pokemonStatValue, String statFieldName) {
  NumericDocValuesField field = new DoubleDocValuesField(statFieldName, Double.parseDouble(pokemonStatValue));
  StoredField storedField = new StoredField(statFieldName, Double.parseDouble(pokemonStatValue));

  document.add(field);
  document.add(storedField);
}

DoublePoint에서 DoubleDocValuesField로 수정했습니다. DoubleDocValuesField는 NumericDocValuesField의 구현입니다.

테스트를 실행해봅시다.

@Test
void 공격력이_가장_큰_불타입_포켓몬() throws IOException {
  File file = new File("hi");
  Directory directory = FSDirectory.open(file.toPath());
  DirectoryReader directoryReader = DirectoryReader.open(directory);

  IndexSearcher indexSearcher = new IndexSearcher(directoryReader);

  Term type1Term = new Term("type1", "fire");
  TermQuery type1TermQuery = new TermQuery(type1Term);
  Term type2Term = new Term("type2", "fire");
  TermQuery type2TermQuery = new TermQuery(type2Term);
  BooleanQuery typeQuery = new BooleanQuery.Builder()
  .add(type1TermQuery, BooleanClause.Occur.SHOULD)
  .add(type2TermQuery, BooleanClause.Occur.SHOULD)
  .build();

  SortField sortField = new SortField("attack", SortField.Type.DOUBLE, true);
  Sort sort = new Sort(sortField);

  TopDocs search = indexSearcher.search(typeQuery, 1000, sort);

  for (int i = 0; i < search.totalHits; i++) {
    Document doc = indexSearcher.doc(search.scoreDocs[i].doc);
    System.out.println(doc.get("name") + doc.get("attack"));
  }
}

공격순으로 정렬이 잘 됩니다.

하지만 또..

문제점이 또 있습니다.

이전 튜토리얼에서 진행한 공격력이 120 이상 땅타입 포켓몬 테스트가 실패했습니다.

DoublePoint에서 NumericDocValuesField로 바뀌면서, sorting은 되지만 이제 range 검색이 되지 않습니다.

range 검색도 함께 되게 하려면,
똑같은 필드 이름으로 DoublePoint 타입 필드를 하나 더 만들면 됩니다.

아래와 같은 의문이 들 수 있습니다.

동일한 필드 이름으로 DoublePoint 타입 필드를 만들면, NumericDocValuesField값을 엎어치는 것 아닐까?

엎어치지 않습니다.

각각의 필드는 따로 저장되며, 기능도 서로 영향을 주지 않습니다.
따라서 정렬이 됨과 동시에 Range 검색도 가능하도록 구성할 수 있습니다.

필드 추가 후 테스트 재실행

DoublePoint 필드 타입을 다시 추가하겠습니다.


private static void indexDouble(Document document, String pokemonStatValue, String statFieldName) {

  // sorting을 위한 필드
  NumericDocValuesField field = new DoubleDocValuesField(statFieldName, Double.parseDouble(pokemonStatValue));

  // range 검색을 위한 필드
  DoublePoint doublePoint = new DoublePoint(statFieldName, Double.parseDouble(pokemonStatValue));

  // 검색 결과에서 값을 확인하기 위한 필드
  StoredField storedField = new StoredField(statFieldName, Double.parseDouble(pokemonStatValue));

  document.add(field);
  document.add(doublePoint);
  document.add(storedField);
}

테스트를 재수행하면,
Range 쿼리(공격력이 120 이상 땅타입 포켓몬),  Sort 쿼리(공격력이 가장 큰 불타입 포켓몬) 모두 성공합니다.

Elasticsearch 쿼리라면

공격력이 가장 큰 불타입 포켓몬 5마리 조건을 elasticsearch 쿼리로 표현했습니다.
Lucene 쿼리와 구조가 유사합니다.

{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "type1": "fire"
          }
        },
        {
          "term": {
            "type2": "fire"
          }
        }
      ]
    }
  },
  "sort": {
    "attack": "desc"
  }
}

0개의 댓글