Solr Facet的应用

Facet是Solr的高级搜索功能之一,Solr作者给出的定义是导航(Guided Navigation)、参数化查询(Paramatic Search)。Facet的主要好处是在搜索的同时,可以按照Facet条件进行分组统计,给出导航信息,改善搜索体验。Facet搜索主要分为以下几类:

1. Field Facet
搜索结果按照Facet的字段分组并统计,Facet字段通过在请求中加入”facet.field”参数加以声明,如果需要对多个字段进行Facet查询,那么将该参数声明多次,Facet字段必须被索引。例如,以下表达式是以DEAL的status和can_buy属性为facet.field进行查询:

select?q=*:*&facet=true&facet.field=status&facet.field=can_buy&wt=json

Facet查询需要在请求参数中加入”facet=on”或者”facet=true”让Facet组件起作用,返回结果:

"facet_counts”: { 
     "facet_queries": {}, 
     "facet_fields":  { "status": [ "32", 96, 
                                     "0", 40, 
                                     "8", 81, 
                                    "16", 50, 
                                   "127", 80, 
                                    "64", 27 ] ,

                       "can_buy": [ "true", 236, 
                                    "false", 21 ]
                      }, 
     "facet_dates": {}, 
     "facet_ranges": {} 
 }

分组count信息包含在“facet_fields”中,分别按照"status"和“can_buy”的值分组,比如状态为32的DEAL数目有96个,能购买的DEAL数目(can_buy=true)是236。

Field Facet主要参数:

 facet.field:Facet的字段
 facet.prefix:Facet字段前缀
 facet.limit:Facet字段返回条数
 facet.offset:开始条数,偏移量,它与facet.limit配合使用可以达到分页的效果
 facet.mincount:Facet字段最小count,默认为0
 facet.missing:如果为on或true,那么将统计那些Facet字段值为null的记录
 facet.method:取值为enum或fc,默认为fc,fc表示Field Cache
 facet.enum.cache.minDf:当facet.method=enum时,参数起作用,文档内出现某个关键字的最少次数

2. Date Facet
日期类型的字段在索引中很常见,如DEAL上线时间,线下时间等,某些情况下需要针对这些字段进行Facet。时间字段的取值有无限性,用户往往关心的不是某个时间点而是某个时间段内的查询统计结果,Solr为日期字段提供了更为方便的查询统计方式。字段的类型必须是DateField(或其子类型)。需要注意的是,使用Date Facet时,字段名、起始时间、结束时间、时间间隔这4个参数都必须提供。
与Field Facet类似,Date Facet也可以对多个字段进行Facet。并且针对每个字段都可以单独设置参数。

简单实例参考:

&facet.date=birthday
&facet.date.start=2014-01-00T09:15:00Z
&facet.date.end=2014-12-00T09:15:00Z
&facet.date.gap=%2B1MONTH
 返回结果如下所示:

"facet_counts":{
    "facet_queries":{},
    "facet_fields":{},
    "facet_dates":{
      "birthday":{
        "2013-12-31T09:15:00Z":0,
        "2014-01-31T09:15:00Z":0,
        "2014-02-28T09:15:00Z":0,
        "2014-03-28T09:15:00Z":0,
        "2014-04-28T09:15:00Z":0,
        "2014-05-28T09:15:00Z":0,
        "2014-06-28T09:15:00Z":0,
        "2014-07-28T09:15:00Z":0,
        "2014-08-28T09:15:00Z":0,
        "2014-09-28T09:15:00Z":1,
        "2014-10-28T09:15:00Z":5,
        "2014-11-28T09:15:00Z":3,
        "gap":"+1MONTH",
        "start":"2013-12-31T09:15:00Z",
        "end":"2014-12-28T09:15:00Z"}},
    "facet_ranges":{}}}
Date Facet参数说明:

1).facet.date
	该参数表示需要进行Date Facet的字段名,与facet.field一样,该参数可以被设置多次,表示对多个字段进行Date Facet.
2).facet.date.start
	起始时间,时间格式为1995-12-31T23:59:59Z
3).facet.date.end
	结束时间.
4).facet.date.gap
	时间间隔.如果start为2009-1-1,end为2010-1-1.gap设置为+1MONTH表示间隔1个月,那么将会把这段时间划分为12个间隔段.
        注意+因为是特殊字符所以应该用%2B代替.
5).facet.date.hardend
	取值可以为true|false,默认为false.它表示gap迭代到end处采用何种处理.举例说明start为2009-1-1,end为2009-12-25,gap为+1MONTH,
	hardend为false的话最后一个时间段为2009-12-1至2010-1-1;
	hardend为true的话最后一个时间段为2009-12-1至2009-12-25.
6).facet.date.other<pre name="code" class="plain">&facet.date=birthday
&facet.date.start=2014-01-00T09:15:00Z
&facet.date.end=2014-12-00T09:15:00Z
&facet.date.gap=%2B1MONTH
&facet.date.other=all
&f.birthday.facet.mincount=3 --单独对某个字段起作用,把统计值小于3的过滤掉

取值范围为before|after|between|none|all,默认为none,before会对start之前的值做统计,after会对end之后的值做统计,between会对start至end之间所有值做统计.如果hardend为true的话,那么该值就是各个时间段统计值的和.none表示该项禁用.all表示before,after,all都会统计.

 
 实例参考,演示fact.date.other、跟单独对某个字段起作用 
 
&facet.date=birthday
&facet.date.start=2014-01-00T09:15:00Z
&facet.date.end=2014-12-00T09:15:00Z
&facet.date.gap=%2B1MONTH
&facet.date.other=all
&f.birthday.facet.mincount=3 --单独对某个字段起作用,把统计值小于3的过滤掉
返回结果如下:
"facet_counts":{
    "facet_queries":{},
    "facet_fields":{},
    "facet_dates":{
      "birthday":{
        "2014-10-28T09:15:00Z":5,
        "2014-11-28T09:15:00Z":3,
        "gap":"+1MONTH",
        "start":"2013-12-31T09:15:00Z",
        "end":"2014-12-28T09:15:00Z",
        "before":0,
        "after":0,
        "between":9}},
    "facet_ranges":{}}}


3、Facet Range

范围统计分组统计,跟Date Facet一样,只是他们定位的字段的类型不同,Data Fact是做日期的分组统计的,而Fact Range是做数字分组统计的,在次强调,是做数字分组统计的,对于字符串,日期是不可以的。

参数跟上面的Date Facet基本一致,如下,就不做解释了,参考Date Facet的各个参数

1.	facet.range
2.	facet.range.start
3.	facet.range.end
4.	facet.range.gap
5.	facet.range.hardend
6.	facet.range.other
7.	facet.range.include
参考实例:

&facet.range=price
&facet.range.start=1000
&facet.range.end=5000
&facet.range.gap=1000
&f.price.facet.mincount=2--单独对某个字段起作用,把统计值小于2的过滤掉
返回结果如下:

 "facet_counts":{
    "facet_queries":{},
    "facet_fields":{},
    "facet_dates":{},
    "facet_ranges":{
      "price":{
        "counts":[
          "1000.0",3,
          "2000.0",3,
          "3000.0",2],
        "gap":1000.0,
        "start":1000.0,
        "end":5000.0}}}}


4、 Facet Query
Facet Query利用类似于filter query的语法提供了更为灵活的Facet。通过facet.query参数,可以对任意字段进行筛选。


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

对于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.pivot -  Computes a Matrix of Constraint Counts across multiple Facet Fields. by Yonik Seeley.

facet.pivot自己的理解,就是按照多个维度进行分组查询

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参数上,效果图如下:


以下是代码实现:

QueryParser.java

query.setFacet(true);
query.addFacetField("provinceAlias","cityAlias","scenics_ws","categoryName","fthemes_ws");
query.setFacetMinCount(1);

query:

List<FacetField> ffList = response.getFacetFields();
    ResultFacet struct = ResultFacet.parse(ffList);
	if (struct.getTags().size() > 0)
	{
	    struct.getTags().get(0).setCount(data.getTotal());
	}

ResultFacet.java

private List<Pair<String, Long>> province;
	private List<Pair<String, Long>> city;
	private List<Pair<String, Long>> properties;
	private List<Pair<String, Long>> scenics;
	private List<Pair<String, Long>> tags;
public static ResultFacet parse(List<FacetField> list){
		ResultFacet f=new ResultFacet();
		for(FacetField ff:list){
			switch(ff.getName()){
			case "provinceAlias": 
				f.setProvince(toVoList(ff.getValues()));
				break;
			case "cityAlias":
				f.setCity(toVoList(ff.getValues()));
				break;
			case "categoryName":
				List<Pair<String,Long>> tags=new ArrayList<>();
				tags.add(new Pair<String, Long>("全部",1L));
				
				List<Pair<String, Long>>  qtags=toVoList(ff.getValues());
				tags.addAll(qtags);
				f.setTags(tags);
				break;
			case "scenics_ws":
				f.setScenics(toVoList(ff.getValues()));
				break;
			case "fthemes_ws":
				f.setProperties(toVoList(ff.getValues()));
				break;
			}
		}
		return f;
	}
private static List<Pair<String, Long>> toVoList(List<Count> values)
	{
		List<Pair<String, Long>> list=new ArrayList<>();
		Pair<String, Long> p=null;
		for(Count c:values){
			p=new Pair<String, Long>(c.getName(),c.getCount());
			list.add(p);
		}
		return list;
	}

Pair<K,V>:

private K name;
		private V count;
		
		public Pair(K name, V count)
		{
			super();
			this.name = name;
			this.count = count;
		}

查询效果如下图:




参考文章:

solr中facet及facet.pivot理解  http://blog.csdn.net/a925907195/article/details/42241265

跟益达学Solr5之Facet一瞥  http://iamyida.iteye.com/blog/2217148

Solr Facet技术的应用与研究  http://tech.meituan.com/solr-facet.html


Solr Facet查询: http://eksliang.iteye.com/blog/2165882



发布了64 篇原创文章 · 获赞 28 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/kevinxxw/article/details/50441576