Solr Multivalue field的索引和搜索
2015-09-17 11:29
495 查看
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的schema.xml的配置,一看我就凉了:
<fieldtype name="textComplex" class="solr.TextField" positionIncrementGap="100"> <analyzer type="query"> <tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="complex" dicPath="dic"> </tokenizer> <filter class="solr.SynonymFilterFactory" synonyms="synonyms_filter.txt" ignoreCase="true" expand="false"/> <filter class="solr.StandardFilterFactory"/> </analyzer> <analyzer type="index"> <tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="complex" dicPath="dic"> </tokenizer> <filter class="solr.SynonymFilterFactory" synonyms="synonyms_filter.txt" ignoreCase="true" expand="false"/> <filter class="solr.StandardFilterFactory"/> </analyzer></fieldtype>
居然之前已经配置过了!!!那就是这个配置没有生效,是什么造成的呢?因为我没有使用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);
}
但是一直以来都有一个问题困扰着我,就是对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的schema.xml的配置,一看我就凉了:
<fieldtype name="textComplex" class="solr.TextField" positionIncrementGap="100"> <analyzer type="query"> <tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="complex" dicPath="dic"> </tokenizer> <filter class="solr.SynonymFilterFactory" synonyms="synonyms_filter.txt" ignoreCase="true" expand="false"/> <filter class="solr.StandardFilterFactory"/> </analyzer> <analyzer type="index"> <tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="complex" dicPath="dic"> </tokenizer> <filter class="solr.SynonymFilterFactory" synonyms="synonyms_filter.txt" ignoreCase="true" expand="false"/> <filter class="solr.StandardFilterFactory"/> </analyzer></fieldtype>
居然之前已经配置过了!!!那就是这个配置没有生效,是什么造成的呢?因为我没有使用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);
}
相关文章推荐
- Android异步更新UI的方式之使用runOnUiThread(action)方法
- finished with non-zero exit value 2
- UIScrollView的一些特点
- iOS开发:设置 UITabBar 的背景色
- easyUI window 拖不动的情况
- ESP8266-01 使用 Arduino IDE
- UIday1603:图片异步下载、KVO
- Redhat 6 Mounting a File System as Encrypted Guide
- Redhat6 File System Structure and Maintenance Guide
- Redhat FS-Cache Guide
- Redhat Network File System (NFS) Guide
- Redhat XFS File System Guide
- Redhat Global File System 2 Guide
- Redhat Ext4 File System Guide
- Build Active Architecture Only
- Get value from agent failed: cannot connect to [[ip]:10050]: [4] [Interrupted system call]
- 易启秀20150629完整包微场景制作源码,新增1.4G素材包,全新后台UI设计+采集
- Why GUID primary keys are a database’s worst nightmare
- 执行grunt build时js文件存放问题
- 通过Uid属性控制TextBlock组件的字体颜色