我正在使用Lucene 6.3,但我无法弄清楚以下非常基本的搜索查询有什么问题。它只是添加每个具有单个日期范围的文档,然后尝试搜索应该找到两个文档的更大范围。有什么问题?
有内联注释应该使exmaple非常自我解释。如果有什么不清楚,请告诉我。
请注意,我的主要要求是能够与其他字段查询(例如
)一起执行日期范围查询text:interesting date:[2014 TO NOW]
这是在观看Lucene spatial deep dive video介绍后介绍DateRangePrefixTree和策略所基于的框架。
Rant:感觉好像我在这里犯了任何错误,我应该在查询或写作上得到一些验证错误,因为我的例子是多么简单。
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.spatial.prefix.NumberRangePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.DateRangePrefixTree;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
public class TestLuceneDatePrefix {
/*
All these names should be lower case as field names are case sensitive in Lucene.
*/
private static final String NAME = "name";
public static final String TIME = "time";
private Directory directory;
private StandardAnalyzer analyzer;
private ScoreDoc lastDocOnPage;
private IndexWriterConfig indexWriterConfig;
@Before
public void setup() {
analyzer = new StandardAnalyzer();
directory = new RAMDirectory();
indexWriterConfig = new IndexWriterConfig(analyzer);
}
@Test
public void testAddDocumentAndSearchByDate() throws IOException {
IndexWriter w = new IndexWriter(directory, new IndexWriterConfig(analyzer));
// Responsible for creating the prefix string / geohash / token to identify the date.
// aka Create post codes
DateRangePrefixTree prefixTree = new DateRangePrefixTree(DateRangePrefixTree.JAVA_UTIL_TIME_COMPAT_CAL);
// Strategy indexing the token.
// aka transform post codes into tokens that make them efficient to search.
PrefixTreeStrategy strategy = new NumberRangePrefixTreeStrategy(prefixTree, TIME);
createDocument(w, "Bill", new Date(2017,1,1), prefixTree, strategy);
createDocument(w, "Ted", new Date(2018,1,1), prefixTree, strategy);
w.close();
// Written the document, now try query them
DirectoryReader reader;
try {
QueryParser queryParser = new QueryParser(NAME, analyzer);
System.out.println(queryParser.getLocale());
// Surely searching only on year for the easiest case should work?
Query q = queryParser.parse("time:[1972 TO 4018]");
// The following query returns 1 result, so Lucene is set up.
// Query q = queryParser.parse("name:Ted");
reader = DirectoryReader.open(directory);
IndexSearcher searcher = new IndexSearcher(reader);
TotalHitCountCollector totalHitCountCollector = new TotalHitCountCollector();
int hitsPerPage = 10;
searcher.search(q, hitsPerPage);
TopDocs docs = searcher.search(q, hitsPerPage);
ScoreDoc[] hits = docs.scoreDocs;
// Hit count is zero and no document printed!!
// Putting a dependency on mockito would make this code harder to paste and run.
System.out.println("Hit count : "+hits.length);
for (int i = 0; i < hits.length; ++i) {
System.out.println(searcher.doc(hits[i].doc));
}
reader.close();
}
catch (ParseException e) {
e.printStackTrace();
}
}
private void createDocument(IndexWriter w, String name, Date fromDate, DateRangePrefixTree prefixTree, PrefixTreeStrategy strategy) throws IOException {
Document doc = new Document();
// Store a text/stored field for the name. This helps indicate that Lucene is orking.
doc.add(new TextField(NAME, name, Field.Store.YES));
//offset toDate
Calendar cal = Calendar.getInstance();
cal.setTime( fromDate );
cal.add( Calendar.DATE, 1 );
Date toDate = cal.getTime();
// This lets the prefix tree create whatever tokens it needs
// perhaps index year, date, second etc separately, hence multiple potential tokens.
for (IndexableField field : strategy.createIndexableFields(prefixTree.toRangeShape(
prefixTree.toUnitShape(fromDate), prefixTree.toUnitShape(toDate)))) {
// Debugging the tokens produced is difficult as I can't intuitively look at them and know if they are valid.
doc.add(field);
}
w.addDocument(doc);
}
}
更新
我想也许答案是使用SimpleAnalyzer与StandardAnalyzer相比,但这似乎也不起作用。
我能够解析用户日期范围的要求似乎是catered by SOLR,所以我希望这是基于Lucene的功能。
答案 0 :(得分:0)
QueryParser对于搜索空间字段没有用,并且分析器不会有任何区别。分析器旨在标记和转换文本。因此,它们不被空间字段使用。同样,QueryParser主要面向文本搜索,不支持空间查询。
您需要使用空间查询进行查询。特别是,AbstractPrefixTreeQuery的子类将很有用。
例如,如果我想查询时间字段是包含 2003 - 2005年的范围的文档,我可以创建一个类似的查询:
Shape queryShape = prefixTree.toRangeShape(
prefixTree.toUnitShape(new GregorianCalendar(2003,1,1)),
prefixTree.toUnitShape(new GregorianCalendar(2005,12,31)));
Query q = new ContainsPrefixTreeQuery(
queryShape,
"time",
prefixTree,
10,
false
);
因此,这将匹配已编入索引的文档,例如,范围为2000-01-01至2006-01-01。
或者以相反的方式匹配范围完全在查询范围内的所有文档:
Shape queryShape = prefixTree.toRangeShape(
prefixTree.toUnitShape(new GregorianCalendar(1990,1,1)),
prefixTree.toUnitShape(new GregorianCalendar(2020,12,31)));
Query q = new WithinPrefixTreeQuery(
queryShape,
"time",
prefixTree,
10,
-1,
-1
);
关于参数的注释:我并不真正理解这些查询的一些参数,特别是detailLevel和prefixGridScanLevel。没有找到任何关于它们如何工作的文档。这些值似乎适用于我的基本测试,但我不知道最佳选择是什么。
答案 1 :(得分:0)
首先,QueryParser可以解析日期并默认生成TermRangeQuery。请参阅生成TermRangeQuery的默认解析器的以下方法。
org.apache.lucene.queryparser.classic.QueryParserBase#getRangeQuery(java.lang.String, java.lang.String, java.lang.String, boolean, boolean)
这假设您将日期作为字符串存储在lucene数据库中,如果使用了SimpleAnalyzer或等效函数,则效率稍低但可以直接使用。
或者你可以将日期存储为LongPoint,这对于日期场景来说是最有效的,根据我上面的问题,其中日期是一个时间点,每个字段存储一个日期。
Calendar fromDate = ...
doc.add(new LongPoint(FIELDNAME, fromDate.getTimeInMillis()));
但是就像在DatePrefixTree中建议的那样,这需要编写硬编码查询。
Query pointRangeQueryHardCoded = LongPoint.newRangeQuery(FIELDNAME, fromDate.getTimeInMillis(), toDate.getTimeInMillis());
如果使用生成LongPoint范围查询的版本覆盖以下方法,则可以重复使用QueryParser。
org.apache.lucene.queryparser.classic.QueryParserBase#newRangeQuery(java.lang.String, java.lang.String, java.lang.String, boolean, boolean)
这也适用于datePrefix树版本,但只有在以下情况下才能使用此方案:
使查询解析器具有方便的术语,捕获我想象的所有相关场景,这对于最后一种情况来说是相当多的工作。
另外请小心不要将日期(年,月,日)与GregorianCalendar(年,月,日)混合,因为参数不相等会导致问题。
请参阅java.util.Date#Date(int, int, int)
,了解参数的不同之处以及不推荐使用此构造函数的原因。根据问题中的代码,这引起了我的注意。
再次感谢femtoRgon指出空间搜索的机制,但最终这对我来说不是。