【问题标题】:How to use QueryParser for Lucene range queries (IntPoint/LongPoint)如何使用 QueryParser 进行 Lucene 范围查询(IntPoint/LongPoint)
【发布时间】:2021-02-19 04:02:54
【问题描述】:

我真正喜欢 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

这是查询解析器中的错误还是我错过了重要的一点,例如 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"));
        }
    }
}

【问题讨论】:

    标签: java lucene


    【解决方案1】:

    我认为这里有几点需要注意:

    1.使用经典解析器

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

    按字典顺序排序。

    也就是说,它使用基于文本的排序来确定字段的值是否在范围内。

    但是,您的字段是 LongPoint 字段(同样,正如您在代码中显示的那样)。该字段将您的数据存储为一个长的 array,如构造函数中所示。

    这不是字典数据——即使你只有一个值,它也不会作为字符串数据处理。

    假设这就是为什么以下查询无法按预期工作的原因 - 但我不能 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 是经典解析器的新替代品,但默认使用与经典解析器相同的语法。

    此解析器允许您配置“点配置映射”,它告诉解析器将哪些字段作为数字数据处理,以进行范围搜索等操作。

    例如:

    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 值(如果查询使用词法排序,则会包含该值)。

    我不知道仅使用经典查询解析器获得相同结果的任何方法 - 但至少这是使用您希望使用的相同查询语法。

    【讨论】:

    • 非常感谢您的详细回答,我不知道 StandardQueryParser(效果很好)。不知何故,我假设搜索格式是由文档字段 (LongPoint) 而不是查询本身定义的,但这是不正确的。此外,不同文档中的相同字段名称可以采用不同的格式/存储。
    猜你喜欢
    • 2018-01-12
    • 2011-06-28
    • 2013-07-24
    • 2017-06-03
    • 1970-01-01
    • 1970-01-01
    • 2012-02-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多