# 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文件的内容注释或删掉没用的)
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>
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,不然不会将词典表中新的词语添加到内存中。
dynamicdic.txt
为动态词典,在此文件配置的词语不需重启服务即可加载进内存中(每次修改dynamicdic.txt
文件,ik.conf
中的lastupdate
参数需要+1,并且要重启一下对应集合)。 以#
开头的词语视为注释,将不会加载到内存中
# 2.集合使用分词器:
找到对应集合中的schema.xml
(managed-schema.xml
)文件
- 添加分词配置:(可能已经配置过了,需要进行修改)
<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
还行 => 还可以
此句表示左边同义于右边,但右边不同义于左边。两边同义应使用','(台式计算机,台式电脑)
- 对需要分词的字段,设置类型为texk_ik
<field name="deviceName" type="text_ik" indexed="true" stored="true" required="false" multiValued="false"/>
- 如果需要使用联合查询,则需要设置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<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