solr高级查询edismax函数详解

前言

最近遇到solr查询中加入字段权重的需求,自然而然地想到了edismax这个功能。通过系统的学习和文档阅读,大概了解solr 对于函数式查询的支持方式。为了便于记忆,这里对常用公式进行整理说明。

使用方式详细见官方文档介绍
,这里不做说明,我们重点讲solr edismax所涉及到的函数。

bf函数列表

  1. constant
    支持小数点的常量
    例如,1.5,查询表达式就是:_val_:1.5

  2. fieldvalue
    返回numberic field的名字.
    域必须是index的,非multivalue。格式为该域的名字。如果这个域没值,就返回0

  3. ord
    ord,返回你要查询的那个特定的值在这个顺序中的排名。
    非multiValued的,当没有值存在的时候,将返回0
    例如:某个特定的域只能去三个值,“apple”、“banana”、“pear”,那么ord(“apple”)=1,ord(“banana”)=2,ord(“pear”)=3
    需要注意的是,ord()这个函数,依赖于值在索引中的位置,所以当有文档被删除、或者添加的时候,ord()的值就会发生变化。当你使用MultiSearcher的时候,这个值也就是不定的了。

  4. rord
    函数将会返回与ord相对应的倒排序的排名。
    格式: rord(myIndexedField).

  5. sum
    就是表示多个数值的“和”。
    格式:
    sum(x,1)
    sum(x,y)
    sum(sqrt(x),log(y),z,0.5)

  6. product
    多个参数的乘积,参数可以是数值,也可以是函数,当为函数时,表示为此函数的计算值乘积。
    格式:
    product(x,2)
    product(x,y)

  7. div
    两个参数做除法。支持函数参数
    格式:
    div(x,y)
    div(sum(x,100),max(y,1))

  8. pow
    幂值计算,pow(x,y)=x^y 。支持函数参数。
    格式:
    pow(x,0.5) 标识开方
    pow(x, log(y))

  9. abs
    返回表达式的绝对值,支持函数参数。
    格式:
    abx(-5)
    abc(x)

  10. log
    返回对数操作,支持函数参数。
    格式:
    log(x)
    log(sum(x,100))

  11. sqrt
    返回平方根。与pow(x,0.5)一样。
    格式:
    sqrt(2)
    sqrt(sum(x,100))

  12. map
    区间检测
    如果 min<=x<=max,那么map(x,min,max,target)=target,如果x不在[min,max]这个区间内,那么map(x,min,max,target)=x.

  13. scala
    限制参数区间
    例如:
    scale(x,minTarget,maxTarget) 这个函数将会把x的值限制在[minTarget,maxTarget]范围内。

  14. 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) 在前一个例子的基础上又加了一个默认值。

  15. linear
    线性函数计算
    例如:
    liner(x,m,c)其中 x为变量或者函数,m,c为常量。整个函数取值为: xm+c的值。
    liner(x,2,4)=2
    x+4

  16. recip
    recip(x,m,a,b) 函数表达式 a/(m*x+b)
    其中,m、a、b是常量,x是变量或者一个函数。当a=b,并且x>=0的时候,这个函数的最大值是1,值的大小随着x的增大而减小。

  17. max
    比较大小
    例如:max(x,c) x可以为变量或者函数,c为常数,返回两个之间最大值。

场景应用

  1. 某地的新闻网页库中原本的逻辑是对仓库里的数据字段 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sqrt(log(1239781944)) = 3.0155174194591075  
权重乘100 得:
301.55174194591075
再加 27.811382
=329.36312394591073

sqrt(log(1503334472)) = 3.029365546794402
权重乘100 得:
302.9365546794402
再加 26.519054
=329.45560867944016

sqrt(log(1473820016)) = 3.0279439311841663
权重乘100 得:
302.79439311841663
再加 26.511488
=329.3058811184166
纳尼。很奇怪为什么 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",

两者稍有不同,所以在计算最终权重的时候有些差异。