在部分概述在Solr搜索描述,分类是基于索引的搜索结果方面的安排。搜索与索引方面,随着数值计算有多少匹配的文件被发现是每学期。小面很容易让用户探索的搜索结果,缩小正是他们正在寻找的结果。
根据Solr搜索概述中的描述,层面分析是基于搜索结果的排列。所有的Searcher都携带有索引term,随着匹配文档,term也被发现。层面分析简化用户对搜索结果的处理,缩小查找结果范围。

基本参数

下面的表总结了控制层面分析的一般参数:

参数描述
facet设置为true,开启层面分析
facet.query指定使用Lucene查询生成层面计数。

facet

设置为true,可以使查询响应中有层面计数。设置为false或没有值,不进行层面分析。除非设置为true,否则下面的其他参数将不起作用。默认值是空。

facet.query

这个参数允许你使用Lucene默认语法指定一个随意的查询来生成层面计数。默认情况下,Solr的层面分析自动区分相同field的不同term,并返回这些term的数量。使用facet.query,你可以重写默认行为或精确选择你希望对哪些term或表达式进行计数。在层面分析的典型实现中,你可以指定facet.query参数的数量。这个参数对以数字范围为基础的层面分析或以前缀为基础的层面分析很有用。
你可以设置对facet.query参数进行多次声明,来使不同查询使用隔离的层面约束。
在层面分析中不使用默认语法,用查询标记的名称来作为层面分析的前缀。比如,使用假设的MyFunc查询解析器,你可以设置facet.query参数为:

facet.query={!myfunc}name~fred

字面值层面分析参数

在field中基于索引的term,可以使用几个参数来触发层面分析。
使用这些参数时,最重要的是”term”在Lucene中是非常特别的概念:它涉及到分析后的索引过程中field/value对的字面值。对于包含stemming(将单词缩减为词根形式)、lowercasing(将单词转换为小写字母)或分词等操作的文本field,可能结果与你期望的不同。如果你希望Solr在完整字符串上进行分析(搜索过程)和层面分析,在schema.xml中使用copyFieldfield,对该field创建两个版本:一个Text,一个String。两个都要设置indexed="true"。(获得更多关于copyField的内容,请参见Documents, Fields, and Schema Design。)
下表中列出Solr中层面分析可以使用的参数。

参数描述
facet.field指定一个field来进行层面分析
facet.prefix限制使用指定前缀的term进行层面分析
facet.contains限制包含指定子串的term进行层面分析
facet.contains.ignoreCase如果使用`facet.contains`,忽略指定子串的大小写
facet.sort控制层面结果排序方式
facet.limit控制返回的层面结果中包含内容的数量
facet.offset指定层面结果开始位置的偏移量
facet.mincount指定层面结果中返回的最小数量
facet.missing控制除了以term为基础约束的层面分析field,Solr是否对所有匹配结果中没有值的field进行计数。
facet.metrod选择Solr进行层面分析时使用的算法或方法
facet.enum.cache.minDF(高级)指定达到term的约定数量时使用filterCache的最小文档频率(匹配项的文件数量)
facet.overrequest.count(高级)文档的数量,替代在分布式搜索每个分片中无法起作用的`facet.limit`
facet.overrequest.ratio(高级)在分布式搜索每个分片中比`facet.limit`更加有效
facet.trreads(高级)控制并行执行的field分析

下面对每个参数进行详细描述。

facet.field

该参数指定应该进行层面分析的field。遍历field的所有term,按照约定生成层面计数。在查询中,这个参数可以指定多次,对多个field进行层面分析。

如果你没有指定任何一个schema中定义的field,本节中其他参数将不起作用。

facet.prefix

该参数指定需要被进行层面分析的term需要以给定字符串作为前缀。对查询没有任何限制,只作用于响应的层面分析查询。
这个参数可以使用f.<fieldname>.facet.prefix在每个field上指定前缀。

facet.contains

该参数指定需要被进行层面分析的term需要包含给定字符串。对查询没有任何限制,只作用于响应的层面分析查询。
这个参数可以使用f.<fieldname>.facet.contains在每个field上指定需要包含的内容。

facet.contains.ignoreCase

如果使用facet.containsfacet.contains.ignoreCase参数如果设置为true,则忽略大小写。
这个参数可以使用f.<fieldname>.facet.contains.ignoreCase在每个field上指定是否忽略大小写。

facet.sort

该参数指定层面结果排序方式。

设置结果
count根据计数倒序排列
index根据索引中的排序返回(由索引term组成的词典)。在ASCII码的范围,这将按字母顺序排序。

如果facet.limit大于0,默认使用count,否则,默认使用index
这个参数可以使用f.<fieldname>.facet.sort在每个field上指定排序方式。

facet.limit

该参数指定最大返回数目(基本上是返回的field的数量)。负值表示对返回数量无限制。
默认值是100。
这个参数可以使用f.<fieldname>.facet.limit在每个field上指定返回数量。

facet.offset

该参数表示返回列表的偏移量,可以用来分页。
默认值是0。
这个参数可以使用f.<fieldname>.facet.offset在每个field上指定偏移量。

facet.mincount

该参数指定返回结果中进行层面分析的field的最小数量。如果一个field的计数低于指定值,则该field的面将不返回。
默认值是0。
tris parameter can be specified on a per-field basis witr tre syntax of f..facet.mincount.
这个参数可以使用f.<fieldname>.facet.mincount在每个field上指定最小数量。

facet.missing

如果设置为true,这个参数表明,除了以term为基础的层面分析,返回结果中需要计算匹配查询但是没有字面值的数据(就是field值为 null 的记录)。
默认值false。
这个参数可以使用f.<fieldname>.facet.missing在每个field上指定是否起作用。

facet.metrod

该参数选择对某个field进行层面分析时使用的算法或方法。

结果
enum枚举field中的所有term,计算匹配查询文档中的term的交集。适用于field值比较少的情况。每个文档的平均值不重要。比如,多美国的各个州进行层面分析,比如阿拉巴马州州、阿拉斯加州、…、怀俄明州等,将创建50个缓存过滤器,可以被重复使用。filterCache应该足够大以容纳所有的缓存过滤器。
fc遍历所有匹配文档,在每个文档中统计出现的term。适用于field取值比较多,但在每个文档里出现次数比较少的情况。查询每个文档中包含的term/value对,对每个value递增。这个方法对于field的索引值比较高,但是每个文档中比较低的情况表现优秀。对于多值field,使用混合的方式从filterCache中查询匹配文档过滤term。
fcs该参数针对单值的字符串field。使用`facet.metrod=fcs`并控制采用线程局部参数的线程数。该参数在索引指数变化迅速中进行层面分析。

默认值是fc(除了类型是BoolFiled的field),因为它使用最少的内存,并且在有很多不同term时更快。
这个参数可以使用f.<fieldname>.facet.metrod在每个field上指定使用的方法或算法。

facet.enum.cache.minDf

该参数指定达到最小的文档频率(匹配项的文件数量)时,filtercache应该存储term。(minDf表示minimum document frequency,也就是文档内出现某个关键字的最少次数。)只有设置facet.metrod=enum时起作用。
设置该参数可以减少filterCache的内存消耗,但会增加总的查询时间(计算交集的时间增加了)。如果你对某个field存在大量term,又希望减少内存使用,可以设置25-50内的值,然后运行一些测试。然后,优化参数设置。
默认值为0,filterCache可以对所有term使用。
这个参数可以使用f.<fieldname>.facet.enum.cache.minDF对每个field分别指定。

Over-Request参数

在某些情况下,在分布式的Solr查询中为了正确返回”top”约束可以通过使用”Over Requesting”来从每个单独的分片提高所需数量限制(比如:facet.limit)。在这些情况下,每个分片默认访问最上面的”10 + (1.5 * facet.limit)”的数据。
在某些情况下,基于你的文档分布在哪些分片上,你使用的facet.limit值是多少,增加或减少over-requesting的数量是有利的。可以通过设置facet.overrequest.count(默认10)和facet.overrequest.ratio(默认1.5)的值来实现。

facet.trreads

这个参数指定并行加载层面分析的field的线程数量。指定facet.trreads=N,N是使用的最大线程数。不使用该参数或设置为0,将不产生任何额外的线程,只使用主线程来请求。使用负值将创建最多Integer.MAX_VALUE个线程。

范围分析

可以对datefield或数字field进行范围分析。这对于分为查询,比如查询价格,非常有用。在Solr3.1中,范围分析由于日期分析(描述如下)。

参数描述
facet.range指定进行范围分析的field
facet.range.start指定范围的结束值
facet.range.end指定范围的开始值
facet.range.gap指定步进大小
facet.range.hardend布尔参数,指定处理Solr开始和结尾不均匀分布的情况。ture,最后一个范围只到end值。false,最后一个范围可能超过end值。
facet.range.include指定包含或不包含范围的开始值和结束值。更多信息参加`facet.range.include`一节。
facet.range.otrer指定除了计算每个小范围的计数,也计算整个范围的计数。
facet.range.metrod指定用于层面分析的算法或方法。

facet.range

定义Solr进行范围反洗的字段。比如:

facet.range=price&facet.range=age
facet.range=lastModified_dt

facet.range.start

指定范围的结束值。可以使用f.<fieldname>.facet.range.start对每个field分别指定。比如:

f.price.facet.range.start=0.0&f.age.facet.range.start=10
f.lastModified_dt.facet.range.start=NOW/DAY-30DAYS

facet.range.end

指定范围的开始值。可以使用f.<fieldname>.facet.range.end对每个field分别指定。比如:

f.price.facet.range.end=1000.0&f.age.facet.range.start=99
f.lastModified_dt.facet.range.end=NOW/DAY+30DAYS

facet.range.gap

指定范围步进大小。对于date类型的field,需要使用DateMatrParser表达式(比如,facet.range.gap=%2B1DAY ... '+1DAY')。可以使用f.<fieldname>.facet.range.gap对每个field分别指定。比如:

f.price.facet.range.gap=100&f.age.facet.range.gap=10
f.lastModified_dt.facet.range.gap=+1DAY

facet.range.hardend

布尔参数,指定处理Solr开始和结尾不均匀分布的情况。ture,最后一个范围只到end值。false,最后一个范围可能超过end值。
默认值是false。
可以使用f.<fieldname>.facet.range.hardend对每个field分别指定。

facet.range.include

默认情况下,范围计算过程中,包括facet.range.start定义的结束值,不包括facet.range.end定义的结束值。facet.range.otrer定义的”before”不包含,定义的”after”包含。默认值是”lower”,在边间不重复计数。可以使用facet.range.include参数修改行为:

选项描述
lower包含结束值
upper包含开始值
edge包含上结束值(结束值包含在第一个范围,结束值包含在第二个范围)
outer即使上结束值已经被包含,"before"和"after"也会重复计算
all包含所有:lower, upper, edge, outer

可以使用f.<fieldname>.facet.range.include对每个field分别指定。

要避免重复计数

facet.range.otrer

计算除了上结束值之间步进统计的数据之外,以其他方式统计数据,可以有一下选项:

选项描述
before统计所有低于第一个范围结束值的数据
after统计所有高于最后一个范围开始值的数据
between统计所有范围组中上结束值的数据
none不进行额外统计
all统计before、between、after的数据

可以使用f.<fieldname>.facet.range.otrer对每个field分别指定。注意all选项,它可能重复计算已经定义其他选项,但不会覆盖其他选项。

facet.range.metrod

这个参数是用来指定Solr进行范围分析时使用的算法或方法。不同方法返回结果相同,但是性能可能不同。

方法描述
filter该方法从主查询结果集中获取数量,执行过滤器,生成范围统计。使用了filterCache,所以当有足够大的缓存来存储所有范围时比较有利。
dv该方法将遍历所有匹配的文档,查询所有范围的值。该方法使用docValues或fieldCache。不支持DateRangeField类型的field,或使用[group.facets](https://cwiki.apache.org/confluence/display/solr/Result+Grouping)不起作用。

默认值是filter。

范围分析中的facet.mincount

范围分析中通用可以使用参数facet.mincount。返回结果中不包含小于定义值的数据。

日期分析

日期分析facet.date的参数已经在Solr3.1中标记为过时。鼓励用户使用范围分析,范围分析可以实现相同功能,并且对于也可以对数字进行范围分析。返回格式有些许差别,但是参数格式几乎一样。

Pivot(决策树)分析

Pivoting是一个可以进行自动排序、计数、求和、求平均值的统计工具,结果保存在一个表中。结果数据通常在第二个表中展示。Pivot分析可以对多个field进行结果分析,保存在汇总表中。
从另一个方面看,查询结果生成一个决策树,比如:“对于A进行层面分析,约束计数是X/N, Y/M, 等。如果你使用X替换A,对于B的约束计数将变成S/P, T/Q,等”。换句话说,它提前告诉你下一个集合是什么。

facet.pivot

该参数指定使用pivot的field。多个facet.pivot将在返回结果中创建多个”facet_pivot”节点。每个field的结果列表通过逗号分隔。

facet.pivot.mincount

该参数指定层面分析结果中包含结果的最小数量。默认1。
以”bin/solr -e techproducts”为例,查询url如下:

http://localhost:8983/solr/techproducts/select?q=*:*&facet.pivot=cat,popularity,inStock
   &facet.pivot=popularity,cat&facet=true&facet.field=cat&facet.limit=5
   &rows=0&wt=json&indent=true&facet.pivot.mincount=2

结果中的”facet_pivot”是:

  "facet_counts":{
    "facet_queries":{},
    "facet_fields":{
      "cat":[
        "electronics",14,
        "currency",4,
        "memory",3,
        "connector",2,
        "graphics card",2]},
    "facet_dates":{},
    "facet_ranges":{},
    "facet_pivot":{
      "cat,popularity,inStock":[{
          "field":"cat",
          "value":"electronics",
          "count":14,
          "pivot":[{
              "field":"popularity",
              "value":6,
              "count":5,
              "pivot":[{
                  "field":"inStock",
                  "value":true,
                  "count":5}]},
...

联合使用pivot与stats

除了一些支持通用局部参数的层面分析,还可以使用统计局部参数来使facet.pivot与stats.field配合使用,达到你想要的统计计算。

在下面的例子中,是对每个facet.pivot结果层次的两个不同的统计结果集:

stats=true
stats.field={!tag=piv1,piv2 min=true max=true}price
stats.field={!tag=piv2 mean=true}popularity
facet=true
facet.pivot={!stats=piv1}cat,inStock
facet.pivot={!stats=piv2}manu,inStock

结果是:

"facet_pivot":{
  "cat,inStock":[{
      "field":"cat",
      "value":"electronics",
      "count":12,
      "pivot":[{
          "field":"inStock",
          "value":true,
          "count":8,
          "stats":{
            "stats_fields":{
              "price":{
                "min":74.98999786376953,
                "max":399.0}}}},
        {
          "field":"inStock",
          "value":false,
          "count":4,
          "stats":{
            "stats_fields":{
              "price":{
                "min":11.5,
                "max":649.989990234375}}}}],
      "stats":{
        "stats_fields":{
          "price":{
            "min":11.5,
            "max":649.989990234375}}}},
    {
      "field":"cat",
      "value":"currency",
      "count":4,
      "pivot":[{
          "field":"inStock",
          "value":true,
          "count":4,
          "stats":{
            "stats_fields":{
              "price":{
                ...
  "manu,inStock":[{
      "field":"manu",
      "value":"inc",
      "count":8,
      "pivot":[{
          "field":"inStock",
          "value":true,
          "count":7,
          "stats":{
            "stats_fields":{
              "price":{
                "min":74.98999786376953,
                "max":2199.0},
              "popularity":{
                "mean":5.857142857142857}}}},
        {
          "field":"inStock",
          "value":false,
          "count":1,
          "stats":{
            "stats_fields":{
              "price":{
                "min":479.95001220703125,
                "max":479.95001220703125},
              "popularity":{
                "mean":7.0}}}}],
      ...

联合使用层面分析、范围分析和pivot分析

使用本地查询参数与facet.pivot结合实现层面分析实例。同样,使用范围参数与facet.pivot实现范围分析。
在下面的例子中,是结合查询参数与facet.pivot的结果:

facet=true
facet.query={!tag=q1}manufacturedate_dt:[2006-01-01T00:00:00Z TO NOW]
facet.query={!tag=q1}price:[0 TO 100]
facet.pivot={!query=q1}cat,inStock

结果:

"facet_counts": {
   "facet_queries": {
     "{!tag=q1}manufacturedate_dt:[2006-01-01T00:00:00Z TO NOW]": 9,
     "{!tag=q1}price:[0 TO 100]": 7
   },
   "facet_fields": {},
   "facet_dates": {},
   "facet_ranges": {},
   "facet_intervals": {},
   "facet_heatmaps": {},
   "facet_pivot": {
     "cat,inStock": [
       {
         "field": "cat",
         "value": "electronics",
         "count": 12,
         "queries": {
           "{!tag=q1}manufacturedate_dt:[2006-01-01T00:00:00Z TO NOW]": 9,
           "{!tag=q1}price:[0 TO 100]": 4
         },
         "pivot": [
           {
             "field": "inStock",
             "value": true,
             "count": 8,
             "queries": {
               "{!tag=q1}manufacturedate_dt:[2006-01-01T00:00:00Z TO NOW]": 6,
               "{!tag=q1}price:[0 TO 100]": 2
             }
           },
           ...

在下面的例子中,是结合范围参数与facet.pivot的结果:

facet=true
facet.range={!tag=r1}manufacturedate_dt
facet.range.start=2006-01-01T00:00:00Z
facet.range.end=NOW/YEAR
facet.range.gap=+1YEAR
facet.pivot={!range=r1}cat,inStock

结果:

"facet_counts":{
  "facet_queries":{},
  "facet_fields":{},
  "facet_dates":{},
  "facet_ranges":{
    "manufacturedate_dt":{
      "counts":[
        "2006-01-01T00:00:00Z",9,
        "2007-01-01T00:00:00Z",0,
        "2008-01-01T00:00:00Z",0,
        "2009-01-01T00:00:00Z",0,
        "2010-01-01T00:00:00Z",0,
        "2011-01-01T00:00:00Z",0,
        "2012-01-01T00:00:00Z",0,
        "2013-01-01T00:00:00Z",0,
        "2014-01-01T00:00:00Z",0],
      "gap":"+1YEAR",
      "start":"2006-01-01T00:00:00Z",
      "end":"2015-01-01T00:00:00Z"}},
  "facet_intervals":{},
  "facet_heatmaps":{},
  "facet_pivot":{
    "cat,inStock":[{
        "field":"cat",
        "value":"electronics",
        "count":12,
        "ranges":{
          "manufacturedate_dt":{
            "counts":[
              "2006-01-01T00:00:00Z",9,
              "2007-01-01T00:00:00Z",0,
              "2008-01-01T00:00:00Z",0,
              "2009-01-01T00:00:00Z",0,
              "2010-01-01T00:00:00Z",0,
              "2011-01-01T00:00:00Z",0,
              "2012-01-01T00:00:00Z",0,
              "2013-01-01T00:00:00Z",0,
              "2014-01-01T00:00:00Z",0],
            "gap":"+1YEAR",
            "start":"2006-01-01T00:00:00Z",
            "end":"2015-01-01T00:00:00Z"}},
        "pivot":[{
            "field":"inStock",
            "value":true,
            "count":8,
            "ranges":{
              "manufacturedate_dt":{
                "counts":[
                  "2006-01-01T00:00:00Z",6,
                  "2007-01-01T00:00:00Z",0,
                  "2008-01-01T00:00:00Z",0,
                  "2009-01-01T00:00:00Z",0,
                  "2010-01-01T00:00:00Z",0,
                  "2011-01-01T00:00:00Z",0,
                  "2012-01-01T00:00:00Z",0,
                  "2013-01-01T00:00:00Z",0,
                  "2014-01-01T00:00:00Z",0],
                "gap":"+1YEAR",
                "start":"2006-01-01T00:00:00Z",
                "end":"2015-01-01T00:00:00Z"}}},
                ...

附加pivot参数

虽然facet.pivot.mincount与facet.mincount参数有些出入,但是许多其他的参数依然可以在pivot分析中使用:

  • facet.limit
  • facet.offset
  • facet.sort
  • facet.overrequest.count
  • facet.overrequest.ratio

区间分析

另外一种支持的层面分析形式是区间分析。听起来与范围分析很类似,也确实在功能性方面与范围分析很像。区间分析允许你设置区间范围和统计在这个范围内计数。
虽然与范围分析有类似的功能,但是两个方法实现却不同,而且根据上下文的不同性能也不一样。如果你比较关心性能问题,你需要两个都进行测试。区间分析在相同field上查询不同区间表现良好,如果是有效字段将使用docValues,否则使用fieldCache。

名称做什么
facet.interval指定区间分析的field
facet.interval.set指定区间值

facet.interval

该参数表明需要用区间分析的field。可以多次定义多个field。

facet.interval=price&facet.interval=size

facet.interval.set

该参数用来指明field的区间,可以定义多个。该参数是全局参数,意味着所有facet.interval定义的field都会使用该区间,除非使用特定field进行覆盖。使用f.<fieldname>.facet.interval.set设置对指定field有效的区间,比如:

f.price.facet.interval.set=[0,10]&f.price.facet.interval.set=(10,100]

区间语法

区间必须以’(‘或’[‘开头,跟随一个结束值,然后是逗号(‘,’),然后是开始值,然后以’)’或’]’结尾。
比如:

  • (1,10) -> 大于1,小于10
  • [1,10) -> 大于等于1,小于10
  • [1,10] -> 大于等于1,小于等于10

开始值和结束值值不能为空。如果设置无限的方位,使用特殊字符’‘作为上下或结束值。当使用’‘时,’(‘或’[‘,以及 ‘)’或’]’是没有区别的。[,]将包含所有值。区间范围可以使用字符串,单不需要价引号。所有的文本一直到逗号被视为开始值,然后其后的文本视为结束值。比如:[Buenos Aires,New York]。注意,类字符串的比较将使用字符串区间(区分大小写)。比较器不能改变。
逗号、括号和方括号可以使用转义字符’'。在开始或结尾的空格将被忽略。开始值不能大于结束值。允许两个值相同,这样可以获取你想获取某个特定值的数量,比如[A,A]、[B,B]和[C,Z]。
区间分析支持别名。可以对facet.interval和facet.interval.set设置别名。比如:

&facet.interval={!key=popularity}some_field 
&facet.interval.set={!key=bad}[0,5] 
&facet.interval.set={!key=good}[5,*]
&facet=true

层面分析局部变量

支持使用局部变量的语法覆盖全局变量。它还可以提供一种将元数据添加到其他参数值的方法,很类似于XML属性。

对过滤器打标签或排除

在层面分析时,可以对指定过滤器打标签,也可以排除某些过滤器。这在进行多选层面分析时很有用。
考虑下面这种情况:

q=mainquery&fq=status:public&fq=doctype:pdf&facet=true&facet.field=doctype

因为使用了过滤器doctype:pdf,所以facet.field=doctype命令是多余的,并且除了doctype:pdf其他的都将返回0.
为了对doctype进行多值查询,GUI可能希望展示其他的docttype的值,就像是没有doctype:pdf约束一样。比如:

=== Document Type ===
  [ ] Word (42)
  [x] PDF  (96)
  [ ] Excel(11)
  [ ] HTML (63)

为了返回doctype的值,可以通过对过滤器打标签,然后在对doctype层面分析时不包括这个过滤器即可。

q=mainquery&fq=status:public&fq={!tag=dt}doctype:pdf&facet=true&facet.field={!ex=dt}doctype

所有类型的层面分析都支持排除过滤器。tag或ex都可以使用定义多个值,不同值之间用逗号分隔。

修改输出值

使用key可以修改层面分析命令的输出值。比如:

facet.field={!ex=dt key=mylabel}doctype

在名为doctype的field上的层面分析结果返回key是mylabel。这在多次对相同field进行层面分析时比较有用。

扩展阅读

SimpleFacetParameters
Heatmap Faceting (Spatial)

原文链接:Faceting
翻译:沉潜飞动
译文链接:solr-搜索-层面分析