Solr --- 聚合统计stats

x33g5p2x  于2021-12-20 转载在 其他  
字(5.9k)|赞(0)|评价(0)|浏览(286)

简介

stats查询用于对文档中的数字型、字符型和日期型字段进行简单的统计。
 stats
设置为true,启用stat统计功能
 stats.field
指定产生stat统计的字段,可以提供多个字段。
 stats.facet
在给定的facet字段中返回子结果
 支持的统计信息
min 最小值
max 最大值
sum 所有值之和
count 值的个数
missing 空值个数
mean 平均值

查询语法

输入:

q=apple&stats=true&stats.field=price&stats.field=popularity

输出统计信息:

<lst name="stats">
 <lst name="stats_fields">
   <lst name="price">
      <double name="min">0.0</double>
      <double name="max">2199.0</double>
      <long name="count">16</long>
      <long name="missing">16</long>
      <double name="sum">5251.270030975342</double>
      <double name="sumOfSquares">6038619.175900028</double>
     <double name="mean">328.20437693595886</double>
     <double name="stddev">536.3536996709846</double>
     <lst name="facets"/>
   </lst>
   <lst name="popularity">
     <double name="min">0.0</double>
     <double name="max">10.0</double>
     <long name="count">15</long>
     <long name="missing">17</long>
     <double name="sum">85.0</double>
     <double name="sumOfSquares">603.0</double>
     <double name="mean">5.666666666666667</double>
     <double name="stddev">2.943920288775949</double>
     <lst name="facets"/>
   </lst>
  </lst>
</lst>

源码分析

(1)stats组件
/** * Stats component calculates simple statistics on numeric field values * @since solr 1.4 */
public class StatsComponent extends SearchComponent {

  public static final String COMPONENT_NAME = "stats";

  @Override
  '''(a)准备阶段'''
  public void prepare(ResponseBuilder rb) throws IOException {
    '''根据参数stats=true设置功能开关'''
    if (rb.req.getParams().getBool(StatsParams.STATS,false)) {
      rb.setNeedDocSet( true );
      rb.doStats = true;
      rb._statsInfo = new StatsInfo(rb);
    }
  }

  @Override
  '''(b)处理阶段'''
  public void process(ResponseBuilder rb) throws IOException {
    '''如果不需要统计,则返回'''
    if (!rb.doStats) return;

    '''保存统计结果的map'''
    Map<String, StatsValues> statsValues = new LinkedHashMap<>();

    '''循环处理“stats.field=price&stats.field=popularity”中每一个field'''
    for (StatsField statsField : rb._statsInfo.getStatsFields()) {
      '''一般情况,返回主查询语句q=apple的结果文档集'''
      DocSet docs = statsField.computeBaseDocSet();

      '''getOutputKey()返回“price/popularity”作为key,computeLocalStatsValues()返回StatsValues的子类,比如NumericStatsValues/DateStatsValues/StringStatsValues/EnumStatsValues等 '''
      statsValues.put(statsField.getOutputKey(), statsField.computeLocalStatsValues(docs));
    }

    '''convertToResponse函数返回的结果即是2~25行的内容,计算统计值'''
    rb.rsp.add( "stats", convertToResponse(statsValues) );
  }
}  

public static NamedList<NamedList<NamedList<?>>> convertToResponse
    (Map<String,StatsValues> statsValues) {

    NamedList<NamedList<NamedList<?>>> stats = new SimpleOrderedMap<>();
    NamedList<NamedList<?>> stats_fields = new SimpleOrderedMap<>();
    stats.add("stats_fields", stats_fields);

    '''遍历每一个StatsValues,调用getStatsValues计算统计值'''
    for (Map.Entry<String,StatsValues> entry : statsValues.entrySet()) {
      String key = entry.getKey();
      NamedList stv = entry.getValue().getStatsValues();
      stats_fields.add(key, stv);
    }
    return stats;
  }
}

'''(2)AbstractStatsValues类负责各统计值的计算'''
abstract class AbstractStatsValues<T> implements StatsValues {
  '''返回key,value对,比如: min 0 max 2199.0 ...'''
  '''此处的min/max/count...等是所有StatsValues子类公共输出'''
  public NamedList<?> getStatsValues() {
    NamedList<Object> res = new SimpleOrderedMap<>();

    if (statsField.includeInResponse(Stat.min)) {
      res.add("min", min);
    }
    if (statsField.includeInResponse(Stat.max)) {
      res.add("max", max);
    }
    if (statsField.includeInResponse(Stat.count)) {
      res.add("count", count);
    }
    if (statsField.includeInResponse(Stat.missing)) {
      res.add("missing", missing);
    }
    if (statsField.includeInResponse(Stat.distinctValues)) {
      res.add("distinctValues", distinctValues);
    }
    if (statsField.includeInResponse(Stat.countDistinct)) {
      res.add("countDistinct", countDistinct);
    }
    if (statsField.includeInResponse(Stat.cardinality)) {
      if (statsField.getIsShard()) {
        res.add("cardinality", hll.toBytes());
      } else {
        res.add("cardinality", hll.cardinality());
      }
    }

    '''此函数由各子类覆盖,定义各自特殊的输出值'''
    addTypeSpecificStats(res);

    '''facet输出'''
    if (!facets.isEmpty()) {

      // add the facet stats
      NamedList<NamedList<?>> nl = new SimpleOrderedMap<>();
      for (Map.Entry<String,Map<String,StatsValues>> entry : facets.entrySet()) {
        NamedList<NamedList<?>> nl2 = new SimpleOrderedMap<>();
        nl.add(entry.getKey(), nl2);
        for (Map.Entry<String,StatsValues> e2 : entry.getValue().entrySet()) {
          nl2.add(e2.getKey(), e2.getValue().getStatsValues());
        }
      }

      res.add(FACETS, nl);
    }

    return res;
  }
}

'''(3)StatsValues子类数字型NumericStatsValues类'''
class NumericStatsValues extends AbstractStatsValues<Number> {
  '''输出数字型特殊的特有的统计值sum, sumOfSquares, mean, stddev, and percentiles'''
  @Override
  protected void addTypeSpecificStats(NamedList<Object> res) {
    if (statsField.includeInResponse(Stat.sum)) {
      res.add("sum", sum);
    }
    if (statsField.includeInResponse(Stat.sumOfSquares)) {
      res.add("sumOfSquares", sumOfSquares);
    }
    if (statsField.includeInResponse(Stat.mean)) {
      res.add("mean", sum / count);
    }
    if (statsField.includeInResponse(Stat.stddev)) {
      res.add("stddev", getStandardDeviation());
    }
    if (statsField.includeInResponse(Stat.percentiles)) {
      if (statsField.getIsShard()) {
        ByteBuffer buf = ByteBuffer.allocate(tdigest.byteSize()); // upper bound
        tdigest.asSmallBytes(buf);
        res.add("percentiles", Arrays.copyOf(buf.array(), buf.position()) );
      } else {
        NamedList<Object> percentileNameList = new NamedList<Object>();
        for (Double percentile : statsField.getPercentilesList()) {
          // Empty document set case
          if (tdigest.size() == 0) {
            percentileNameList.add(percentile.toString(), null);
          } else {
            Double cutoff = tdigest.quantile(percentile / 100);
            percentileNameList.add(percentile.toString(), cutoff);
          }
        }
        res.add("percentiles", percentileNameList);
      }
    }
  }

}

相关文章

微信公众号

最新文章

更多