当前位置: 首页 > 知识库问答 >
问题:

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

萧繁
2023-03-14

关于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搜索不起作用

完整代码:

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"));
        }
    }
}

共有1个答案

步衡
2023-03-14

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

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.使用标准查询解析器

这可能是一种很好的方法,允许您继续使用首选语法,同时还支持基于数字的范围搜索。

Standard ardQueryParser是经典解析器的较新的替代方案,但默认情况下,它使用与经典解析器相同的语法。

这个解析器允许您配置一个“点配置映射”,它告诉解析器将哪些字段作为数字数据处理,用于范围搜索等操作。

例如:

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

我不知道只有使用经典的查询解析器才能获得相同的结果,但至少这使用了您希望使用的相同查询语法。

 类似资料:
  • 我正在使用Lucene 6 new,我想做一些范围搜索 使用搜索工作,并返回正确的文档,但是当我使用(经典)或新的时,不会返回任何内容。 这是窃听器还是我漏掉了什么?

  • 事情是这样的。我有一个术语存储在索引中,它包含特殊字符,比如'-',最简单的代码是这样的: 然后使用QueryParser创建一个查询,如下所示: 不使用QueryParser而直接使用TermQuery可以做我想做的事情,但是这种方式对于用户输入文本来说不够灵活。 我想可能StandardAnalyzer做了一些事情来省略查询字符串中的特殊字符。我尝试了debug,我发现字符串是拆分的,实际查询

  • 使用DynamoDB,没有简单的方法可以对列执行索引范围查询。主键、本地二级索引和全局二级索引都需要分区键来进行范围查询。 例如,假设我有一个带有数字分数属性的高分表。使用索引范围查询无法获得前10名或前25至50名 那么,执行这项极其常见的任务的惯用方式或首选方式是什么呢? > 接受表格扫描。 使用静态分区键并利用分区查询。 使用固定数量的静态分区键并使用多个分区查询。

  • 我只是好奇为什么Lucene不以标准的方式区分字符串和数值。。例如,范围查询的和,并在默认情况下将它们全部视为字符串。 是否有任何特殊原因将这两种情况都视为字符串值?

  • 我正在使用一个JPA查询,它使用一个规范来检索实体。当我执行查询时,我得到了一个错误: 组织。springframework。数据映射。PropertyReferenceException:找不到类型任务的属性名称! 我已经查看了之前在该网站上提出的类似问题的答案 当我使用调试器逐步检查代码时,条件生成器中的扩展路径将返回嵌入的ID类,但当规范实际用于查询时,该属性似乎正在应用于基本实体类。 我是

  • 可以对模型的查询和写入操作进行封装,例如: <?php namespace app\index\model; use think\Model; class User extends Model { public function scopeThinkphp($query) { $query->where('name','thinkphp')->field('i