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都还没有现成的实现例子。