elasticsearch 聚合(二)过滤、排序

当当当~,聚合数据的时候先缩小一下数据范围,然后再排序聚合,这样理论上来讲是会快很多。
我们还是上次的数据,数据准备

PUT /tvs
{
  "mappings": {
    "properties": {
      "price": {
        "type": "long"
      },
      "color": {
        "type": "keyword"
      },
      "brand": {
        "type": "keyword"
      },
      "sold_date": {
        "type": "date"
      }
    }
  }
}
#添加数据
POST /tvs/_bulk
{"index":{}}
{"price":1000,"color":"红色","brand":"长虹","sold_date":"2016-10-28"}
{"index":{}}
{"price":2000,"color":"红色","brand":"长虹","sold_date":"2016-11-05"}
{"index":{}}
{"price":3000,"color":"绿色","brand":"小米","sold_date":"2016-05-18"}
{"index":{}}
{"price":1500,"color":"蓝色","brand":"TCL","sold_date":"2016-07-02"}
{"index":{}}
{"price":1200,"color":"绿色","brand":"TCL","sold_date":"2016-08-19"}
{"index":{}}
{"price":2000,"color":"红色","brand":"长虹","sold_date":"2016-11-05"}
{"index":{}}
{"price":8000,"color":"红色","brand":"三星","sold_date":"2017-01-01"}
{"index":{}}
{"price":2500,"color":"蓝色","brand":"小米","sold_date":"2017-02-12"}
{"index":{}}
{"price":1999,"color":"红色","brand":"长虹","sold_date":"2021-10-28"}

1 先缩小数据范围再聚合分析,通过品牌搜索,统计销售额

#query
GET /tvs/_search
{
  "size": 0,
  "query": {
    "term": {
      "brand": {
        "value": "小米" #品牌为小米
      }
    }
  },
  "aggs": {
    "group_by_color": {
      "terms": {
        "field": "color" #以颜色分组
      }
    }
  }
}
#result
"buckets" : [
        {
          "key" : "绿色",
          "doc_count" : 1
        },
        {
          "key" : "蓝色",
          "doc_count" : 1
        }
      ]

2 单品牌与所有品牌的销量对比,场景:一个品牌在所有销售中的占比

 #query
  {
  "size": 0,
  "query": {
    "term": {
      "brand": {
        "value": "小米"
      }
    }
  },
  "aggs": {
    "single_brand_avg_price": {
      "avg": {
        "field": "price"
      }
    },
    "all": {
      "global": {},
      "aggs": {
        "all_brand_avg_price": {
          "avg": {
            "field": "price"
          }
        },
        "all_brand_total_price": {
          "sum": {
            "field": "price"
          }
        }
      }
    }
  }
}

先对小米品牌做个搜索,其中singe_brand_avg_price是针对query中小米销售平均价格
all.all_brand_avg_price 拿到所有品牌的平均价格
all.all_brand_total_price 拿到所有品牌的总价格

#result
"aggregations" : {
    "all" : {
      "doc_count" : 9,
      "all_brand_total_price" : {
        "value" : 23199.0
      },
      "all_brand_avg_price" : {
        "value" : 2577.6666666666665 #注意精度问题
      }
    },
    "single_brand_avg_price" : {
      "value" : 2750.0
    }
  }

3 按照价格大于某要求,然后计算平均价格,这个是filter

#query
{
  "size": 0,
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "price": {
            "gt": 1500
          }
        }
      }
    }
  },
  "aggs": {
    "avg_price": {
      "avg": {
        "field": "price"
      }
    }
  }
}

#result
"aggregations" : {
    "avg_price" : {
      "value" : 3249.8333333333335
    }
  }

4 按时间段、品牌统计销售额(过滤桶)

#query
{
  "size": 0,
  "query": {
    "term": {
      "brand": {
        "value": "长虹"
      }
    }
  },
  "aggs": {
    "recent_150d": {
      "filter": {
        "range": {
          "sold_date": {
            "gte": "now-1M"
          }
        }
      },
      "aggs":{
        "recent_150d_avg_price":{
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

#result
"aggregations" : {
    "recent_150d" : {
      "doc_count" : 1,
      "recent_150d_avg_price" : {
        "value" : 1999.0
      }
    }
  }

5 排序(哈哈哈哈哈,终于到排序了),按照颜色分组,价格排序

#query
{
  "size": 0,
  "aggs": {
    "group_by_color": {
      "terms": {
        "field": "color",
        "order": {
          "avg_price": "asc" #asc是升序 desc降序,key是下面aggs.avg_price
        }
      },
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}
#result
"aggregations" : {
    "group_by_color" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "蓝色",
          "doc_count" : 2,
          "avg_price" : {
            "value" : 2000.0
          }
        },
        {
          "key" : "绿色",
          "doc_count" : 2,
          "avg_price" : {
            "value" : 2100.0
          }
        },
        {
          "key" : "红色",
          "doc_count" : 5,
          "avg_price" : {
            "value" : 2999.8
          }
        }
      ]
    }
  }

先按颜色分组,再按品牌分组,再按价格倒序

#query
{
  "size": 0,
  "aggs": {
    "group_by_color": {
      "terms": {
        "field": "color"
      },
      "aggs": {
        "group_by_brand": {
          "terms": {
            "field": "brand",
            "order": {
              "avg_price": "desc"
            }
          },
          "aggs": {
            "avg_price": {
              "avg": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}
#result
{
          "key" : "红色",
          "doc_count" : 5,
          "group_by_brand" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "三星",
                "doc_count" : 1,
                "avg_price" : {
                  "value" : 8000.0
                }
              },
              {
                "key" : "长虹",
                "doc_count" : 4,
                "avg_price" : {
                  "value" : 1749.75
                }
              }
            ]
          }
        }

6 后过滤器

目前为止,我们可以同时对搜索结果和聚合结果进行过滤(不计算得分的 filter 查询),以及针对聚合结果的一部分进行过滤( filter 桶)。我们可能会想,"只过滤搜索结果,不过滤聚合结果呢?" 答案是使用 post_filter 。
它是接收一个过滤器的顶层搜索请求元素。这个过滤器在查询 之后 执行(这正是该过滤器的名字的由来:它在查询之后 post 执行)。正因为它在查询之后执行,它对查询范围没有任何影响,所以对聚合也不会有任何影响

#query
GET /tvs/_search
{
  "size": 0,
  "query": {
    "match": {
      "brand": "TCL"
    }
  },
  "post_filter": {
    "term": {
      "color": "蓝色"
    }
  },
  "aggs": {
    "all_colors": {
      "terms": {
        "field": "color"
      }
    }
  }
}

#result
"hits" : {
    "total" : {
      "value" : 1,#注意这里
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
"aggregations" : {
    "all_colors" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "绿色",
          "doc_count" : 1
        },
        {
          "key" : "蓝色",
          "doc_count" : 1
        }
      ]
    }
  }
}

查询 部分找到所有的 TCL 家电,然后用 terms 聚合创建一个颜色列表。因为聚合对查询范围进行操作,颜色列表与TCL有的颜色相对应。
最后, post_filter 会过滤搜索结果,只展示蓝色 TCL 家电。这在查询执行过 后 发生,所以聚合不受影响。
这通常对 UI 的连贯一致性很重要,可以想象用户在界面商选择了一类颜色(比如:绿色),期望的是搜索结果已经被过滤了,而 不是 过滤界面上的选项。如果我们应用 filter 查询,界面会马上变成 只 显示 绿色 作为选项,这不是用户想要的。靠我第一遍没看懂,md为什么限定了蓝色还会出现绿色。答案在于hits里面的total.value的值 当使用过post_filter(后置过滤器)的时候 执行顺序是 先query、再aggs 最后才是post_filter。这里蓝色的TCl家电只有一台,所以total.value 的值是1

6 统计去重的数量
网站独立访客是多少?
卖了多少种汽车?
每月有多少独立用户购买了商品?
例如:实现 SELECT COUNT(DISTINCT color) FROM product;
这种要用到cardinality(基数)

#query
{
    "size" : 0,
    "aggs" : {
        "distinct_colors" : {
            "cardinality" : {
              "field" : "color",
              "precision_threshold": 100 #精度控制 0--40000 溢出仍为40000
            }
        }
    }
}
#result
"aggregations" : {
    "distinct_colors" : {
      "value" : 3 # 有三种不同的产品在出售
    }
  }

cardinality 度量是一个近似算法。 它是基于 HyperLogLog++ (HLL)算法的。 HLL 会先对我们的输入作哈希运算,然后根据哈希运算的结果中的 bits 做概率估算从而得到基数。

算法的特性 :
可配置的精度,用来控制内存的使用(更精确 = 更多内存)。小的数据集精度是非常高的。
我们可以通过配置参数,来设置去重需要的固定内存使用量。无论数千还是数十亿的唯一值,内存使用量只与你配置的精确度相关。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注