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

使用Lucene空间搜索/DateRangePrefix Tree的日期范围查询?

范高刚
2023-03-14

我使用Lucene 6.3,但我无法找出以下非常基本的搜索查询有什么问题。它只是添加到具有单个日期范围的文档中,然后尝试在更大的范围内搜索,应该可以找到两个文档。什么是错的?

有内联注释应该使示例非常不言自明。如果有什么不清楚的地方,请告诉我。

请注意,我的主要要求是能够执行日期范围查询以及其他字段查询,例如

text:interesting date:[2014 TO NOW]

这是在观看了Lucene空间深潜视频介绍之后,介绍了DateRangePrefix Tree和策略所基于的框架。

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

更新:

>

我对能够解析用户日期范围的需求似乎是由SOLR满足的,所以我希望这是基于Lucene功能的。

共有2个答案

韩羽
2023-03-14

QueryParser对于搜索空间字段没有用,分析器也不会有任何影响。分析器旨在标记和转换文本。因此,它们不被空间字段使用。同样,QueryParser主要面向文本搜索,不支持空间查询。

您需要使用空间查询进行查询。特别是AbstractPrefixTreeQuery的子类将非常有用。

  • containsRefExtreeQuery

例如,如果我想查询时间字段范围为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
  );

关于参数的注意事项:我真的不理解这些查询的一些参数,特别是细节级别和前缀GridScanLevel。还没有找到任何关于它们工作原理的留档。这些值似乎在我的基本测试中起作用,但我不知道最好的选择是什么。

百里诚
2023-03-14

首先,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());

即使在这里,如果以下方法被生成长点范围查询的版本覆盖,也可以重用QueryParser。

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

对于datePrefix树版本也可以这样做,但是只有当:

  • 你想通过一些不寻常的标记进行搜索(我相信它可以容纳星期一)。
  • 每个文档字段有多个日期。
  • 您正在存储需要查询的日期范围。

对查询解析器进行调整,使其具有一个方便的术语来捕获所有相关的场景,我认为这将是最后一个案例的大量工作。

此外,请注意不要将日期(年、月、日)与GregoriaCalendar(年、月、日)混用,因为参数不相等,会导致问题。

请参见java.util.Date#Date(int, int, int)了解参数的不同程度以及不建议使用此构造函数的原因。根据问题中的代码,这让我明白了。

再次感谢femtoRgon指出了空间搜索的机制,但最终这不是我要走的路。

 类似资料:
  • 我有一个lucene查询,可以对索引字段进行全文搜索。我想将日期范围添加到此查询。 我找到了这个问题并在那里使用了答案:如何在日期之间搜索(Hibernate搜索)? 但当我想在两个日期之间获取数据时,它什么也不返回。我使用的是MSSQL数据库,日期字段的类型是datetime。但它在实体类中被注释为。 这是我的实体类: 这就是我进行全文搜索的地方: 有两种不同的查询。一个是在指定字段上进行全文通

  • 我正在使用Lucene 8.2。Java11中的0。 我试图索引一个值,以便我可以使用范围查询过滤它,例如:。然而,任何变体,甚至,返回都会导致这个最小的示例。一旦我从其中删除使其成为,我就会得到结果。 所以我在想我一定是在索引上犯了一个错误,但是我不知道它可能是什么。 从JavaDoc: 用于快速范围过滤器的索引长字段。如果还需要存储该值,则应添加一个单独的StoredField实例。在搜索时查

  • 我有一个大约为100GB的cosmos数据库。我成功地创建了一个漂亮的分区键,我在70M记录上有大约4600个分区,但是我仍然需要查询两个存储为字符串的日期时间字段,而不是纪元格式。 示例json: 我注意到当我做中选择*以及当我做

  • 版本字符串有一个支持java类(版本),它实现了Comparable。 我的分析器是一个分析器包装器,它是一个小写和空格分析器,类似于内置的分析器。我使用经典的查询解析器进行搜索。在确切的条件下搜索工作良好。 我想做的是: 我试图在索引之前将版本字符串转换为int,但查询输入需要以某种方式转换,以便在搜索之前将版本字符串转换为int。 看起来我必须为version字段实现一个自定义分析器,但是我在

  • 使用Lucene libs,我需要对现有的搜索函数进行一些更改:假设以下对象: 名称:“端口对象1” 数据:"TCP(1)/1000-2000" 查询(或搜索文本)为“1142”,是否可以在数据字段内搜索“1142”并找到端口对象1,因为它指的是1000-2000之间的范围? 我只找到了数值范围查询,但这不适用于本例,因为我不知道范围。。。 参考以上代码。查询"1200"应该找到第一个doc。 L

  • 我的任务是使用lucene在我们的产品表中搜索。我已经创建了一个索引,正在使用带有多个字段的QueryParser进行搜索,但结果不是我所需要的。我有一个存储为LM10的产品,但如果搜索词是LM 10,我希望能够找到它,但如果搜索词是Fred LM10或Fred LM 10,它也必须能够匹配。你知道我如何在Lucene做到这一点吗。 提前谢谢