前言
最近遇到solr查询中加入字段权重的需求,自然而然地想到了edismax这个功能。通过系统的学习和文档阅读,大概了解solr 对于函数式查询的支持方式。为了便于记忆,这里对常用公式进行整理说明。
使用方式详细见官方文档介绍
,这里不做说明,我们重点讲solr edismax所涉及到的函数。
bf函数列表
constant
支持小数点的常量
例如,1.5,查询表达式就是:_val_:1.5fieldvalue
返回numberic field的名字.
域必须是index的,非multivalue。格式为该域的名字。如果这个域没值,就返回0ord
ord,返回你要查询的那个特定的值在这个顺序中的排名。
非multiValued的,当没有值存在的时候,将返回0
例如:某个特定的域只能去三个值,“apple”、“banana”、“pear”,那么ord(“apple”)=1,ord(“banana”)=2,ord(“pear”)=3
需要注意的是,ord()这个函数,依赖于值在索引中的位置,所以当有文档被删除、或者添加的时候,ord()的值就会发生变化。当你使用MultiSearcher的时候,这个值也就是不定的了。rord
函数将会返回与ord相对应的倒排序的排名。
格式: rord(myIndexedField).sum
就是表示多个数值的“和”。
格式:
sum(x,1)
sum(x,y)
sum(sqrt(x),log(y),z,0.5)product
多个参数的乘积,参数可以是数值,也可以是函数,当为函数时,表示为此函数的计算值乘积。
格式:
product(x,2)
product(x,y)div
两个参数做除法。支持函数参数
格式:
div(x,y)
div(sum(x,100),max(y,1))pow
幂值计算,pow(x,y)=x^y 。支持函数参数。
格式:
pow(x,0.5) 标识开方
pow(x, log(y))abs
返回表达式的绝对值,支持函数参数。
格式:
abx(-5)
abc(x)log
返回对数操作,支持函数参数。
格式:
log(x)
log(sum(x,100))sqrt
返回平方根。与pow(x,0.5)一样。
格式:
sqrt(2)
sqrt(sum(x,100))map
区间检测
如果 min<=x<=max,那么map(x,min,max,target)=target,如果x不在[min,max]这个区间内,那么map(x,min,max,target)=x.scala
限制参数区间
例如:
scale(x,minTarget,maxTarget) 这个函数将会把x的值限制在[minTarget,maxTarget]范围内。query
计算subquery查询分数
例如:
query(subquery,default)表示返回给定的subquery的分数,如果subquery与文档不匹配,那么将会返回默认值。任何的查询类型都是受支持的。可以通过引用的方式,也可以直接指定查询串。
q=product(popularity, query({!dismax v=’solr rocks’})) 将会返回popularity和通过dismax 查询得到的分数的乘积
q=product(popularity, query($qq)&qq={!dismax}solr rocks) 跟上一个例子的效果是一样的。不过这里使用的是引用的方式
q=product(popularity, query($qq,0.1)&qq={!dismax}solr rocks) 在前一个例子的基础上又加了一个默认值。linear
线性函数计算
例如:
liner(x,m,c)其中 x为变量或者函数,m,c为常量。整个函数取值为: xm+c的值。
liner(x,2,4)=2x+4recip
recip(x,m,a,b) 函数表达式 a/(m*x+b)
其中,m、a、b是常量,x是变量或者一个函数。当a=b,并且x>=0的时候,这个函数的最大值是1,值的大小随着x的增大而减小。max
比较大小
例如:max(x,c) x可以为变量或者函数,c为常数,返回两个之间最大值。
场景应用
某地的新闻网页库中原本的逻辑是对仓库里的数据字段 subject,message进行搜索。默认是通过score检索字段匹配得分进行排序输出。随着时间的推移,大量的搜索可能会展示两年前,三年前匹配度更高的数据,这些搜索结果明显不合适的。那么我们需要对其进行改造,加入发布时间权重排序。
原本的参数:
1
subject:武则天 OR message:武则天
搜索得出结果:
文档得分:
上面我们可以看到,tid为666811的文档排在第一位,得分27.811375 它的dateline时间是:1239781944明显早于第二位 tid:10364925的 1503334472,得分:26.519054。第三位是 tid:9759987 得分:26.511488。这样的搜索结果显然不是很令人满意的。
开启edismax 加入1
bf=sqrt(log(dateline))^100
搜索得出结果:
文档得分:
经过调整,我们得出的结果中排在第一位的是 tid:9759987 其时间dateline是1473820016 得分:330.85 是原本的第三位。原来的排第一的 tid:666811排在了第三位,得分 329.40 原来的第二tid:10364925 得分:329.50调整后的排序大致满足我们的需求。那么为什么调整后会变成这样的排序呢?
首先我们要清楚solr的打分机制默认是通过匹配度计算文档相似度得来的。也就是第一次搜索的默认得分,引入edismax的bf函数后我们来分析下最终的结果是怎样,以第一次搜索排名前三的数据为例子:
tid dateline 初始得分 引入bf重新计算 666811 1239781944 27.811382 329.40198 10364925 1503334472 26.519054 329.50174 9759987 1473820016 26.511488 330.85834
根据bf=sqrt(log(dateline))^100 分别计算上面三个的新得分
1 | sqrt(log(1239781944)) = 3.0155174194591075 |
纳尼。很奇怪为什么 9759987 计算最小 不对劲
于是翻看原来前面查询的debug列表分析仔细看原来是
原图:
添加edismax后:
对比以上靓图,原来是我们的Qparser不一样。在普通查询的时候我么使用的是定制化的 SWMCLuceneQparser 查询解析器。而 用edimax后,解析器变成了 ExtendDismaxQparser 这两个差别在于 定制化的 SWMCLuceneQparser会将查询字段通过IK分词转换后进行查询。其parsedquery_tostring 变成
1 | "parsedquery":"PhraseQuery(subject:\"武 则 天\") PhraseQuery(message:\"武 则 天\")", |
ExtendDismaxQparser的 parsedquery_tostringshi :
1 | "parsedquery_toString":"+((subject:武 subject:则 subject:天) (message:武 message:则 message:天)) (sqrt(log(long(dateline))))^10.0", |
两者稍有不同,所以在计算最终权重的时候有些差异。