포켓몬으로 알아보는 루씬 (3) Range Query

jinwook han·2022년 3월 2일
0

이번 글에서는 다음 쿼리를 완성하겠습니다.
조건은 공격력이 120 이상인 땅타입 포켓몬 입니다.
Range Query를 사용합니다.

@Test
void 공격력이_120_이상_땅타입_포켓몬() throws IOException {
// TODO
}

조건 분석

두 가지 조건을 동시에 만족하도록 예시를 만들었습니다.
타입이 땅이어야 하고, 공격력이 120이상 이어야 한다.

타입이 땅인지 여부 쿼리는 이전 글에서 썼듯이, Boolean query를 사용합니다. 포켓몬은 타입이 2가지이므로, 두 타입 중 한 개라도 타입이 땅인지를 확인하겠습니다.
공격력이 120이상인지 여부는 Range query를 활용합니다.

또한 타입이 땅이어야 하고, 공격력이 120이상이어야 하므로, and를 표현하기 위해 바깥에 Boolean query가 한 개 더 필요합니다.

쿼리

아래는 완성된 쿼리입니다.

Term type1GroundTerm = new Term("type1", "ground");

TermQuery type1GroundTermQuery = new TermQuery(type1GroundTerm);
Term type2GroundTerm = new Term("type2", "ground");
TermQuery type2GroundTermQuery = new TermQuery(type2GroundTerm);

// 공격력이 120이상 130이하인지 확인
Query doubleQuery = DoublePoint.newRangeQuery("attack", 120, 130);

// 땅 타입인지 확인
BooleanQuery groundTypeQuery = new BooleanQuery.Builder()
.add(type1GroundTermQuery, BooleanClause.Occur.SHOULD)
.add(type2GroundTermQuery, BooleanClause.Occur.SHOULD)
.build();

// 공격력이 120~130이고, 땅 타입인지 확인
BooleanQuery outerQuery = new BooleanQuery.Builder()
.add(groundTypeQuery, BooleanClause.Occur.FILTER)
.add(doubleQuery, BooleanClause.Occur.FILTER)
.build();

타입이 땅인지 구분하는 BooleanQuery와 120이상 130 이하인지 확인하는 Range query입니다. (Range query에서 상한선은 필수이므로, 최대값인 130을 상한선으로 넣음)

타입 조건과 공격력 조건을 and 조건(FILTER)로 묶어주는 바깥 Boolean query도 있습니다.

결과 확인, 그런데..

IndexSearcher를 만들었습니다.
이제 테스트를 돌려서, 조회 결과를 확인하면 됩니다.

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

  IndexSearcher indexSearcher = new IndexSearcher(directoryReader);

  Term type1GroundTerm = new Term("type1", "ground");
  TermQuery type1GroundTermQuery = new TermQuery(type1GroundTerm);
  Term type2GroundTerm = new Term("type2", "ground");
  TermQuery type2GroundTermQuery = new TermQuery(type2GroundTerm);

  Query doubleQuery = DoublePoint.newRangeQuery("attack", 120, 130);

  BooleanQuery groundTypeQuery = new BooleanQuery.Builder()
  .add(type1GroundTermQuery, BooleanClause.Occur.SHOULD)
  .add(type2GroundTermQuery, BooleanClause.Occur.SHOULD)
  .build();

  // 공격력이 120~130이고, 땅 타입인지 확인
  BooleanQuery outerQuery = new BooleanQuery.Builder()
  .add(groundTypeQuery, BooleanClause.Occur.FILTER)
  .add(doubleQuery, BooleanClause.Occur.FILTER)
  .build();

  TopDocs search = indexSearcher.search(outerQuery, 1000);

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

그런데.. attack 값이 무엇인지 조회 결과로 나오지 않습니다.

이름, type1, type2, attack이 출력 순서입니다.
네번째 순서인, attack이 출력되지 않고 null로 나오는 이유는 다음과 같습니다.

attack(공격력)은 DoublePoint 타입으로 저장되고 있으며, DoublePoint는 조회 결과로 볼 수 없기 때문입니다.
조회 조건으로는 활용 가능하지만(ex: 공격력 120 이상), 조회 결과로서 공격력은 읽을 수 없습니다.

특정 데이터를 저장하면, 당연히 그 데이터를 볼 수 있다고 저는 생각했습니다.
하지만 루씬에서는 데이터를 저장하는 것과, 그 데이터를 볼 수 있는지 여부가 분리되어 있습니다.

위 검색 결과에서 attack값을 확인할 수 있으려면 어떻게 해야할까요?

"attack" 필드 이름을 갖지만, 하지만 타입이 다른 필드를 하나 더 만드는 방법이 있습니다.

attack 값을 StoredField로 만들면, 상세 내용에서 attack 값을 확인할 수 있다.

똑같은 필드 이름("attack")으로 다른 타입의 필드를 만들 경우, 기존에 있던 DoublePoint 타입의 값을 엎어치는 것 아닌가?
위와 같은 의문점이 들 수 있습니다. 하지만 엎어치지 않습니다.
DoublePoint로 range 검색이 가능하게 하고, 동시에 StoredField로 문서 상세 내용에서 확인이 가능하도록 만들 수 있습니다.

StoredField 추가 후 검색 결과 확인

StoredField를 추가로 인덱싱하도록 인덱싱 코드를 바꿔줍니다.

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

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

모든 더블형 데이터에 대해서 두 개의 필드를 인덱싱합니다.
하나는 DoublePoint, 하나는 StoredField입니다.

테스트를 다시 실행했습니다.

공격력 필드가 null이 아니라, 실제 값이 출력됩니다.
이제 문서 상세에서 공격력 필드도 함께 확인할 수 있습니다.

Elasticsearch 쿼리로 하면

조건 타입이 땅이어야 하고, 공격력이 120이상 이어야 한다.을 elasticsearch 쿼리로 바꾸면 아래와 같습니다.


{
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "attack": {
              "gte": 120,
              "lte": 130
            }
          }
        },
        {
          "bool": {
            "should": [
              {
                "term": {
                  "type1": "ground"
                }
              },
              {
                "term": {
                  "type2": "ground"
                }
              }
            ]
          }
        }
      ]
    }
  }
}

루씬 쿼리와 구조가 비슷합니다.

0개의 댓글