2017-01-18 64 views
1

我正在使用Lucene 6.3,但我無法弄清楚以下非常基本的搜索查詢有什麼問題。它只是添加到具有單個日期範圍的文檔中,然後嘗試在更大範圍上搜索應找到這兩個文檔。哪裏不對?使用Lucene空間搜索日期範圍查詢/ DateRangePrefixTree?

有內嵌評論應該使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

我想也許答案是使用SimpleAnalyzer與StandardAnalyzer相比,但這似乎並不奏效。 –

回答

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。還沒有找到任何有關他們如何工作的文件。這些值似乎在我的基本測試中有效,但我不知道選擇會是什麼。

+0

感謝您的詳細回答,這表明我期望Lucene能夠在查詢解析方面完成繁重的工作。我認爲這是因爲這似乎在SOLR(鏈接添加)中可用,我認爲這將在Lucene層進行編碼。進一步的咆哮:分析器API令人困惑,因爲它被添加到共享索引編寫器中,而不是英文文本字段。我需要測試一下你的解決方案,並說服自己,在我標記正確之前沒有其他選擇。再次感謝。 –

0

首先,QueryParser可以解析日期並默認生成TermRangeQuery。查看產生TermRangeQuery的默認解析器的以下方法。

org.apache.lucene.queryparser.classic.QueryParserBase#getRangeQuery(java.lang.String, java.lang.String, java.lang.String, boolean, boolean) 

這是假定你會被存儲日期爲Lucene的數據庫,這是有點低效率的,但工程開箱串,提供了SimpleAnalyzer或使用等效的。

或者,您可以將日期存儲爲日期場景中最有效的日期,其中日期是時間點,每個字段存儲一個日期。

Calendar fromDate = ... 
doc.add(new LongPoint(FIELDNAME, fromDate.getTimeInMillis())); 

但是這裏就像爲DatePrefixTree建議的那樣,這需要編寫硬編碼的查詢。

Query pointRangeQueryHardCoded = LongPoint.newRangeQuery(FIELDNAME, fromDate.getTimeInMillis(), toDate.getTimeInMillis()); 

即使在這裏,如果使用生成長點範圍查詢的版本覆蓋以下方法,也可以重用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指出空間搜索的機制,但最終這不是我走的路。