跟益达学Solr5之Facet一瞥

Tags: java solr

Facet 属于 Solr 的高级查询部分,之所以在还没有讲解普通 Query 之前,就开始更新 Facet 查询,是因为看到很多小伙伴都在为 Facet 而困扰,其实根本原因还是对 Facet 不理解。 Facet 英文单词本意是方面的意思,但在 solr 中 Facet 一般翻译为维度的意思,举个例子,学生可以按班级来分类,可以按性别来分类,可以身高来分类,可以按年龄来分类,可以按考试分数来分类,可以按兴趣爱好分类,可以按出生地址分类等等,上面所说的班级,性别,身高,年龄,考试分数,兴趣爱好,出生地址等等这些都是把学生进行归类分组的一个个维度。可能有些骚年就要发问了,这 TM 不就是分组吗?他跟分组有什么区别?嗯,如果你能有这个疑问,起码你有在思考,如果你连这个疑问都没有,那你根本没上心。乍看貌似跟 Group 是一个概念,其实 Facet 跟 Group 还是有点区别的,比如,按考试分数统计,我们一般不会说统计 60 分有几人, 61 分有几人, 62 分有几人, 63 分有几人 …… 一直到 100 分,实际我们一般会这样统计: 60 分以下有几人, 60-70 分有几人, 70-80 分有几人, 80-90 分有几人, 90-100 分有几人。这里说的 60 分以下, 60-70 分, 70-80 , 80-90 , 90-100 这表示是分数段,即数字范围,这不是简简单单的按照某个域进行分组的,甚至会有更复杂的维度统计,比如,我要你统计各个分数段男生多少人,女生多少人,这其实就是多个查询条件组合成一个维度,说到这儿,我想大家应该都豁然开朗了, Facet 即维度不仅仅是建立在某个域上,它还可以建立在某个查询条件上,该查询条件你可以任意组合,这是 Group 分组所办不到的,如果你仅仅是对某个域进行 Facet 统计,那就跟 Group 类似了。你可以把 Facet 理解为 Group 的火力升级版,功能更强大更威猛!!!

学习 Facet 之前需要解决的另一个问题就是 Facet

一般用来解决什么问题?我还是用图说话吧!


上图中的品牌,颜色,网络,大家说,价格,热点,屏幕尺寸,系统,机身颜色,购买方式这些都是对手机进行分类的一个个维度,有些维度是手机自身所包含的属性,比如品牌,颜色,网络,系统等,而像价格,屏幕尺寸这就是区间范围了。

对于 Facet 域建议最好是只创建索引不进行分词不进行存储,因为 Facet 域的值只是用来显示给用户看的,根据域值进行统计总数,那如果你想要对品牌进行普通查询,你可能需要对品牌域进行分词且你需要在页面上显示品牌的域值,这似乎跟 Facet 的设计初衷自相矛盾了,其实你可以使用 Solr 里的 copyField 来解决,对于普通查询,你可以直接对品牌域使用 TextField 进行索引分词,而对于 Facet 统计,你可以使用 CopyField 且指定域类型为 solr.StringField 即不进行分词即可。

下面我们就拿上回做 MySQL 数据增量更新示例里使用的测试数据来做我们的 Facet 测试,启动你的 Tomcat, 打开 Query

界面,如图:


勾选了 Facet 即表示开启了 Facet 高级查询功能,不过遗憾的是, Solr Web UI 的 Query 表单查询界面里支持的 Facet 配置参数太少了,就 3 个,所以通过这个 UI 界面进行 Facet 测试会受到限制,所以我建议大家还是通过直接在浏览器里输入请求 URL 来进行测试吧,只要保证你的 Tomcat 是处于正常启动状态即可。把 Facet 勾选后,在 facet.field

栏里填写你要对哪个域创建一个维度,如图:


查询结果如图:


实际请求 URL 为:

http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.field=brand  

你可能要问了,那如果我想对两个域进行 facet 呢,对不起,通过 Solr 的 Web UI 无法实现,只能通过在浏览器地址栏里敲请求 URL 方式来进行测试,

http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.field=brand&facet.field=color  

上面 URL 表示同时对 brand 和 color 这两个域进行 Facet

统计查询,返回结果如下:


为了照顾一些小白,这里我需要对 URL 里的一些请求参数做下说明, indent=true 即表示需要对返回的数据进行格式化缩进,否则返回的数据将会显示为一整行,不利于你阅读,

wt 即 write type 表示数据输出类型,如图 json.xml,html

等等,你懂的。还有一些其他的参数,如下:


这部分内容我曾在群里贴过,这里我还是再次分享出来,因为这些参数名称大都是缩写形式,有些小白会不懂这些参数的含义看到后就蒙 B 了, url 里要添加什么参数直接就这样写就行了: & 参数名 = 参数值

Facet.prefix :用来过滤那些 Facet 维度会被返回,即符合指定前缀字符串的维度才会显示,具体看示例:

url: http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.field=brand&facet.field=color&facet.prefix=  小

返回结果如图:


你会发现 brand 维度里只有小米这一个维度了,这个很好理解,而 color 这个维度里返回为空,因为 color 维度里没有以小字开头的。

facet.sort: 表示按照什么来排序,默认值是 count 即按照每个维度的统计数字降序进行排序的,另一个可选值是 index, 表示按照维度名称的 ASCII 值进行升序排序,测试如下:

url: http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.field=brand&facet.sort=index  

返回结果如下:


直接 facet.field 这样设置是对所有维度有效的,如果你只想对单个维度进行排序设置,你可以这样,

url: http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.field=brand&facet.field=color&f.brand.facet.sort=count&f.color.facet.sort=index  

返回结果如下:


facet.limit :表示每个维度最多返回几组,默认是全部返回,测试如下:

url: http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.field=brand&facet.field=color&f.brand.facet.sort=count&f.color.facet.sort=index&facet.limit=3  

返回结果如下:


如果你只想对某一个维度进行限制,那你可以这样:

url: http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.field=brand&facet.field=color&f.brand.facet.sort=count&f.color.facet.sort=index&f.brand.facet.limit=3  

返回结果如下:


注意 f.brand.facet.limit 写法的含义,前面的 f 即 field 的缩写,后面的我想你懂的,不要只知道 f. 域名称 .facet.limit 这样写,还要知道这样写的含义,这样有助于你记忆。其实 solr 里有很多这样的缩写。

facet.offset: 表示从那一组开始显示,默认是从 0 开始,这个参数也支持全局设置和单个维度设置,和 facet.limit 类似,测试如下:

url: http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.field=brand&facet.field=color&f.brand.facet.sort=count&f.color.facet.sort=index&f.brand.facet.limit=3&f.brand.facet.offset=1  

测试结果如下:


facet.mincount 表示每组统计的数字的最小值,默认最小值为 0 ,这个参数也支持对单个维度进行设置,即支持 f. 域名 .facet.mincount ,直接 facet.mincount 设置是全局生效。具体测试就留给你们自己去操作了,我想现在你们应该会了,不需要我再截图了。

facet.missing: 表示如果匹配的 document 当中有域值为 null 的时候,该 document 是否应该统计在内,默认 facet.missing=false 即不统计在内,

facet.method :表示使用哪种策略或者说方法进行 facet 统计,可选值有 enum( 枚举迭代 ),fc 即 field cache( 域缓存 ) , fcs(field cache per segment) 即每个段文件的域缓存,默认值是 fc, 如果你的索引数据需要频繁更新的情况下,才推荐使用 fcs, 如果你的 facet 域存在少量唯一的域值即 facet 统计出来的组数只有几个 (10 个以下 ) 时,才推荐使用 enum 进行遍历而不使用 field Cache ,只有 Facet 统计出来组数比较多的时候使用 Field Cache 才有价值。这个查询参数同样支持单个维度设置,你懂的,具体测试就留给你们自己去操作了。

facet.enum.cache.minDf: 表示当你 facet.method=enum 时,指定 term 的 document 频率最小值,小于这个频率的 document 不进行 filterCache, 这个值越大相对于 FilterCache 来说,占用内存越少,但查询时间也增加。一般这个值推荐设置为 20-50 ,,它并不影响最终的 facet 返回结果,只是影响 facet 查询性能。这个参数同样支持全局设置和单个维度设置

facet.threads :表示最多开启多少个线程并行去延迟加载域的值,默认为 0 ,即表示不额外

开辟线程,设置为负数即表示线程数不受限制

facet.date: 表示对某个域进行日期范围的 facet 查询,参数值一般为 dateField 的域名称,如果你使用了 facet.date 参数,那么 facet.date.start. facet.date.end, facet.date.gap 需要强制性的同时搭配使用, facet.date.start 表示日期范围的起始日期, end 即表示结束日期, gap 即表示递增的公差 , 比如 +1DAY,+1MONTH,2YEARS

facet.date.hardend: 可选值为 true/false, 表示含义还是举例说明吧,比如你 facet.date.start=2015-01-01, 而 facet.date.end=2015-09-20 假如你 facet.date.gap=+1MONTH 即表示按一个月把 start 与 end 之间的时间根据 gap 值分成 9 份,如果 hardend 为 true, 那最后一份的时间范围是 2015-09-01 至 2015-09-20 ,如果 hardend 为 false, 那最后一份的时间范围就是 2015-09-01 至 2015-10-01 ,即直接无视 end 参数的限制,严格按照 gap 的间隔来算。

Facet.date.other: 可选的参数值为 before,after,between,none,all

before: 表示需要对 start 之前的日期做个统计,

after: 表示需要对 end 之后的日期做个统计

between: 表示需要对 start 与 end 之间的日期做个统计

none: 表示不做任何汇总统计

all: 表示 before,after,between 都需要做统计

示例如下:

&facet=on

&facet.date=date

&facet.date.start=2009-1-1T0:0:0Z

&facet.date.end=2010-1-1T0:0:0Z

&facet.date.gap=%2B1MONTH

&facet.date.other=all

返回结果如下:

<int name="before">180</int>

<int name="after">5</int>

<int name="between">54</int>

Facet Query: 它跟 Filter Query 有点类似,示例如下:

&facet=on

&facet.query=date:[2009-1-1T0:0:0Z TO 2009-2-1T0:0:0Z]

&facet.query=date:[2009-4-1T0:0:0Z TO 2009-5-1T0:0:0Z]

Key 操作符

阿通过 key 操作符可以为 face 查询返回的 json 串中的属性名起一个别名,示例如下:

facet.field={!key=”br”}brand  为 brand 起一个别名叫 br

facet.query=price:[10 TO 20]  返回的 json 串中属性名默认是 price:[10 TO 20], 这显然很难看,可以为她起个别名,比如 facet.query={!key=”10-20”}price:[10 TO 20] 起个别名叫 10-20

tag&ex 操作符

当你使用 filter query 时,如果你 filter Quer 作用的域刚好又是 facet 域时,统计的结果会被限定在 filter Query 范围之内,看示例:

URL: http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.field=brand&fq=brand:iPhone  

返回结果如下:


这时如果你想要统计其他组的数字时, 你就需要使用 tag 操作符来标记 filter Query, 然后在 facet.field 时,通过 tag 打的标记使用 ex 操作符排除该 tag 标记代表的 filter Query 对 facet.field 统计的影响,具体请看示例:

url: http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.field={!ex=aa}brand&fq={!tag=aa}brand:iPhone  

返回结果如下:


Facet Range 查询

即 Facet 的数字范围查询,日期范围请使用 facet.date ,不过它的使用方法跟 facet.date 日期范围查询相似,同理也有 facet.range.start , facet.range.end , facet.range.gap , facet.range.hardend , facet.range.other 你懂的,就不多说了,区别就是 facet range 一般用于数字范围检索,此配置项同样支持全局配置以及单个 facet 域配置。

Facet Pivot 查询:

啊 pivot 干嘛用的,很难用文字解释 , 我的理解是两个维度的叠加,比如我想统计每种品牌下各种颜色的手机都有多少个?可能你首先想到的是对 brand 和 coloe 域进行 facet 查询啊,测试一下看符不符合我们的预期,

URL: http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.field=brand&facet.field=color  

返回结果如下:


但我们可以通过多次查询来实现,比如:

url: http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.field=brand&fq=color :  灰色

返回结果:


如果要统计白色呢, url 改为:  http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.field=brand&fq=color :  白色,如果要统计金色呢,你懂的,那如果我们有几百种颜色呢,那岂不是要查询几百次,有没有一种方法能实现一次查询完成我们期望的统计效果呢,答案就是 Pivot Facet Query, 测试示例如下:

url : http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.pivot=brand,color

返回结果如下:


url: http://localhost:8080/solr/core2/select?wt=json&indent=true&q=*:*&facet=true&facet.pivot={!key=bc}brand,color

给 brand,color 起个 bc 别名,我只是为了演示下 key 取别名也可以作用在 facet.pivot

参数上,效果图如下:


OK, 到此 Facet 的常见查询我就讲解的差不多了,不过我们的测试都是直接敲 URL 然后贴到浏览器地址栏里去测试的,略显蛋疼,以后再介绍 solr4j 的使用吧,那样我们就能通过编程的方式来进行 Facet Query 测试了,不过理解 Facet 这些查询配置参数有助于你以后学习 solr4j,磨刀不误砍材工嘛!

本文链接:http://www.4byte.cn/learning/119726/gen-yi-da-xue-solr5-zhi-facet-yi-pie.html