在 Elasticsearch 5.x 有一个字段折叠(Field Collapsing)的功能非常有用,在这里分享一下

前言

在电商商品中有分SKU和SPU

sku是和商品的库存相关的一个概念。比如air max90、蓝色、40码的nike跑鞋,就是一个sku。你可以设置这个sku的价格、库存。

spu是标类商品的一个概念。所谓标类简单理解就是有型号的商品,比如

  • air max90、蓝色、40码的NIKE跑鞋
  • air max90、蓝色、41码的NIKE跑鞋
  • air max90、白色、40码的NIKE跑鞋
  • air max90、白色、41码的NIKE跑鞋

卖家发布商品时候,只要选到spu,和这个商品有关的属性会自动附加到商品上。

So,什么是字段折叠,可以理解就是按特定字段进行合并去重,比如我们有一个商品搜索,我希望按商品的“SPU”字段进行折叠,即返回结果每个SPU的商品都只返回一个结果,也就是按SPU去重,我搜索关键字“NIKE跑鞋”,要去返回的结果里面各种NIKE跑鞋都有,有air max90 NIKE跑鞋,有Nike Free RN 5.0 NIKE跑鞋,有Nike Flex Contact 3 NIKE跑鞋,有Nike Zoom Winflo 6 NIKE跑鞋,别全是air max90 NIKE跑鞋,不需要展示把所有的air max90 NIKE跑鞋都展示出来,只需要展示一个就可以了,这个时候折叠的功能就很有用了。,通过按特定字段折叠之后,来丰富搜索结果的多样性,而不是看到的页面上大部分都是重复的商品。

使用

//查询数据
var response = _esClient.Search<Product>(selector => selector
    .Index(Indices.Index<Product>())
    .Source(fields)
    .Query(query)
    .Collapse(x => x.Field(f => f.CorrelationId)) //折叠的字段
    .Aggregations(ag => ag.Terms("CorrelationId_group", t => t.Field(fd => fd.CorrelationId).Size(100000)))
    .From(Offset)
    .Size(PageSize)
    .Sort(sort)
);
//数据解析
var model = new SearchProductModel()
{
    Page = StartIndex,
    Size = PageSize,
    Total = (int)response.Total,
    TookTime = (int)response.Took,
    Items = BuildProduct(response.Hits)
};
//折叠关联产品后 查询出来的Total数量不对,这里使用分组重新统计折叠关联产品后的产品数量
var total = response.Aggregations.Terms("CorrelationId_group")?.Buckets.Count;
if (total.HasValue)
{
    model.Total = total.Value;
}
return model;

问题

折叠只会影响搜索结果Hits的数量,但不影响聚合Total 总数,搜索结果的 Total 依旧是所有的命中纪录数,去重的结果数无法取到。

换一种思路,根据折叠的字段再分组一下,总数Total用aggregations中的value,结果列表用hits,这样就可以准确提交折叠后的数量了。(这里的分组的size一定要设置,大小为搜索结果总数的最大值,当然你可以直接设置为Int.MaxValue)