设为首页 收藏本站
查看: 2037|回复: 0

[经验分享] solr特点三: defType(查询权重排序)

[复制链接]

尚未签到

发表于 2015-7-18 09:19:12 | 显示全部楼层 |阅读模式
  Solr的defType有dismax/edismax两种,这两种的区别,可参见:http://blog.iyunv.com/duck_genuine/article/details/8060026
  
  edismax支持boost函数与score相乘作为,而dismax只能使用bf作用效果是相加,所以在处理多个维度排序时,score其实也应该是其中一个维度 ,用相加的方式处理调整麻烦。
  而dismax的实现代码逻辑比较简单,看起来比较易理解,edismax是它的加强版,其实是改变了不少。。比如在以下:
  

  先看看dismax的解析主要实现思路:
  首先取出搜索字段名qf
  将最终解析成一个BooleanQuery
  先解析主mainQuery:


  • 用户主要是搜索串的解析
  • altQuery解析处理,看是否使用用户定义的后备搜索串
  • PhraseQuery解析组装


再解析bq查询,主要是额外加分的查询,不会影响搜索结果数,只会影响排序



再则是bf解析,函数搜索最后会以加的方式作用于文档评分


  看主要代码更清晰:
  







[java] view plaincopy

  • @Override  
  • public Query parse() throws ParseException {  
  •   SolrParams solrParams = SolrParams.wrapDefaults(localParams, params);

  •   queryFields = SolrPluginUtils.parseFieldBoosts(solrParams.getParams(DisMaxParams.QF));
  •   if (0 == queryFields.size()) {  
  •     queryFields.put(req.getSchema().getDefaultSearchFieldName(), 1.0f);  
  •   }

  •   /* the main query we will execute.  we disable the coord because
  •    * this query is an artificial construct
  •    */  
  •   BooleanQuery query = new BooleanQuery(true);  

  •   boolean notBlank = addMainQuery(query, solrParams);  
  •   if (!notBlank)  
  •     return null;  
  •   addBoostQuery(query, solrParams);
  •   addBoostFunctions(query, solrParams);

  •   return query;  
  • }
  

  

  edismax的主要实现思路跟dismax差不多,以下是一些主要差别之处:
  edismax解析含有+,OR,NOT,-语法时,就会忽略掉使用MM。
  以下是主要代码实现:
  统计搜索串中+,OR ,NOT,-语法元个数






[java] view plaincopy

  • // defer escaping and only do if lucene parsing fails, or we need phrases  
  •      // parsing fails.  Need to sloppy phrase queries anyway though.  
  •      List clauses = null;  
  •      int numPluses = 0;  
  •      int numMinuses = 0;  
  •      int numOR = 0;  
  •      int numNOT = 0;  

  •      clauses = splitIntoClauses(userQuery, false);  
  •      for (Clause clause : clauses) {  
  •        if (clause.must == '+') numPluses++;  
  •        if (clause.must == '-') numMinuses++;  
  •        if (clause.isBareWord()) {  
  •          String s = clause.val;
  •          if ("OR".equals(s)) {  
  •            numOR++;
  •          } else if ("NOT".equals(s)) {  
  •            numNOT++;
  •          } else if (lowercaseOperators && "or".equals(s)) {  
  •            numOR++;
  •          }
  •        }
  •      }
  

/////当搜索串里包含有+,OR ,NOT,-这四种时候,mm就会失效






[java] view plaincopy

  • boolean doMinMatched = (numOR + numNOT + numPluses + numMinuses) == 0;  
  • (parsedUserQuery != null && doMinMatched) {  
  •   String minShouldMatch = solrParams.get(DisMaxParams.MM, "100%");  
  •   if (parsedUserQuery instanceof BooleanQuery) {  
  •     SolrPluginUtils.setMinShouldMatch((BooleanQuery)parsedUserQuery, minShouldMatch);
  •   }
  • }
  









短语查询,先找出普通的查询,原来就是短语查询的、或者属于“OR”,“AND”,“NOT”,’TO‘类型的都不要。由于
edismax支持解析符合lucene语法的搜索串,所以不像dismax那样,只需要简单的将搜索串去掉\“,然后加个“”括起来就行        

        // find non-field clauses
        ListnormalClauses =new ArrayList(clauses.size());
        for (Clauseclause :clauses) {
          if (clause.field !=null ||clause.isPhrase)continue;
          // check for keywords "AND,OR,TO"
          if (clause.isBareWord()) {
            String s =clause.val.toString();
            // avoid putting explict operators in the phrase query
            if ("OR".equals(s) ||"AND".equals(s) ||"NOT".equals(s) ||
"TO".equals(s))continue;
          }
          normalClauses.add(clause);
        }


        // full phrase...
        addShingledPhraseQueries(query,
normalClauses, phraseFields, 0,
                                 tiebreaker,pslop);
        // shingles...
        addShingledPhraseQueries(query,
normalClauses, phraseFields2, 2,  
                                 tiebreaker,pslop);
        addShingledPhraseQueries(query,
normalClauses, phraseFields3, 3,
                                 tiebreaker,pslop);


////下面是dismax获取短语查询的作法:








[java] view plaincopy

  • protected Query getPhraseQuery(String userQuery, SolrPluginUtils.DisjunctionMaxQueryParser pp) throws ParseException {  
  •   String userPhraseQuery = userQuery.replace("\"", "");  
  •   return pp.parse("\"" + userPhraseQuery + "\"");  
  • }
  
下面是edismax的作法:






[java] view plaincopy

  • private void addShingledPhraseQueries(final BooleanQuery mainQuery,   
  •                                       final List clauses,  
  •                                       final Map fields,  
  •                                       int shingleSize,  
  •                                       final float tiebreaker,  
  •                                       final int slop)   
  •   throws ParseException {      
  •   if (null == fields || fields.isEmpty() ||   
  •       null == clauses || clauses.size() 1) {  
  •        ValueSource prod = new ProductFloatFunction(boosts.toArray(new ValueSource[boosts.size()]));  
  •        topQuery = new BoostedQuery(query, prod);  
  •      } else if (boosts.size() == 1) {  
  •        topQuery = new BoostedQuery(query, boosts.get(0));  
  •      }
  •    }
  
可以看到最后不是一个BooleanQuery,而是一个BoostedQuery。
它就是简单处理子查询的分值再与函数查询的分值相乘返回 :主要的score方法如下:








[java] view plaincopy

    • public float score() throws IOException {  
    •   float score = qWeight * scorer.score() * vals.floatVal(scorer.docID());  
    •   return score>Float.NEGATIVE_INFINITY ? score : -Float.MAX_VALUE;  
    • }

  下面示例用于演示如下场景:
  有一网站,在用户查询的结果中,需要按这样排序:


  • VIP的付费信息需要排在免费信息的前头
  • 点击率越高越靠前
  • 发布时间越晚的越靠前
  
  这样的查询排序使用普通的查询结果的Order by是做不到的,必需使用solr的defType。
  
  做法:
  1、先看schema.xml的定义:




DSC0000.gif







































id

searchText




  说明:
  a)里头定义了一个copyField:searchText,此字段为:subject+content+region+category,并把这个字段设置为默认查询字段。意思是查询时,默认查询四个字段的内容。
  b)把solrQueryParser设置为AND,事实上,大多情况下,我们是习惯使用AND为条件查询,而非OR
  c)text_cn字段类型中的:useSmart



        

  意思是:useSmart =true ,分词器使用智能切分策略, =false则使用细粒度切分。详细,可下载IK分词器的源码看看。
  
  2、加入一个查询Handler到solrconfig.xml的当中:





   

edismax

sum(linear(vip,1000,0),linear(sqrt(log(linear(point,1,2))),100,0),sqrt(log(ms(createTime))))





  说明:
  a)上面的default="true"意思为设置为默认的查询handler(记得把原standard中的default="true"删除掉)
  b)见已经被注释的这段:



            
  这是简单的不使用bf的排序加权方式,可以用于应付简单的排序,具体pf/qf的使用,可以上网上搜搜应用。这里演示的功能相对“复杂”,不适用它。
  c)见这句公式:



sum(linear(vip,1000,0),linear(sqrt(log(linear(point,1,2))),100,0),sqrt(log(ms(createTime))))
  公式中的函数定义和意思,可以参考:
  官方文档:
  http://wiki.apache.org/solr/FunctionQuery
  中文说明:
  http://mxsfengg.iteye.com/blog/352191
  这里的函数意思是:


  • 如果是vip信息=值+1000,非vip信息=值+0
  • 点击率(point)的值范围为:50~500之间
  • 发布时间(createTime)值范围为:50以内
  以上三个值相加得出最统权重分从高到低排序
  
  3、Java bean:





package com.my.entity;
import java.util.Date;
import org.apache.solr.client.solrj.beans.Field;
public class Item {
@Field
private long id;
@Field
private String subject;
@Field
private String content;
@Field
private int regionId;
@Field
private int categoryId;
@Field
private float price;
@Field
private Date createTime;
@Field
private long point;
@Field
private boolean vip;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getRegionId() {
return regionId;
}
public void setRegionId(int regionId) {
this.regionId = regionId;
}
public int getCategoryId() {
return categoryId;
}
public void setCategoryId(int categoryId) {
this.categoryId = categoryId;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public long getPoint() {
return point;
}
public void setPoint(long point) {
this.point = point;
}
public boolean isVip() {
return vip;
}
public void setVip(boolean vip) {
this.vip = vip;
}
}

  
  4、Java测试代码:





package com.my.solr;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrQuery.ORDER;
import org.apache.solr.client.solrj.SolrQuery.SortClause;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.apache.solr.client.solrj.impl.XMLResponseParser;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.FacetField.Count;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.params.AnalysisParams;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.FacetParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import com.my.entity.Item;
public class TestSolr {
private static HashMap mapRegion = new HashMap();
private static HashMap mapCategory = new HashMap();
@SuppressWarnings("unchecked")
public static void main(String[] args) throws IOException,
SolrServerException {
// ------------------------------------------------------
// Set map
// ------------------------------------------------------
mapRegion.put(1, "罗湖区");
mapRegion.put(2, "南山区");
mapRegion.put(3, "龙岗区");
mapRegion.put(4, "福田区");
mapCategory.put(1, "单间");
mapCategory.put(2, "2房1厅");
mapCategory.put(3, "3房2厅");
mapCategory.put(4, "1房1厅");
String url = "http://localhost:8983/solr/sample5";
HttpSolrServer core = new HttpSolrServer(url);
core.setMaxRetries(1);
core.setConnectionTimeout(5000);
core.setParser(new XMLResponseParser()); // binary parser is used by
// default
core.setSoTimeout(1000); // socket read timeout
core.setDefaultMaxConnectionsPerHost(100);
core.setMaxTotalConnections(100);
core.setFollowRedirects(false); // defaults to false
core.setAllowCompression(true);
// ------------------------------------------------------
// remove all data
// ------------------------------------------------------
core.deleteByQuery("*:*");
List items = new ArrayList();
items.add(makeItem(items.size() + 1, "龙城公寓一房一厅", "豪华城城公寓1房1厅,拧包入住", 1, 1, 1200f, 10, false));
items.add(makeItem(items.size() + 1, "兴新宿舍楼 1室0厅", " 中等装修 招女性合租", 1, 1, 1000f, 11, false));
items.add(makeItem(items.size() + 1, "西丽新屋村新宿舍楼单间", " 无敌装修只招女性", 2, 1, 1000f, 2, true));
items.add(makeItem(items.size() + 1, "大芬村信和爱琴居地铁口2房1厅", " 地铁口 + 出行便利=居家首选", 3, 2, 2000f, 5, false));
items.add(makeItem(items.size() + 1, "龙岗富豪花园3房2厅出租", " 离地铁口只要5分钟,快来秒杀吧", 3, 3, 4500f, 21, true));
items.add(makeItem(items.size() + 1, "海景房园3房2厅出租", "海景房园出租,无敌海景,可以看到伦敦", 4, 3, 8500f, 12, false));
items.add(makeItem(items.size() + 1, "天域花园1房1厅", "天域花园,男女不限,入住免水电一月", 2, 4, 1500f, 13, true));
items.add(makeItem(items.size() + 1, "神一样的漂亮,玉馨山庄3房2厅", "心动不如行动,拧包即可入住,来吧!", 1, 3, 9500f, 8, false));
items.add(makeItem(items.size() + 1, "玉馨山庄2房1厅,情侣最爱", "宅男宅女快来吧只要2500,走过路过,别再错过", 1, 2, 2500f, 5, false));
items.add(makeItem(items.size() + 1, "天域花园3房2厅", "天域花园出租,都来看看,都来瞄瞄,3房出租只要7500.", 4, 3, 7500f, 6, true));
items.add(makeItem(items.size() + 1, "深都花园出租3房2厅", "找爱干净的人氏,全新装修", 4, 3, 5200f, 31, false));
items.add(makeItem(items.size() + 1, "This is Mobile test", "haha Hello world!", 4, 3, 1200f, 31, false));
core.addBeans(items);
// commit
core.commit();
// ------------------------------------------------------
// Set search text
// ------------------------------------------------------
String searchText = AnalysisSearchText(core, "出租花园"); //subject:*出租* && price:[1000 TO 8000]
System.out.println("Search Text:" + searchText);
// ------------------------------------------------------
// Set query text
// ------------------------------------------------------
String queryText = searchText + "&& price:[1000 TO 8000]";
System.out.println("Query Text:" + queryText);
// ------------------------------------------------------
// search
// ------------------------------------------------------
SolrQuery query = new SolrQuery();
query.setQuery(queryText);
query.setStart(0); // query的开始行数(分页使用)
query.setRows(100); // query的返回行数(分页使用)
query.setFacet(true); // 设置使用facet
query.setFacetMinCount(0); // 设置facet最少的统计数量
query.setFacetLimit(10); // facet结果的返回行数
query.addFacetField("categoryId", "regionId"); // facet的字段
query.setFacetSort(FacetParams.FACET_SORT_COUNT);
//query.addSort(new SortClause("id", ORDER.asc)); // 排序
query.setRequestHandler("/browse");
QueryResponse response = core.query(query);
List items_rep = response.getBeans(Item.class);
List facetFields = response.getFacetFields();
// 因为上面的start和rows均设置为0,所以这里不会有query结果输出
System.out.println("--------------------");
System.out.println("Search result:");
for (Item item : items_rep) {
System.out.println("id=" + item.getId() + "\tsubject=" + item.getSubject()
+ "\tregion=" + mapRegion.get(item.getRegionId())
+ "\tcategory=" + mapCategory.get(item.getCategoryId())
+ "\tprice=" + item.getPrice());
}
// 打印所有facet
for (FacetField ff : facetFields) {
System.out.println("--------------------");
System.out.println("name=" + ff.getName() + "\tcount=" + ff.getValueCount());
System.out.println("--------------------");
switch (ff.getName()) {
case "regionId":
printOut(mapRegion, ff.getValues());
break;
case "categoryId":
printOut(mapCategory, ff.getValues());
break;
}
}
}
@SuppressWarnings({ "rawtypes" })
private static void printOut(HashMap map, List counts) {
for (Count count : counts) {
System.out.println("name=" + map.get(Integer.parseInt(count.getName())) + "\tcount=" + count.getCount());
}
System.out.println("--------------------");
}
private static Item makeItem(long id, String subject, String content, int regionId, int categoryId, float price,
long point, boolean vip) {
Calendar cale = Calendar.getInstance();
cale.setTime(new Date());
cale.add(Calendar.DATE, (int)id);
Item item = new Item();
item.setId(id);
item.setSubject(subject);
item.setContent(content);
item.setRegionId(regionId);
item.setCategoryId(categoryId);
item.setPrice(price);
item.setCreateTime(cale.getTime());
item.setPoint(point);
item.setVip(vip);
return item;
}
@SuppressWarnings("unchecked")
/**
* 重新将需要查询的文本内容解析成分词
* @param core
* @param searchText
* @return
* @throws SolrServerException
*/
private static String AnalysisSearchText(HttpSolrServer core, String searchText) throws SolrServerException {
StringBuilder strSearchText = new StringBuilder();
final String STR_FIELD_TYPE = "text_cn";
SolrQuery queryAnalysis = new SolrQuery();
queryAnalysis.add(CommonParams.QT, "/analysis/field"); // query type
queryAnalysis.add(AnalysisParams.FIELD_VALUE, searchText);
queryAnalysis.add(AnalysisParams.FIELD_TYPE, STR_FIELD_TYPE);
QueryResponse responseAnalysis = core.query(queryAnalysis);
//对响应进行解析
NamedList analysis = (NamedList) responseAnalysis.getResponse().get("analysis");// analysis node
NamedList field_types = (NamedList) analysis.get("field_types");// field_types node
NamedList fieldType = (NamedList) field_types.get(STR_FIELD_TYPE);// text_cn node
NamedList index = (NamedList) fieldType.get("index");// index node
List list = (ArrayList)index.get("org.wltea.analyzer.lucene.IKTokenizer");// tokenizer node
// 在每个词条中间加上空格,为每个词条进行或运算
for(Iterator iter = list.iterator(); iter.hasNext();)
{
strSearchText.append(iter.next().get("text") + " ");
}
return strSearchText.toString();
}
}

  说明:
  a)AnalysisSearchText(...)方法:此方法会把需要查询的语句先使用分词分析,如上例子“出租花园”,调用AnalysisSearchText(...)后,会得到“出租 花园”,会把两个词分拆成以空格分隔的字符串。不然solr会以“出租花园”整体做为词做查询而得不到结果。
  b)使用自定义的Handler,需要在代码中加入这句:



query.setRequestHandler("/browse");
  对应的是solrconfig.xml中的requestHandler的:/browse
  
  5、运行结果:
DSC0001.png
  
  或者使用solr的query查询查看结果:
DSC0002.png

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-87872-1-1.html 上篇帖子: Solr的学习使用之(十)数据库(Oracle、SqlServer)原有的全文索引功能和Solr对比? 下篇帖子: Solr DataImportHandler
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表