# solr分词查询和权重排序


# 一、Solr7.6.0版本+IK Analysis的词典及同义词配置

# 1.jar包和分词器配置

  • ik-analyzer-8.6.3.0.jar包放到 /solr/server/solr-webapp/webapp/WEB-INF/lib目录下(旧的删除,重启才会生效)
  • ext.dic(扩展词典)、IKAnalyzer.cfg.xml、stopword.dic(停用词典)、ik.conf、dynamicdic.txt复制到/solr/server/solr-webapp/webapp/WEB-INF/classes目录下(classes自行创建)
  • 注意:扩展词词典和停用词词典一定要是utf-8格式的!没有找到classes目录的文件直接新建也可以(.dic文件的内容注释或删掉没用的)
  1. IKAnalyzer.cfg.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer 扩展配置</comment>
   <!-- 配置是否加载默认词典 -->
   <entry key="use_main_dict">true</entry>
    <!-- 配置自己的扩展字典,多个用分号分隔 -->
    <entry key="ext_dict">ext.dic;</entry>
    <!-- 配置自己的扩展停止词字典,多个用分号分隔 -->
    <entry key="ext_stopwords">stopword.dic;</entry>
</properties>
  1. ik.conf内容如下
Wed Aug 01 11:21:30 CST 2018
files=dynamicdic.txt,brandDic.txt,modelDic.txt
lastupdate=0
  • files为动态词典列表,可以设置多个词典表,用逗号进行分隔,默认动态词典表为dynamicdic.txt
  • lastupdate默认值为0,每次对动态词典表修改后请+1,不然不会将词典表中新的词语添加到内存中。
  1. dynamicdic.txt为动态词典,在此文件配置的词语不需重启服务即可加载进内存中(每次修改dynamicdic.txt文件,ik.conf中的lastupdate参数需要+1,并且要重启一下对应集合)。 以#开头的词语视为注释,将不会加载到内存中

# 2.集合使用分词器:

找到对应集合中的schema.xmlmanaged-schema.xml)文件

  1. 添加分词配置:(可能已经配置过了,需要进行修改)
<fieldType name="text_ik" class="solr.TextField">
    <analyzer type="index" positionIncrementGap="100" autoGeneratePhraseQueries="true">
       <tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="true" conf="ik.conf"/>
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
    </analyzer>
    <analyzer type="query">
       <tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="true" conf="ik.conf"/>
        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
       <filter class="solr.LowerCaseFilterFactory"/>
    </analyzer>
</fieldType>

解析:

  • useSmart:true使用智能切分;false不使用

  • LowerCaseFilterFactory:是一个配置不区分大小写的factory

  • SynonymFilterFactory:同义词factory,同义词词典在同目录下的synonyms.txt。

  • Synonym mappings can be used for spelling correction too

  • pixima => pixma

  • 还行 => 还可以

  • 此句表示左边同义于右边,但右边不同义于左边。两边同义应使用','(台式计算机,台式电脑)

  1. 对需要分词的字段,设置类型为texk_ik
<field name="deviceName" type="text_ik" indexed="true" stored="true" required="false" multiValued="false"/>

  1. 如果需要使用联合查询,则需要设置keyword
<!-- 关键字查询 -->
<field name="keyword" type="text_ik" indexed="true" stored="false" multiValued="true"/>

<!-- 联合查询 -->
<copyField source="deviceName" dest="keyword"/>
<copyField source="deviceModel" dest="keyword"/>
<copyField source="deviceBrand" dest="keyword"/>

设置完成后需要重新导入数据

# 二、solr根据权重排序

# 1. edismax控制评分

如果设置了sort字段,那么将会按照sort字段的顺序返回结果。 如果没有设置sort字段,那么将会根据相关度打分来排序。也就是说,相关度更高的排在前面。

edismax的理念: edismax中将查询字段和查询词项分开了,如我们在标准用法中使用name:zjf,那么在edismax中,需要在qf中设置查询字段为name,然后在q中输入zjf,注意这里不能加name了,否则没有结果。

这样设计的结果就是,查询词项会去所有的qf列(query field,可以设置多列,也可以配置为每列配置权重)上进行查询,如果要针对每个字段有不同的查询,如name:zjf,age:30,那么需要将其中一个移入到fq中,但是注意,fq中的查询是不影响评分的。这也是edismax的理念。满足常用的搜索场景。

edismax的常用参数:

q和fq来自标准查询,也经常使用。 edismax扩展的有:

  • qf:query field。q中的词项要在哪些字段上执行查询。可以设置多列以及每一列的权重。如果没有设置,那么将会使用df默认字段(一般在配置文件中配置好)。
  • pf:parse field。pf和qf的格式一样。区别是pf会更加注重短语匹配,也就是说如果输入zjf xhj作为查询,那么在配置了pf的字段上,zjf随后出现xhj的文档的评分更高。注意这里只是评分更高,如果想获得更加严格的短语匹配,应该在查询中使用"zjf xhj"。
  • ps:用于配置pf中的词项的短语间隔。可以控制zjf和xhj之间多少个间隔。
  • bq:接受一个和q一样的查询,它和q的区别是不影响返回的结果集,只会影响排名。
  • bf:提升函数,通过数学公式来影响评分,而且不局限在qf中的字段。
  • mm:最小匹配,如果我们不严格要求AND,可以配置mm来定义查询结果集的匹配程度。 注意:像pf 和qf这种需要查询的字段上,一定要是indexed的。

关于mm

  • mm可以设置为整数,如2,代表至少匹配两个词项,如果输入词项少于两个,那么要全部匹配才行。
  • mm可以设为百分比,表示必须匹配到多大的百分比才可以。也可以合并在一起匹配。
  • 如设置为mm="2<50%" 那么如下查询的话:
  • 只有一个词项,必须全部匹配。
  • 2个词项,也必须全部匹配。
  • 超过两个,按照50%来计算

# 2.使用Solr的DisMaxQParserPlugin通过配置来制定结果文档打分规则。

DisMaxQParserPlugin提供在针对文本boost打分上,支持搜索多个schema索引字段,并针对每一个字段设置不同的boost权限。 pf查询 与 qf查询 pf: 可提供对一条记录的多个字段做匹配的功能 qf: 针对查询的每个字段设置不同的boost权重打分,其设置的字段必须为在pf中配置的项。 可在solrconfig.xml中的browse中配置做如下配置:

<requestHandler name="/browse" class="solr.SearchHandler">
<lst name="defaults">
<str name="defType">edismax</str>
<str name="pf">
name info title
</str>
<str name="qf">
name^1 info^0.8 title^0.6
</str>
</lst>
</requestHandler> 

解析:查询name,info,title三个字段,每个字段的文本相关度打分分别为1,0.8,0.6。计算查询出的每一条结果的权重方法如下:分别计算各字段的文本打分然后乘于配置的系统,最后三者相加即为该结果的boost得分。

使用函数式打分配置:

<requestHandler name="/browse" class="solr.SearchHandler">
<lst name="defaults">
<str name="defType">edismax</str>
<str name="bf">
  <!--把时间转变成距离现在的时间,日为单位(1/86400000000=1.16e-11);月(3.86e-13)-->
  recip(ms(NOW,createTime),3.86e-13,1,1)^10
</str>
<str name="pf">
  deviceName deviceBrand deviceModel
</str>
<str name="qf">
  deviceName^10 deviceBrand^8 deviceModel^6
</str>
<str name="mm">
  2&lt;80%
</str>
<int name="rows">10</int>
</lst>
</requestHandler>


其中sum,recip,ms,sqrt,log,max这些都是Solr提供的数学方法,支持的所有函数:

函数 说明 举例
abs(x) 返回绝对值 abs(-5)
“constant” 指定一个浮点数 1.5
def(“field”,value) 默认值,当指定字段不存在时,返回默认值 def(rationg,5)
div(x,y) 除法,x除以y div(1,5)
dist 计算两点之间的距离 dis(2,x,y,0,0)
docfreq(field,val) 返回某值在某字段出现的次数 docfreq(title,’solr’)
field(“field”) 返回该field的索引数量 field(‘title’)
hsin 曲面圆弧上两点之间的距离 hsin(2,true,x,y,0,0)
idf Inverse document frequency 倒排文档频率 idf(“field”,’solr’)
if if(test,value1,value2) if(termfreq(title,’solr’),popularity,42)
linear(x,m,c) 就是m*x+c,等同于sum(product(m,x),c) linear(1,2,4)=1x2+4=6
log(x) 以10为底,x的对数 log(sum(x,100))
map(x,min,max,target) 如果x在min和max之间,x=target,否则x=x map(x,0,0,1)
max(x,y,…) 返回最大值 max(2,3,0)
maxdoc 返回索引的个数,查看有多少文档,包括被标记为删除状态的文档 maxdoc()
min(x,y,…) 返回最小值 min(2,4,0)
ms 返回两个参数间毫秒级的差别 ms(datefield1,2000-01-01T00:00:00Z)
norm(field) 返回该字段索引值的范数 norm(title)
numdocs 返回索引的个数,查看有多少文档,不包括被标记为删除状态的文档 numdocs()
ord 根据顺序索引发货结果 ord(title)
pow(x,y) 返回x的y次方 pow(x,log(y))
product(x,y) 返回多个值得乘积 product(x,2)
query 返回给定的子查询的得分,或者文档不匹配的默认值值 query(subquery,default)
recip(x,m,a,b) 相当于a/(m*x+b),a,m,b是常量,x是变量 recip(myfield,m,a,b)
rord 按ord的结果反序返回
scale 返回一个在最大值和最小值之间的值 scale(x,1,3)
sqedist 平方欧氏距离计算 sqedist(x_td,y_td,0,0)
sqrt 返回指定值得平方根 sqrt(x)sqrt(100)
strdist 计算两个字符串之间的距离 strdist(“SOLR”,id,edit)
sub 返回x-y sub(field1,field2)
sum(x,y) 返回指定值的和 sum(x,y,…)
sumtotaltermfreq 返回所有totaltermfreq的和
termfreq 词出现的次数 termfreq(title,’sorl’)
tf 词频 tf(text,’solr’)
top 功能类似于ord
totaltermfreq 返回这个词在该字段出现的次数 ttf(title,’memory’)
and 返回true值当且仅当它的所有操作为true and(not(exists(popularity)),exists(price))
or 返回true值当有一个操作为true or(value1,value2)
xor 返回false值如果所有操作都为真 xor(field1,field2)
not 排除操作 not(exists(title))
exists 如果字段存在返回真 exists(title)
gt,gte,lt,lte,eq 比较函数 2 gt 1

# 三、代码查询修改

# 1.设置Request-Handler (qt)

solr默认的请求头是/select,如果需要使用2-2配置的默认打分规则,则需要重置Request-Handler 代码如下:

SolrQueryWrapper wrapper = new SolrQueryWrapper();
wrapper.setRequestHandler(SolrRequestHandlerConstant.BROWSE);

# 2.查询设置

由于2-1中所示edismax查询是把查询字段和查询信息分开,所以查询内容中的非分词查询都要通过fq查询,q查询输入查询信息(也可以直接输入,但是直接输入的计分规则是整个集合的字段,对分数没要求时可以使用)。 事例:

  • 搜索设备类型为:硬盘:3.5英寸 1TB 7200rpm SATA硬盘的信息
  • 直接把查询内容带入wrapper
  • wrapper.applyQuery("硬盘:3.5英寸 1TB 7200rpm SATA硬盘")
  • wrapper.eq("keyword","硬盘/:3.5英寸 1TB 7200rpm SATA硬盘")
  • 设置时间条件应强制使用fq查询
  • wrapper.fq().ge("createTime",LocalDateTime.now());
  • 其他设置不受限制
  • wrapper.select("deviceName deviceBrand deviceModel deviceSpec createTime score")
  • wrapper.page(0, 10);
  • 搜索设备类型为:硬盘:3.5英寸 1TB 7200rpm SATA硬盘的信息
  • 直接把查询内容带入wrapper
  • wrapper.applyQuery("硬盘:3.5英

# 3.设置权重

权重规则设置优先使用自定义设置字段,没有设置的字段使用2-2中的默认设置 注意:使用权重方式不能根据时间字段排序(不然权重就没意义了)

事例:

  • 与配置文档完全一直则不需要开启edismax(查询条数不在讨论范围)

wrapper.set("defType", "edismax");

  • 设置查询分词信息的字段(各字段间使用空格隔开)

wrapper.set(DisMaxParams.QF,"deviceName deviceBrand deviceModel");

  • 设置权重(各字段间使用空格隔开)

wrapper.set(DisMaxParams.PF,"deviceName^10 deviceBrand^8 deviceModel^6");

  • 设置匹配度

wrapper.minMustMatch("2<80%");

  • 与配置文档完全一直则不需要开启edismax(查询条数不在讨论范围)

wrapper.set("defType", "edismax");

  • 设置查询分词信息的字段(各字段间使用空格隔开)

wrapper.set(DisMaxParams.QF,"device

Last Updated: 3/15/2021, 11:29:58 AM