如何使用QueryParser进行Lucene范围查询(IntPoint/LongPoint)

ttygqcqt  于 2022-11-07  发布在  Lucene
关注(0)|答案(1)|浏览(288)

我非常喜欢Lucene的一点是它的查询语言,我/一个应用程序用户)可以用它来编写动态查询。

QueryParser parser = new QueryParser("", indexWriter.getAnalyzer());
Query query = parser.parse("id:1 OR id:3");

但这不适用于如下范围查询:

Query query = parser.parse("value:[100 TO 202]"); // Returns nothing
Query query = parser.parse("id:1 OR value:167"); // Returns only document with ID 1 and not 1

另一方面,通过API它的工作(但我给予了方便的方式,只是使用查询作为输入):

Query query = LongPoint.newRangeQuery("value", 100L, 202L); // Returns 1, 2 and 3

这是查询解析器中的一个bug吗?还是我忽略了一个重要的点,比如QueryParser接受的是词法值而不是数值?如果不使用查询API而是解析字符串,我怎么可能会遇到这种情况呢?
这个问题是这个问题的一个后续,指出了问题,但没有说明原因:Lucene LongPoint Range search doesn't work
完整代码:

package acme.prod;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;

public class LuceneRangeExample {

    public static void main(String[] arguments) throws Exception {
        // Create the index
        Directory searchDirectoryIndex = new RAMDirectory();
        IndexWriter indexWriter = new IndexWriter(searchDirectoryIndex, new IndexWriterConfig(new StandardAnalyzer()));

        // Add several documents that have and ID and a value
        List<Long> values = Arrays.asList(23L, 145L, 167L, 201L, 20100L);
        int counter = 0;
        for (Long value : values) {
            Document document = new Document();
            document.add(new StringField("id", Integer.toString(counter), Field.Store.YES));
            document.add(new LongPoint("value", value));
            document.add(new StoredField("value", Long.toString(value)));
            indexWriter.addDocument(document);
            indexWriter.commit();
            counter++;
        }

        // Create the reader and search for the range 100 to 200
        IndexReader indexReader = DirectoryReader.open(indexWriter);
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        QueryParser parser = new QueryParser("", indexWriter.getAnalyzer());
//        Query query = parser.parse("id:1 OR value:167");
//        Query query = parser.parse("value:[100 TO 202]");
        Query query = LongPoint.newRangeQuery("value", 100L, 202L);
        TopDocs hits = indexSearcher.search(query, 100);
        for (int i = 0; i < hits.scoreDocs.length; i++) {
            int docid = hits.scoreDocs[i].doc;
            Document document = indexSearcher.doc(docid);
            System.out.println("ID: " + document.get("id") + " with range value " + document.get("value"));
        }
    }
}
wwtsj6pe

wwtsj6pe1#

我认为这里有几件不同的事情需要注意:

1.使用传统解析器

正如您在问题中所展示的,经典解析器支持范围搜索,如本文所述。但文档中需要注意的关键点是:

  • 按字典顺序进行排序。*

也就是说,它使用基于文本的排序来确定字段的值是否在范围内。
但是,您的字段是一个LongPoint字段(同样,如代码中所示),该字段将数据存储为一个长整型 * 数组 *,如构造函数中所示。
这不是字典式数据--即使只有一个值,也不会作为字符串数据处理。
我 * 假设 * 这就是以下查询无法按预期工作的原因-但我不能100%肯定这一点,因为我没有找到任何文档证实这一点:

Query query = parser.parse("id:1 OR value:167");
Query query = parser.parse("value:[100 TO 202]");

(我对这些查询没有抛出错误感到有点惊讶)。

2.使用LongPoint查询

如前所述,您可以使用一个专门的LongPoint查询来获得所需的结果-在您的示例中,使用了LongPoint.newRangeQuery("value", 100L, 202L);
但是您也注意到,您失去了经典解析器语法的优点。

3.使用标准查询解析器

这可能是一个很好的方法,它允许您继续使用您喜欢的语法,同时还支持基于数字的范围搜索。
StandardQueryParser是一种较新的经典解析器的替代方法,但是默认情况下它使用与经典解析器相同的语法。
这个解析器允许您配置一个“points config map”,它告诉解析器哪些字段要作为数字数据处理,以便进行范围搜索等操作。
例如:

import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser;
import org.apache.lucene.queryparser.flexible.standard.config.PointsConfig;
import java.text.DecimalFormat;
import java.util.Map;
import java.util.HashMap;

...

StandardQueryParser parser = new StandardQueryParser();
parser.setAnalyzer(indexWriter.getAnalyzer());

// Here I am just using the default decimal format - but you can provide
// a specific format string, as needed:
PointsConfig pointsConfig = new PointsConfig(new DecimalFormat(), Long.class);
Map<String, PointsConfig> pointsConfigMap = new HashMap<>();
pointsConfigMap.put("value", pointsConfig);
parser.setPointsConfigMap(pointsConfigMap);

Query query1 = parser.parse("value:[101 TO 203]", "");

使用上面的查询运行索引搜索器代码会得到以下输出:

ID: 1 with range value 145
ID: 2 with range value 167
ID: 3 with range value 201

请注意,这正确地排除了20100L值(如果查询使用词法排序,则会包含该值)。
我不知道有什么方法可以只使用经典的查询解析器来获得相同的结果--但至少这是使用了您喜欢使用的相同查询语法。

相关问题