Solr Multivalue field的索引和搜索

Solr里头可以设计Field为Multivalue类型,这样的一个好处是可以很方便的设置copyField,在我们的项目中也有使用。

但是一直以来都有一个问题困扰着我,就是对multivalue里头多个值域的搜索问题。多个value之间我认为应该是保持相互独立的,但是在实际搜索中感觉solr把所有的值域都串在一起,当作一个长value来处理,没有达到我想要的效果。后来通过搜索,我发现solr的field type中有一个 positionIncrementGap字段,貌似可以解决我的问题,在网上看到一段讨论: http://lucene.472066.n3.nabble.com/positionIncrementGap-in-schema-xml-td488338.html。其中有人举了一个例子:
Suppose a document has a multi-valued "author" field.   Like this: 

author: John Doe 
author: Bob Smith 

With a position increment gap of 0, a phrase query of "doe bob" would   
be a match.  But often it is undesirable for that kind of match across   
different field values.  A position increment gap controls the virtual   
space between the last token of one field instance and the first token   
of the next instance.  With a gap of 100, this prevents phrase queries   
(even with a modest slop factor) from matching across instances. 

一看之下大喜过望,这不是就是我想要的效果么 Solr Multivalue field的索引和搜索 - rockiee281 - rockiee281的博客 马上去翻我solr的schema.xml的配置,一看我就凉了:
 
   

name = "textComplex" class = "solr.TextField" positionIncrementGap = "100" >
type = "query" >
    class = "com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode = "complex" dicPath = "dic" >
class = "solr.SynonymFilterFactory" synonyms = "synonyms_filter.txt" ignoreCase = "true" expand = "false" />
class = "solr.StandardFilterFactory" />  
type = "index" >
    class = "com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode = "complex" dicPath = "dic" >
class = "solr.SynonymFilterFactory" synonyms = "synonyms_filter.txt" ignoreCase = "true" expand = "false" />
class = "solr.StandardFilterFactory" />  

居然之前已经配置过了!!!那就是这个配置没有生效,是什么造成的呢?因为我没有使用solr自己的standardTokenizerFactory,而是为了中文分词使用了MMSeg4J的类,我就把怀疑的目光放到了MMSeg4J身上。检查了 MMSegTokenizerFactory的源代码,发现里头木有对 positionIncrementGap的处理,以为问题出在这里,但是在深入对比 MMSegTokenizerFactory和 StandardTokenizerFactory及相关的代码类之后,感觉问题不应该出在建立索引的环节。

之后就是搜了一堆的资料,了解了 positionIncrementGap这个字段的含义 ,其作用就是在对Multivalue Field进行处理的时候,给两个field中相隔的词人为的插入一段固定的distance,然后在使用Lucene/Solr做Phrase query的时候,如果没有指定Slop (对slop的介绍,可以参考: http://blog.csdn.net/rick_123/article/details/6708527 ) ,会默认Slop为0,即查询的短语之间应该紧紧挨着,这样对很多情况下都得不到用户想要的结果。解决的办法就是使用phrase query,同时设置一个适当的Slop值,然后为了不让lucene的搜索跨越多个Field Value,设置一个远大于slop的 positionIncrementGap,就可以达到目标。在这里不用担心 positionIncrementGap设置过大会影响效率,尽情的设吧……

既然了解 positionIncrementGap的含义,问题就一目了然,楼主为了查询的方便,使用自定的QueryParser替换了Solr自己默认的,将Phrase search改为了BooleanSearch,所以实际上导致了 positionIncrementGap的失效。解决办法就是将BooleanSearch改为 MultiPhraseQuery,同时调用MultiPhraseQuery.setSlop(int slop)方法设置slop为50(经验值,根据索引的数据设定,只要远小于 positionIncrementGap即可 )。测试ok达到效果!自定义的queryparser代码如下:

 
   

protected Query getFieldQuery(String field, String queryText, boolean quoted) throws ParseException { String myField = field == null ? defaultField : field; if (myField != null) { FieldType ft = schema.getField(myField).getType(); if (ft instanceof TextField && "keywordtext".equals(myField)) { // 只对keywordtext字段做特殊处理 try { System.out.println("queryText:" + queryText); //此字段的分词器 Analyzer analyzer = ft.getQueryAnalyzer() == null ? ft.getAnalyzer() : ft.getQueryAnalyzer(); System.out.println("getPositionIncrementGap:" + analyzer.getPositionIncrementGap("keywordtext")); if (analyzer != null) { // 对同义词进行处理 // queryText = SynonymFilterUtil.getSynonymWord(queryText); // BooleanQuery bq = new BooleanQuery();//(true); // TokenStream ts = analyzer.tokenStream(field, new StringReader(queryText)); // int endOffset = 0; // while (ts.incrementToken()) { // CharTermAttribute ta = (CharTermAttribute) ts.getAttribute(CharTermAttribute.class); // OffsetAttribute oa = (OffsetAttribute) ts.getAttribute(OffsetAttribute.class); // //顺序增用 and 关系 // if (oa.startOffset() >= endOffset) { // //sb.append(t.term()).append(' '); // bq.add(new TermQuery(new Term(myField, ta.toString())), Occur.MUST); // endOffset = oa.endOffset(); // } else { // //可以用分词相交用 or 关系 // //这里也使用 and 关系 // bq.add(new TermQuery(new Term(myField, ta.toString())), Occur.MUST); // } // } // System.out.println("BooleanQuery:" + bq); // return bq; MultiPhraseQuery mq = new MultiPhraseQuery(); queryText = com.netease.autosolr.solr.Synonym.SynonymFilterUtil.getSynonymWord(queryText); TokenStream ts = analyzer.tokenStream(field, new StringReader(queryText)); int endOffset = 0; while (ts.incrementToken()) { CharTermAttribute ta = (CharTermAttribute) ts.getAttribute(CharTermAttribute.class); OffsetAttribute oa = (OffsetAttribute) ts.getAttribute(OffsetAttribute.class); //顺序增用 and 关系 if (oa.startOffset() >= endOffset) { //sb.append(t.term()).append(' '); mq.add(new Term(myField, ta.toString())); endOffset = oa.endOffset(); } else { //可以用分词相交用 or 关系 //这里也使用 and 关系 mq.add(new Term(myField, ta.toString())); } } mq.setSlop(50); System.out.println("MultiQuery:" + mq); return mq; // PhraseQuery pq = new PhraseQuery(); // pq.add(new Term(myField,queryText)); // return pq; } } catch (Exception e) { throw new ParseException(e.getMessage()); } }//TextField }// myField != null return super.getFieldQuery(field, queryText, quoted); }

你可能感兴趣的:(solr)