自己动手写分布式搜索引擎
上QQ阅读APP看书,第一时间看更新

3.3.7 跨度查询

若查询词是“吃饭”,而文档中出现了“吃完饭”这样的词。把查询词和文档都按最小粒度分词,分成“吃”和“饭”两个词。文档中的这两个词虽然不是连续出现,但只间隔了一个词。

近似查询假设同时查询的几个词之间的匹配点很近,则这样的文档可能也是要查找的。匹配词在文档中的位置信息用跨度(Spans)描述。抽象类Spans封装了一次匹配的文档和位置信息。span是一个<文档编号,开始位置,结束位置>的三元组。

Spans的接口定义如下:

        boolean next() //移动到下一个匹配的文档
        boolean skipTo(int target)//跳到指定的文档
        int doc()//当前的文档编号
        int start()//匹配区域的开始位置
        int end() //匹配区域的结束位置

TermSpans是Spans的具体子类。

SpanQuery称为跨度查询,用于查询多个词时考虑几个词在文档中的匹配位置。SpanQuery和PhraseQuerys或者MultiPhraseQuerys很相似,因为都是通过位置限制匹配,但是SpanQuery更灵活。

SpanNearQuery用来查询在比较近的区域内出现的多个查询词。例如,下面这个查询既可以匹配“吃饭”,又可以匹配“吃完饭”。

        new SpanNearQuery(new SpanQuery[] {
          new SpanTermQuery(new Term(FIELD, "吃")), //第1个词
          new SpanTermQuery(new Term(FIELD, "饭"))},  //第2个词
          1, //在1个位置以内
          true); //有序

SpanTermQuery是一个最基础的跨度搜索实现类,因为可以通过它得到一个词的位置信息。在SpanNearQuery的构造方法中可以指定一些查询词需要在多近的区域出现。

同样的几个词在一起,但是如果顺序不一样,意思可能就不一样。例如,“花的中间”与“中间的花”意义就不一样,类似的例子还有“妈妈爱自己的儿女”和“儿女爱自己的妈妈”。所以,SpanNearQuery有一个参数表示是否要求按数组中的顺序匹配。

例如,需要查找lucene和doug在5个位置以内,doug在lucene之后,也就是说,词出现的顺序是需要考虑的。可以使用如图3-11所示的SpanNearQuery。

图3-11 SpanNearQuery

        SpanQuery[] baseQueries = new SpanQuery[] {
                        new SpanTermQuery(new Term(FIELD, "lucene")),
                        new SpanTermQuery(new Term(FIELD, "doug"))};
        //这里的true表示词必须按数组中的顺序出现在文档中
        new SpanNearQuery(baseQueries, 5, true);

在这个例子中,lucene和doug的间隔在3以内,也就是间隔了3个词。

SpanNearQuery会查找互相在给定距离内的一些数量的SpanQuery。可以指定span是按顺序或者不按顺序查询。SpanNearQuery根据SpanQuery对象组成的数组来构建。因为SpanTermQuery是SpanQuery的子类,所以可以根据SpanTermQuery构造出SpanNearQuery。

SpanNearQuery构造方法接收一个SpanQuery数组。span之间的距离,一个布尔值表明是否要求按SpanQuery数组中的顺序出现。下面是另一个嵌套的例子。这次,查找doug在lucene之后的5个间隔内,而hadoop在lucene → doug之后的4个间隔内,如图3-12所示。

图3-12 嵌套的SpanNearQuery

        SpanNearQuery spanNear = new SpanNearQuery(new SpanQuery[] {
            new SpanTermQuery(new Term(FIELD, "lucene")),
            new SpanTermQuery(new Term(FIELD, "doug"))},
            5,
            true);


        new SpanNearQuery(new SpanQuery[] {
            spanNear,
            new SpanTermQuery(new Term(FIELD, "hadoop"))},
            4,
            true);

有些列,例如标题,头几个词可能更重要,可以使用SpanFirstQuery限定只查询前几个词。例如,查找名称以“玩具”开头的产品。

        SpanTermQuery toy = new SpanTermQuery(new Term("title", "玩具"));
        SpanFirstQuery first = new SpanFirstQuery(toy, 1); //限定在头一个位置出现

可以把SpanFirstQuery看成是SpanNearQuery的特例,第一个词是一个虚拟的开始词,从第二个词开始才是真正的匹配词。

例如,有两个文档“I love Lucene”和“Lucene is nice”,想要能够查询Lucene出现在最开始的文档,也就是匹配正则表达式“^Lucene .*”的文档。

        SpanTermQuery lucene = new SpanTermQuery(new Term("title", "Lucene"));
        SpanFirstQuery first = new SpanFirstQuery(lucene, 1); //限定在头一个位置出现

SpanRegexQuery使用标准的正则表达式语法。例如,查询年:2000, 2001, 2002, ..., 2009。

        SpanRegexQuery srq = new SpanRegexQuery(new Term("year", "200.? "));

SpanNotQuery包含必须满足的SpanQuery和必须排除的SpanQuery。例如,要查找Microsoft Windows,但是不匹配Microsoft Windows之后的文档是大写的,伪代码如下:

        SpanNot:
            include:
              SpanNear(in-order=true, slop=0):
                  SpanTerm: "Microsoft"
                  SpanTerm: "Windows"
            exclude:
              SpanNear(in-order=true, slop=0):
                  SpanTerm: "Microsoft"
                  SpanTerm: "Windows"
                  SpanRegex: "^\\p{Lu}.*"

实际的代码:

        SpanNearQuery includeQuery = new SpanNearQuery(new SpanQuery[] {
          new SpanTermQuery(new Term(FIELD, "Microsoft")),
          new SpanTermQuery(new Term(FIELD, "Windows"))},
          0,
          true);


        SpanRegexQuery upperCaseQuery = new SpanRegexQuery( new Term(FIELD,
        "^\\p{Lu}.*"));
        SpanNearQuery excludeQuery = new SpanNearQuery(new SpanQuery[] {
          new SpanTermQuery(new Term(FIELD, "Microsoft")),
          new SpanTermQuery(new Term(FIELD, "Windows")),
          upperCaseQuery },
          0,
          true);

若想查找包含“喜欢”一词,并且在其前面没有“不”字的文档。可以这样:

        SpanNotQuery(like, SpanNearQuery(not, like))

再扩展出同类查询词,也就是:

        SpanNotQuery(
              SpanOrQuery(喜欢,爱...)
              SpanNearQuery(
                    SpanOrQuery(不)
                    SpanOrQuery(喜欢,爱...)
                    )
              )

SpanOrQuery可以匹配符合任何一个条件的文档。

        SpanTermQuery term1 = new SpanTermQuery(new Term("field", "thirty"));
        SpanTermQuery term2 = new SpanTermQuery(new Term("field", "three"));
        SpanNearQuery near1 = new SpanNearQuery(new SpanQuery[] {term1, term2},
                                          0, true);
        SpanTermQuery term3 = new SpanTermQuery(new Term("field", "forty"));
        SpanTermQuery term4 = new SpanTermQuery(new Term("field", "seven"));
        SpanNearQuery near2 = new SpanNearQuery(new SpanQuery[] {term3, term4},
                                          0, true);


        SpanOrQuery query = new SpanOrQuery(new SpanQuery[] {near1, near2});

例如,说一个人坏透了,用“头上长疮,脚下流脓”来形容。这个条件可以这样描述:

        SpanAndQuery(
              SpanFirstQuery(疮)
              SpanLastQuery(脓)
        )

SpanAndQuery和SpanLastQuery都还没有现成的实现例子。