多属性多选项Elasticsearch筛选器优化:保留可选值
多属性商品筛选可选值优化问题
问题背景
已基于Elasticsearch实现多属性多选项的商品筛选功能,核心筛选逻辑正常,但返回前端的后续可选筛选值仅保留用户已选中的选项(比如选了2020年后,Year属性的可选值只剩2020,丢失其他符合当前筛选条件的年份),需要优化以返回所有符合当前筛选结果集的可选属性值。
数据示例
单商品属性结构
{"attributes": [{"id": "bafa62bf-cc24-4434-943e-6931bb31091f","value": "Black","attributeName": "Color","attributeId": "1c5aae51-a58b-44bc-9073-2c0e4e97bebe"},{"id": "71dd6aeb-03ab-4fae-9ebd-6bf41ab821fa","value": "Brand","attributeName": "Tesla","attributeId": "4c1c9939-6348-42e4-9c07-293587645300"},{"id": "f5fc9a7f-cc11-46fe-aa04-aa0f21ef4f14","value": "Sedan","attributeName": "Type","attributeId": "8cdf4684-3306-4924-92dd-36efc874d416"},{"id": "c666cb4a-4442-4979-aed3-4156a6d92fec","value": "KM","attributeName": "0","attributeId": "975461d4-65a6-47fa-8dc9-595d6def34c5"},{"id": "b3c66548-2a56-47f2-bdfd-2fc043d9d330","value": "2020","attributeName": "Year","attributeId": "f546d055-8c87-45cf-8700-b3d6ca114dbc"}]}
前端筛选请求Payload
{"attributesForFilter": [{"id": "4c1c9939-6348-42e4-9c07-293587645300","values": ["Tesla"]},{"id": "1c5aae51-a58b-44bc-9073-2c0e4e97bebe","values": ["Gray","Black"]},{"id": "f546d055-8c87-45cf-8700-b3d6ca114dbc","values": ["2020"]}]}
当前错误返回结果
{"attributes": [{"id": "1c5aae51-a58b-44bc-9073-2c0e4e97bebe","name": "Color","source": {"options": [{"id": "6e73fac6-95b3-4665-97ef-8590f9702ddd","value": "Black"}]}},{"id": "71dd6aeb-03ab-4fae-9ebd-6bf41ab821fa","name": "Brand","source": {"options": [{"id": "6e73fac6-95b3-4665-97ef-8590f9702ddd","value": "Tesla"}]}},{"id": "b3c66548-2a56-47f2-bdfd-2fc043d9d330","name": "Year","source": {"options": [{"id": "6e73fac6-95b3-4665-97ef-8590f9702ddd","value": "2020"}]}}]}
当前Elasticsearch查询语句
{"post_filter": {"bool": {"filter": [{"term": {"status": "active"}},{"bool": {"must_not": {"exists": {"field": "officeId"}}}},{"bool": {"must": {"exists": {"field": "asset"}}}},{"term": {"categories.id": "6cd9fef8-5c19-4b36-8297-e0fa229df687"}},{"bool": {"must": [{"bool": {"should": [{"bool": {"should": [{"nested": {"path": "attributes","query": {"bool": {"must": [{"multi_match": {"analyzer": "custom_analyzer","fields": ["attributes.value^3"],"query": "Tesla","type": "phrase_prefix"}},{"term": {"attributes.attributeId": "71dd6aeb-03ab-4fae-9ebd-6bf41ab821fa"}}]}}}}]}}]}},{"bool": {"should": [{"bool": {"should": [{"nested": {"path": "attributes","query": {"bool": {"must": [{"multi_match": {"analyzer": "custom_analyzer","fields": ["attributes.value^3"],"query": "Black","type": "phrase_prefix"}},{"term": {"attributes.attributeId": "1c5aae51-a58b-44bc-9073-2c0e4e97bebe"}}]}}}}]}},{"bool": {"should": [{"nested": {"path": "attributes","query": {"bool": {"must": [{"multi_match": {"analyzer": "custom_analyzer","fields": ["attributes.value^3"],"query": "Gray","type": "phrase_prefix"}},{"term": {"attributes.attributeId": "1c5aae51-a58b-44bc-9073-2c0e4e97bebe"}}]}}}}]}}]}},{"bool": {"should": [{"bool": {"should": [{"nested": {"path": "attributes","query": {"bool": {"must": [{"multi_match": {"analyzer": "custom_analyzer","fields": ["attributes.value^3"],"query": "2020","type": "phrase_prefix"}},{"term": {"attributes.attributeId": "f546d055-8c87-45cf-8700-b3d6ca114dbc"}}]}}}}]}}]}}]}}},"aggs": {"attributes": {"nested": {"path": "attributes"},"aggs": {"attributeId": {"terms": {"field": "attributes.attributeId"},"aggs": {"top_attribute_hits": {"top_hits": {"_source": {"includes": ["attributes"]},"size": 10}}}}}}}
优化方案
1. 调整Elasticsearch聚合逻辑
当前聚合使用top_hits只能返回匹配到的属性条目,无法获取当前筛选结果集中所有可选的属性值。需要修改聚合结构,针对每个attributeId下的value做分桶聚合,同时保留属性名称等元数据:
{ "post_filter": {...}, // 保留原有的post_filter筛选逻辑不变 "aggs": { "attributes": { "nested": {"path": "attributes"}, "aggs": { "attributeId": { "terms": {"field": "attributes.attributeId"}, "aggs": { "attributeName": { "terms": {"field": "attributes.attributeName", "size": 1} }, "values": { "terms": {"field": "attributes.value", "size": 100}, "aggs": { "valueId": { "terms": {"field": "attributes.id", "size": 1} } } } } } } } } }
聚合说明:
attributeId分桶:按属性ID分组,确保同一属性的选项归为一类attributeName聚合:获取该属性的名称(同一attributeId对应唯一attributeName,size设为1即可)values分桶:获取当前筛选结果集中该属性下所有存在的可选值valueId聚合:获取每个属性值对应的唯一ID
2. Golang代码逻辑调整
在解析ES聚合结果时,需要遍历聚合桶,把每个属性的所有可选值收集起来,而不是只保留用户选中的条目:
// 定义ES聚合结果的结构体 type AggregationResult struct { Attributes struct { AttributeId struct { Buckets []struct { Key string `json:"key"` // attributeId AttributeName struct { Buckets []struct { Key string `json:"key"` // attributeName } `json:"buckets"` } `json:"attributeName"` Values struct { Buckets []struct { Key string `json:"key"` // 属性值 ValueId struct { Buckets []struct { Key string `json:"key"` // 属性值ID } `json:"buckets"` } `json:"valueId"` } `json:"buckets"` } `json:"values"` } `json:"buckets"` } `json:"attributeId"` } `json:"attributes"` } // 转换为前端需要的响应结构 type AttributeOption struct { Id string `json:"id"` Name string `json:"name"` Source struct { Options []Option `json:"options"` } `json:"source"` } type Option struct { Id string `json:"id"` Value string `json:"value"` } func transformAggToResponse(agg AggregationResult) []AttributeOption { var attributes []AttributeOption for _, attrBucket := range agg.Attributes.AttributeId.Buckets { attr := AttributeOption{ Id: attrBucket.Key, Name: attrBucket.AttributeName.Buckets[0].Key, } for _, valBucket := range attrBucket.Values.Buckets { option := Option{ Id: valBucket.ValueId.Buckets[0].Key, Value: valBucket.Key, } attr.Source.Options = append(attr.Source.Options, option) } attributes = append(attributes, attr) } return attributes }
关键注意点
- 确保
post_filter位置正确:聚合会基于post_filter过滤后的结果集计算,这样返回的可选值就是符合当前筛选条件的所有选项 - 调整聚合
size参数:根据业务需求设置terms聚合的size,避免遗漏可选值 - 字段映射检查:
attributes.attributeId、attributes.value、attributes.id等字段必须是keyword类型,否则分桶聚合会出现分词错误
内容的提问来源于stack exchange,提问作者wiliamvj
相关产品推荐
相关产品推荐

