如何使用 QueryParser 进行 Lucene 范围查询 (IntPoint/LongPoint)
How to use QueryParser for Lucene range queries (IntPoint/LongPoint)
我真正喜欢 Lucene 的一件事是查询语言,I/an 应用程序用户可以在其中编写动态查询。我通过
解析这些查询
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 但解析字符串呢?
这个问题是对这个问题的跟进,它指出了问题,但没有指出原因:
完整代码:
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.使用经典解析器
如您在问题中所示,经典解析器支持范围搜索,如 documented here。但是文档中需要注意的重点是:
Sorting is done lexicographically.
也就是说,它使用text-based排序来判断一个字段的值是否在范围内。
但是,您的字段是 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。使用标准查询解析器
这可能是一个很好的方法,它允许您继续使用您喜欢的语法,同时还支持 number-based 范围搜索。
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
值(如果查询使用词法排序,则会包括该值)。
我不知道有什么方法可以仅使用经典查询解析器获得相同的结果 - 但至少这是使用您希望使用的相同查询语法。
我真正喜欢 Lucene 的一件事是查询语言,I/an 应用程序用户可以在其中编写动态查询。我通过
解析这些查询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 但解析字符串呢?
这个问题是对这个问题的跟进,它指出了问题,但没有指出原因:
完整代码:
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.使用经典解析器
如您在问题中所示,经典解析器支持范围搜索,如 documented here。但是文档中需要注意的重点是:
Sorting is done lexicographically.
也就是说,它使用text-based排序来判断一个字段的值是否在范围内。
但是,您的字段是 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。使用标准查询解析器
这可能是一个很好的方法,它允许您继续使用您喜欢的语法,同时还支持 number-based 范围搜索。
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
值(如果查询使用词法排序,则会包括该值)。
我不知道有什么方法可以仅使用经典查询解析器获得相同的结果 - 但至少这是使用您希望使用的相同查询语法。