solr group - yaokun123/php-wiki GitHub Wiki

Solr分组详解

一、简介

当你使用搜索引擎时可能已经见过了类似"Field Collapsing"(即域折叠或者域收缩)功能,如果搜索引擎告诉你有很多结果匹配了,但是只是显示了部分结果,那么有可能你已经感受到了"Field Collapsing"功能。通常,"Field Collapsing"功能还会提供一个链接给用户,用户单击后会显示展开后的完整查询结果集。举个例子,当你在淘宝上搜索“衣服”,衣服会有很多品牌和款式,对于同一个品牌同一个款式的衣服可能还会有很多不同的颜色(红、蓝、绿、黄、青、黑、白、灰等)和尺码(S、M、L、XL等),你肯定不希望同一个品牌同一款式的衣服因为不同颜色不同尺码就沾满了整个屏幕,用户大都希望对于同一个品牌同一款式的衣服就显示一条即可。但是对于solr而言,不同颜色不同款式在索引中其实是不同的Document,即是按照多个索引文档来存储的,"Field Collapsing"功能就是用于解决此类问题,他会按照指定值对索引文档进行收缩折叠,对于同一个值会落入同一个分组最终只返回top one,这就是"Field Collapsing"。

此外,solr中的结果集分组功能还提供了其他有用的特性。一般在solr中对查询结果集进行分组显示你可以采用Facet方式,但是Facet仅仅返回每个纬度下的统计数字,而solr中的Group除了会统计每个分组的索引文档个数,还会返回每个分组下匹配的索引文档。另外一个不同点就是Group可以基于指定sort参数对分组中的索引文档进行排序。Solr中的Group可以基于域值、基于域值经过Function动态计算后得到的值、任意查询进行分组查询。尽管Solr Group乍一看有点复杂,我们会通过各种查询示例来演示这些大部分非常有用,但其实使用非常简单的功能。再开始进入Solr Group学习之前,我们有必要先弄清楚"Result Grouping"和"Field Collapsing"之间的区别,这也是Solr新手经常容易感觉迷惑和混淆的两个概念。

二、"Result Grouping" VS "Field Collapsing"

Solr新手经常会问到的一个问题就是:为什么Solr中的分组会有两个名称"Result Grouping"和"Field Collapsing",两者有什么区别?

Field Collapsing用于解决对于索引文档中大部分域的域值相同,只有个别域的域值不同的情况下只返回一个索引文档,正如我们上面所举的衣服颜色尺码例子那样。早期版本中,Field Collapsing这个功能是为扩展Solr而开发,有个非正式的名称叫做"Field Collapsing Patch"。后来这个功能越来越通用了就被纳入到了正式版本中。

而Result Grouping通常表示更常用的结果集分组功能。尽管Field Collapsing在指定域的多个重复值上返回单个索引文档时需要Result Grouping的支持,但Field Collapsing其实也支持对于单个查询返回多个结果集或者多个分组。

Result Grouping相比Field Collapsing而言,它表示一种更通用的使用场景即结果集分组,而Field Collapsing更多是对传统的结果集分组功能的一个扩展。Field Collapsing还有一种更高效的实现方式(使用Collapsing Query Parser实现),但这种方式使用起来有一些限制,他要求在Collapsing之前必须先对索引文档进行排序,但是在某些场景下,这种方式会很有用,稍后会详细讲解。本节主要目标是Solr的结果集分组功能即Result Grouping。

三、按照指定域分组

假设你在访问一个电子商务网站,你输入一个搜索关键字,它可能返回很多结果集,但可能都是同一类的商品仅仅只是颜色尺码不同或者仅仅只是发货方不同,这对于你来说或许并不是你所期望的,你会希望对于同一类商品显示一个就够了。正由于有了Field Collapsing的存在,是的这种需求的实现变得简单,同时它也允许你返回的结果集中的每个索引文档具有多样化或者差异性。假如你搜索关键字"spider man"(蜘蛛侠),如果返回的结果集顶部是"The Amazing Spiderman-2012",但是随后有100个重复项,且假设这100项结果的相关性评分都是相同的,那么前100项结果应该只返回顶部那个就行了。那么你可以对返回的结果集按照名称分组,那么"The Amazing Spiderman-2012"就只会显示一次。如果你将同一个商品在一个页面重复展示10次、100次,这显然会降低用户继续搜索"spider man"的欲望。在大部分情况下,使用Result Grouping来收缩折叠查询结果集可以改善用户体验。

http://localhost:8080/solr/ecommerce/select?fl=id,product,format&
sort=popularity asc&
q=spider-man

返沪的查询结果部分显示如下:

"response":{
    "numFound":18,
    "start":0,
    "docs":[
        {
            "id":"4",
            "format":"dvd",
            "product":"The Amazing Spider Man - 2012"
        },
        {
            "id":"5",
            "format":"blu-ray",
            "product":"The Amazing Spider Man - 2012"
        }

从返沪的查询结果可以看出,部分结果中的product域的域值是相同的,只是format值不同,这跟我们之前举的例子:“衣服的品牌款式等信息都相同只是颜色尺码不同”差不多,同一个商品因为某些值不同而被显示多次,这是很糟糕的用户体验。接下来我们尝试开启Group,请执行如下查询示例:

http://localhost:8080/solr/ecommerce/select?fl=id,product,format&
sort=popularity asc&
q=spider-man&
group=true&
group.field=product&
group.limit=1

返回结果部分显示如下:

"product":{
    "matchs":18,
    "groups":[
        {
            "groupValue":"The Amazing Spider Man - 2012",
            "doclist":{
                "numFound":2,
                "start":0,
                "docs":[
                    {
                        "id":"4",
                        "format":"dvd",
                        "product":"The Amazing Spider Man - 2012"
                    }

关于Solr中的Group有几个关键点值得注意一下:首先需要明白Solr中的Group功能必须指定group=true参数来显式开启。然后你需要指定group.field参数,表示对那个域进行分组。根据指定域分组后,该域的域值相同的索引文档会落入同一分组内,而group.limit参数用于指定同一分组内最多返回多少个索引文档,这里我们设置为1即表示对于每个分组内只返回1个索引文档。当你想移除所有“重复”的索引文档时,group.limit参数设置为1对你来说可能会有意义。注意,这里说的“重复”只是我们主管感觉上的重复,其实索引文档的个别域的域值还是不同的,严格意义上来说两者并不是完全重复的,只是看起来像是“重复”了。

返回的groupValue属性表示每个分组,值为分组域的每个唯一域值,"doclist"下的numFound属性表示当前分组下有多少个索引文档。

如果你仅仅只想要移除“重复”的索引文档,并不需要其他额外的分组信息,比如每个分组下匹配的索引文档总数,那么你可以通过设置group.main=true参数来合并每个分组的结果形成一个扁平化的列表并最后在主结果集展示区"docs"部分显示,下面查询示例演示了group.main参数的使用:

http://localhost:8080/solr/ecommerce/select?fl=id,product,format&
sort=popularity asc&
q=spider-man&
group=true&
group.field=product&
group.mian=true

返回的查询结果集部分如下所示:

"response":{
    "numFound":18,
    "start":0,
    "docs":[
        {
            "id":"4",
            "format":"dvd",
            "product":"The Amazing Spider Man - 2012"
        },
        {
            "id":"6",
            "format":"dvd",
            "product":"The Amazing Spider Man - 2002"
        }

从上面返回的查询结果集来看,就像我们没有使用group查询似的。但是开启group.mian参数有个最大缺点就是没有返回每个组下匹配的索引文档的总数,但是如果每个分组下匹配的索引文档的总数这个数值对你的搜索程序来说并不关心的话,那么你就可以将group.mian设置为true,同时不用解析处理两种格式的查询结果集。而且开启group.mian参数之后,你还无法获取每个分组的名称即groupValue值,但是这个值通常可以通过分组域的域值推断出来。开启group.mian参数另外一个缺点就是它只支持单个分组,使用Result Grouping来实现Field collapsing最大的缺点就是他的执行性能比标准查询请求还差。值得庆幸的是,自Solr4.6开始,提供了一种全新的基于Collapse Query Parser实现的更高效的Field collapsing,他会在主结果集展示区"docs"部分显示分组查询结果集,就像你使用group.main=true参数。

目前为止,我们还仅仅只是对单个域进行分组,其实Solr Group还支持对多个域进行分组,比如group.field=type & group.field=format,如果同时还指定了group.mian=true,那么solr会只返回之后一个分组。你还可以指定grouping.format参数来设置分组结果集的输出格式,group.format=simple会类似group.main那样扁平化的列表形式展示分组内的每个索引文档,但此时不是在主查询结果集展示区"docs"部分显示:

//group.format参数的使用
http://localhost:8080/solr/ecommerce/select?fl=id,product,format&
sort=popularity asc&
q=spider-man&
group=true&
group.field=product&
group.format=true

//group.mian参数的使用以及多个域分组
http://localhost:8080/solr/ecommerce/select?fl=id,product,format&
sort=popularity asc&
q=spider-man&
group=true&
group.field=type&
group.field=format&
group.main=true

//默认多个域分组的结果集输出格式测试
http://localhost:8080/solr/ecommerce/select?fl=id,product,format&
sort=popularity asc&
q=spider-man&
group=true&
group.field=type&
group.field=format

四、每个分组返回多个文档

Solr的Group分组查询实际上并不仅仅只是用来对每个域的唯一值只返回单个索引文档(前提是group.limit=1),回顾我们之前章节中的e-commerce演示示例,假设仅仅只是收缩折叠重复的索引文档,我们可以保证每个分组都返回不超过固定数目的索引文档。当分组数目很多时,我们可以通过rows参数来限制返回的分组总个数,start表示分组返回的起始索引位置(从零开始计算),这里跟普通的Query查询分页有点类似,这里是对返回的所有分组进行分页。

http://localhost:8080/solr/ecommerce/select?fl=id,product,format&
sort=popularity asc&
q=spider-man&
group=true&
group.field=type&
group.limit=3&
rows=5&
start=0&
group.offset=0

从上面的查询示例可以得知以下几点:首先你需要注意group.limit参数表示限制每个分组返回的索引文档最大个数,但并不表示每个分组必须返回3个索引文档,有可能该分组下只有2个索引文档。group.limit参数只是设置一个上限值。第二点,你需要注意的是rows=5参数控制的不是有多少条索引文档返回,而是最多有几个分组被返回,在默认的分组输出格式中,rows和start参数是应用于每个分组,而不是每个分组内的索引文档。这两个参数搭配在一起使用其实就是对返回的所有分组进行分页。group.offset参数用于控制每个分组内的索引文档的起始偏移量,比如某个分组下有10个索引文档,但group.limit设置为3,那么默认该分组下只会返回0,1,2这3个索引文档,如果group.offset这是为2,即表示每个分组从第2个位置开始返回索引文档。注意:这里的offset(偏移量)是从0开始计算的,所以此时返回的3个所以文档就是2,3,4这3个索引文档,而且还要注意,group.offset参数值不能大于等于每个分组下所有索引文档的总个数。

最后比较重要的一点就是分组中的排序问题。先假设分组查询未启用,那么默认所有的索引文档会按照相关性评分从高到底排序,当开启分组后,相当于在每个分组内部求索引文档评分最大值,直到所有分组内的评分最大值计算完之后,最后按照每个分组内的最大评分从高到低进行排序。这就好比先在每个班里挑选成绩最好的学生,然后把每个班里成绩最好的学生派去参加竞赛然后按照最后的成绩排名,每个参加竞赛得学生最后的成绩排名代表他所在班级的排名。

在每个分组内部,索引文档也会进行排序,默认是按照id域从小到大进行排序。当你将group.limit参数设置为1且group.mian=true或者group.format=simple时,那么默认就是按照id域从小到大进行排序。

五、按照Function动态计算值分组

除了能够按照指定域的唯一性域值进行分组外,Solr还支持两种分组方式,第一种有点类似于按照指定域进行分组,但是它允许你对指定域应用Function QUery动态计算值,最后按照动态计算值进行分组。第二种就是按照Query进行分组,它允许同时执行多个Query并返回独立的结果集。

按照Function进行分组你需要指定group.func参数来完成,这里暂时不全面介绍Solr中所有的Function,会留到后面专门讲解。下面这个查询示例演示如何按照Function进行分组:

http://localhost:8080/solr/ecommerce/select?fl=id,product,format&
sort=popularity asc&
q=spider-man&
group=true&
group.limit=3&
rows=5&
group.func=map(map(map(popularity,1,5,1),6,10,2),11,100,3)

在上面这个示例中,group.func参数指定的函数尝试按照popularity(流行度)将索引文档分3个等级,你会发现返回的分组结果集跟按照指定域进行分组是类似的,唯一的区别就是按照Function分组作用对象是Function(函数)动态计算得到的值,而按照指定域进行分组的作用对象是分组域的每个唯一性的域值。这就有点类似于SQL语句里的按照count()/sum()/avg()这些函数动态计算值进行分组和按照指定字段分组的区别。这里的map(popularity,1,5,1)其实就是对popularity域的域值进行一个映射,表是popularity域值在[1,5]这个区间内的落入第1组,同理(map(popularity,1,5,1),6,10,2)表示popularity域值在[6,10]这个区间内的落入第二个组,以此类推。

Function是可以嵌套的,正如你在上面示例中所看到的那样,我们对map函数嵌套了3次,这意味着你可以结合多个函数灵活控制函数的动态计算值。如果按照函数分组对于你来说,还有太多限制的话,你还可以按照Query进行分组,这样你就可以将任意你指定的值进行分组。

六、按照任意Query分组

通过前面章节了解到,我们可以通过group.field参数对指定的域进行分组,从而达到对分组域的每个特定值下匹配的索引文档进行收缩折叠。如果group.limit参数设置为1,还可以实现对每个分组下重复索引文档进行“删除”。你除了可以对提前指定域的域值进行分组之外,还可以动态的对任意查询进行分组,你可以定义多个Query,同时对多个Query进行分组,就好比对多个域进行分组。为了演示Solr Group的这种功能,让我们尝试对3个Query进行分组:一个查询匹配所有type=Movies的产品,一个查询所有包含"games"关键字的,一个查询所有包含"The Hunger Games"这个短语的。

http://localhost:8080/solr/ecommerce/select?fl=id,type,product,format&
sort=popularity asc&
q=*:*&
group=true&
group.limit=2&
group.query=type:Movies&
group.query=games&
group.query="The Hunger Games"

从上面的查询示例我们可以获取到以下3点心得

对任意的分组查询,不管是group.field或group.func还是group.query,你都可以返回多个分组。

你可以在原查询基础之上执行多个分组子查询,这里的多个分组都是一个分类上分组,并不是多个分组的叠加,这里跟SQL里的group by a,b不同。多个分组之间是独立的,之间并没有联系。

按照某个域分组,那么某个索引文档必定只能属于一个分组,但是如果按照多个query分组,那么某个索引文档可能属于多个分组。

七、Group的分页与排序

在Solr的普通查询中,我们使用rows参数俩告诉solr最多返回多少个索引文档,然而在Group分组查询中,这里有一点复杂,rows参数到底限制的是什么呢?你是想限制每个分组下返回索引文档的个数还是所有分组的个数,还是所有分组总共返回的索引文档总个数?相似的问题还有当你在分组查询中使用start参数进行分页以及使用sort参数对分组内的索引文档进行排序,这些参数表示什么含义呢?Group查询的相关参数如下:

在Group分组查询中,rows参数用于限制返回的分组个数,start参数用于控制对分组进行分页时第一个分组的起始位置(从零开始计算)。因此在Group分组查询中,将start参数与rows参数搭配使用,可以实现对分组的分页查询。sort参数用于控制如何对每个分组进行排序,默认是基于当前分组下评分最高的索引文档的分数从高到低排序,而不是控制分组内每个索引文档的排序。

group.limit参数用于指定每个分组内最多返回多少个索引文档,group.offset参数用于分组内返回的索引文档的起始位置(从零开始计算),将此参数与group.limit参数结合使用,可以实现对分组内的索引文档进行分页。而group.sort参数允许你对分组内部的每个索引文档进行排序。

八、Group & Facet

默认Facet查询统计是基于q参数的查询结果集的,而不是Group分组查询返回的结果集,这意味着不管你是否开启分组查询,Facet查询统计的结果都是一样的。

http://localhost:8080/solr/ecommerce/select?fl=id,type,product,format&
sort=popularity asc&
q=*:*&
group=true&
facet=true&
facet.mincount=1&
fq=type:Movies&
facet.field=type&
group.field=product

返回的查询结果部分如下所示:

"product":{
    "matches":11,
    "groups":[
        {
            "groupValue":"The Hunger Games",
            "doclist":{
                "numFound":1,
                "start":0,
                "maxScore":1.0,
                "docs":[
                    {...},
                    {...},
                    ......
                 ]
             }
         },
         ......
    ]
    "facet_counts":{
        "facet_queries":{},
        "facet_fields":{
            "type":[
                "Moves",11
            ]
         }

从上面返回的结果集你应该注意到尽管Filter Query type:Movies匹配了11个索引文档,但按照product域分组查询后返回了6个“索引文档”,这是因为这11个索引文档中有些索引文档的product域的域值相同仅仅只是type域的域值不同而已,这里的6个是按照product域分组后有6个分组。另一个你需要注意的是Facet查询统计仍然是基于q和fq参数的,而不是基于group分组查询。此外你还需要注意的是每个分组内的numFound属性值虽然统计是正确的,但是每个分组下返回的索引文档数与numFound值并不一致,这是因为group.limit默认值为1,设置为-1就全部返回了。最后需要注意的是,Group分组查询和Facet查询统计都是基于q和fq参数的,两者没有任何联系。此时可以设置group.facet=true,请看下面的示例:

http://localhost:8080/solr/ecommerce/select?fl=product&
sort=popularity asc&
q=*:*&
group=true&
facet=true&
facet.mincount=1&
group.format=simple&
fq=type:Movies&
facet.field=type&
facet.field=product&
group.facet=true

返回的查询结果如下所示:

"product":{
    "matchs":11,
    "doclist":{
        "numFound":11,
        "start":0,
        "docs":[
            {
                "product":"The Hunger Games"
             },
             {...},
             ......
        ],
        "facet_counts":{
            "facet_queries":{},
            "facet_fields":{
                "type":[
                    "Movies",6
                ]
         }

从上面返回的结果集我们可以发现此时Facet查询统计得到的数字是6,而我们的Group查询返回的分组个数也是6,说明此时我们的Facet查询统计实际是分组总个数了。换句话说,此时Facet是基Group查询后返回的结果集进行统计。类比到关系型数据里SQL Group BY语法,group.facet=true其实就相当于Group by后返回的结果集条数,举个例子,假如我们有一张user表,有id、name、province3个字段,我们执行如下SQL语句:

select count(id),province from user group by province;

上面的SQL语句其实就是对用户按照省份进行分组,返回的结果如下:

正如你所看到的那样,该SQL一共返回了6个分组,第一列为每个分组下的记录总条数,而我们的group.facet=true参数开启之后,统计的其实就相当于分组的总个数,即我们这里GROUP BY SQL语句返回的记录总条数。

默认通过group.facet=true设置是全局生效的,如果不想全局生效,你只想单独对某个域进行设置,那么可以这样指定,语法如下:

f.<fieldName>.group.facet=true

这也是Solr中很多请求参数所支持的为单独某个域局部设置的语法。这里的表示你想应用group.facet=true的Facet域名称即facet.field的某个域名称。

遗憾的是,的呐喊你按照多个域进行分组时,Facet查询统计并不会统计每个分组域返回的分组总个数,只会统计第一个分组域Group之后返回的分组总个数。具体请执行如下示例:

http://localhost:8080/solr/ecommerce/select?fl=product&
sort=popularity asc&
q=*:*&
group=true&
facet=true&
facet.mincount=1&
group.format=simple&
fq=type:Movies&
facet.field=type&
facet.field=product&
facet.field=format&
group.facet=true

九、Group分布式查询

在使用Solr的Group分组查询时,需要考虑的一个问题就是,我们该如何进行Group分布式查询。不像标准的Solr查询,Group分组查询不能完全在分布式模式下工作,更准确地说,它是运行在伪分布式模式下。结果集聚合是在分布式模式下运行的,但是聚合需要的每个结果集确是分别在每个本地Core上单独进行Group计算而来的。

这样为什么会有问题?因为你分组查询依赖的值可能是随机分布在多个Solr Core上,这样你Group分组统计的数字可能会不准确,比如你对产品按照制造商进行分组查询,那么得到的总分组个数可能会约等于实际每个Core上统计的分组总个数。当且仅当,你的索引文档按照制造商这个域进行分区时,你会得到正确的分组个数。因为每个分组只会存在于一个唯一的shard上。当在分布式模式下使用Solr Group功能时,你需要时刻记住这点,你需要设置group.ngroups=true,以保证能够返回分组的总个数。如果你的索引没有按照你的分组进行分区,并且你执行的是分布式搜索,那么返回的分组总个数可能仅仅为粗略值。

除了需要考虑分区的问题之外,某些group参数在分布式模式下也不支持,比如group.truncate、group.func。group.truncate参数如果设置为true,则表示Facet查询统计会基于当前查询匹配的每个分组相关性最高的文档进行统计数量。group.func参数表示基于Function Query动态计算值进行Facet查询统计,参数值一般是表示Function Query的查询表达式。

最后你需要注意的是,Solr Group分组查询不支持多值域,即你不能在一个多值域(即multiValue=true)上进行分组查询。虽然Solr Group分组查询支持分词域(即域类型为TextField),但Solr并不会将分词后得到的每个Token作为GroupValue,你也不能选择按照那个Token来分组,而且Solr也不保证会按照分词后得到的每个Token进行分组,有可能会丢弃部分Token。因此对分词域进行分组查询没有太大意义,尽管对分词域进行分组查询不会报错,但最后得到的结果并不可靠,因此,一般建议你将需要分组的域设置为不分词的单值域。

十、Group缓存

尽管Solr Group查询功能很强大,跟Solr标准查询相比,查询速度那是相当的慢。对大数据量的索引文档按照指定域进行分组查询比不分组查询要花费更多的时间。

为了提升Solr Group分组查询的性能,你可以通过指定group.cache.percent参数来为分组查询启用缓存。group.cache.percent参数的默认值为零,取值返回为[1,100]则表示启用Group缓存。Solr内部会分两次查询来执行,group.cache.percent参数表示第一次匹配查询的索引文档应该被缓存的百分比,缓存第一次查询是为了提升第二次查询的性能。group.cache.percent参数设置越大,那么你的Group查询占用的内存将会更多,所以你应该设置不同的百分比来测试出以尽量少的内存占用来获取最快的所以速度。当你的第一次查询是Boolean查询、Wildcard(通配符)查询、或者Fuzzy(模糊)查询,此参数能显著提升查询性能。

如果你只需要返回每个分组,并不需要返回每个分组下包含的所有索引文档,而且你也不需要对分组进行排序,那么你可以采用更高效的方式来实现"Field collapsing"(只返回每个分组即域折叠)。

十一、使用Collapsing Query Parser高效的Field Collapsing

Solr中提供一种Collapsing Query Parser(对应CollapsingQParserPlugin类),它允许你将查询结果集为每个唯一值折叠收缩成一个单一的结果,而且使用起来还不复杂。使用语法如下所示:

/select?q=*:*&fq={! collapse field=fieldToCollapseOn}

这个查询会为field属性指定的域下每个唯一值只返回一个索引文档,而且返回的那个索引文档是包含该域值的所有索引文档中评分最高的。你还可以通过该设置min/max属性来控制返回的索引文档,比如:

/select?q=*:*&fq={! collapse field=fieldToCollapseOn min=numericFieldName}

/select?q=*:*&fq={! collapse field=fieldToCollapseOn max=numericFieldName}

/select?q=*:*&fq={! collapse field=fieldToCollapseOn max=sum(field1,field2)}

此外,Collapsing Query Parser通过nullPolicy策略以多种方式支持对域值为空的索引文档的处理,nullPolicy策略用于决定如何处理这些包含了空值的索引文档,默认包含3种策略:ignore、expand、collapse。ignore表示移除所有包含空值的索引文档,ignore也是nullPolicy默认的策略。使用语法如下所示:

/select?q=*:*&fq={! collapse field=fieldToCollapseOn nullPolicy=ignore}

当nullPolicy=collapse时,那么所有包含空值的索引文档会被分为一组,并最终折叠收缩为一个文档。如果你想要返回所有包含空值的索引文档,那么你可以指定nullPolicy=expand。

理论上来讲,Collapsing Query Parser返回的结果集与你使用分组查询(group=true)且指定了group.main=true、group.limit=1、sort=score desc(或者指定按照指定的数字域排序)返回的结果集相似。

但是使用Collapsing Query Parser时又一个限制你需要注意:Group分组查询首先会在文档折叠收缩之前对所有匹配的索引文档进行排序,但是Collapsing Query Parser只考虑单一因素(相关性评分或者min/max值),因此对Collapsing Query Parser设置sort参数无效。此外你还可以对min/max属性应用Function函数,通过Function函数动态计算值来确定min或max值,从而确定top one索引文档,使用示例如下:

/select?q=*:*&fq={! collapse field=fieldToCollapseOn max=sum(product(field1,1000000),cscore())}

上面这个示例中,我们先按照field1域降序排,然后按照cscore函数用鱼仔索引文档折叠收缩之前计算索引文档的分数。

十二、Solr Group VS SQL Group by

在本章中,我们以及学习了如何使用Solr中的Result Grouping(结果集分组)和Field Collapsing(域折叠收缩)功能,Result Grouping功能有点类似SQL里的Group by ,但是Solr中的Result Grouping(结果集分组)不仅仅支持对某个域进行分组,还是对任意的Query、任意的Function在查询时动态计算值进行分组。通过将group.limit参数设置为1可以实现Field Collapsing(域折叠收缩)功能,看起来像是将域值重复的索引文档“删除”了。你还可以通过指定多个group.field参数支持在单个查询请求中执行多个域的分组查询,但是注意,这里并没有实现分组嵌套的效果。何为分组嵌套?还记得我们SQL里的Group By吗?在SQL中假如我们按照两个字段进行分组,我们可以这样写:

select * from table_name group by location,type

在SQL中会首先按照location字段进行分组,然后再每个分组内部再按照type字典进行分组,但会的结果可能是类似下面这样的结构:

-Location X
    --Type1
        ---records
    --Type2
        ---records

-Location Y
    --Type1
        ---records
    --Type2
        ---records

而solr中你指定多个group.field进行分组,并不会嵌套分组查询,而是单独指定的每个域进行分组查询,就好比是SQL里执行2次Group By:

select * from table_name group by location

select * from table_name group bytype

因此,在Solr中不支持类似SQL中的嵌套分组查询,关于Solr Group的嵌套查询请关注Solr官方JIRA,不过目前仍然没有实现嵌套分组查询,至于后续版本是否会实现,敬请期待。。。

⚠️ **GitHub.com Fallback** ⚠️