一、背景
1、已知一个项目类型的树形结构,数据格式为:项目类型(理疗) + 属性(健康调理) + 属性值(推拿) 三个节点构成。如下图。
2、一颗项目类型树中,可以有多个项目类型,项目类型不会重复,不同的项目类型,属性和属性值 会重复。
3、ElasticSearch 索引商品库内容(100万数据)每个商品的文档中都维护一个项目类型的树形结构。
4、如下图的红色字体统计结果。
理疗:一共100个商品
理疗 + 健康调理:一共50个商品
理疗 + 健康调理 + 推拿:一共10个商品
二、问题
现使用 ElasticSearch 版本为 7.7.1
1、ES 商品索引中,如何存储这种数据结构?
使用 nested 嵌套类型?
使用 join 特殊类型?
2、ES 如何把所有的商品项目类型树结构整合为一个树?
实现功能有点类似于 solr 的 facet 分面查询。
ES 使用分组
如下测试代码中,插入的3条数据,聚合的树结构,应为
亮白美甲 - 款式 - 红色 - 白色 极光美甲 - 颜色 - 红色 - 兰色 - 蓝色 - 产地 - 北京 - 杭州 科技美甲 - 款式 - 红色 - 白色 - 黑色 - 产地 - 北京 - 南京
那么,该如何使用es查询呢?
3、ES 如何统计红色字体的商品数量?
1、用一条查询实现出上图的红色字体统计结果。
答:如下测试代码中的统计
2、如何只统计出项目类型树中,商品数量 >10 的显示?
答:使用 ES 的 min_doc_count 属性控制。
三、测试
1、创建索引
# 创建索引 PUT /my_demo { "mappings": { "properties": { "id": { "type": "integer" }, "productName": { "type": "keyword" }, "price": { "type": "double" }, "categoryCode": { "type": "keyword" }, "projectType": { // 项目类型 "type": "nested", "properties": { "id": { "type": "integer" }, "name": { "type": "keyword" }, "attr": { // 属性 "type": "nested", "properties": { "name":{ "type": "keyword" }, "value": { "type": "keyword" } // 属性值 } } } } } } }
2、禁用动态字段
# 禁用动态字段 PUT /my_demo/_mapping { "dynamic":"strict" }
3、插入数据
# 一个项目,一个属性,多个属性值 PUT /my_demo/_doc/1 { "id": 1, "productName": "美甲商品1", "categoryCode": "tag_mei_jia", "projectType":[{ "id": 101, "name": "极光美甲", "attr":[{ "name": "颜色", "value":["红色", "兰色"] }] }] } # 一个项目,一个属性,多个属性值 PUT /my_demo/_doc/2 { "id": 2, "productName": "美甲商品2", "categoryCode": "tag_mei_jia", "projectType":[{ "id": 102, "name": "亮白美甲", "attr":[{ "name": "款式", "value":["红色", "白色"] }] }] } # 多个项目,多个属性,多个属性值 PUT /my_demo/_doc/3 { "id": 3, "productName": "美甲商品3", "categoryCode": "tag_mei_jia", "projectType":[{ "id": 101, "name": "极光美甲", "attr": [{ "name": "颜色", "value": ["红色", "蓝色"] },{ "name": "产地", "value": ["北京", "杭州"] }] },{ "id": 103, "name": "科技美甲", "attr": [{ "name": "款式", "value": ["红色", "白色", "黑色"] },{ "name": "产地", "value": ["北京", "南京"] }] }] }
4、查询测试
查询字段数据
# 存在 项目类型 的数据 GET stg_item/_search { "query": { "nested": { "path": "projectType", "query": { "bool": { "must": { "exists": { "field": "projectType" } } } } } } }
查询条件数据
# 项目类型测试(2条) GET /my_demo/_search { "query": { "nested": { "path": "projectType", "query": { "bool": { "must": [ { "match": { "projectType.name": "极光美甲" }} ] } } } } } # 项目类型 + 属性 测试(2条) GET /my_demo/_search { "query": { "nested": { "path": "projectType", "query": { "bool": { "must": [ { "match": { "projectType.name": "极光美甲" }}, { "nested": { "path": "projectType.attr", "query": { "bool": { "must": [ { "match": {"projectType.attr.name": "颜色"}} ] } } } } ] } } } } } # 项目类型 + 属性 + 属性值测试(1条) GET /my_demo/_search { "query": { "nested": { "path": "projectType", "query": { "bool": { "must": [ { "match": { "projectType.name": "极光美甲" }}, { "nested": { "path": "projectType.attr", "query": { "bool": { "must": [ { "match": {"projectType.attr.name": "颜色"}}, { "match": {"projectType.attr.value": "兰色"}} ] } } } } ] } } } } } # 项目类型 + 属性值 测试(1条) GET /my_demo/_search { "query": { "nested": { "path": "projectType", "query": { "bool": { "must": [ { "match": { "projectType.name": "极光美甲" }}, { "nested": { "path": "projectType.attr", "query": { "bool": { "must": [ { "match": {"projectType.attr.value": "蓝色"}} ] } } } } ] } } } } } # 属性 测试(2条) GET /my_demo/_search { "query": { "nested": { "path": "projectType", "query": { "bool": { "must": [ { "nested": { "path": "projectType.attr", "query": { "bool": { "must": [ { "match": {"projectType.attr.name": "款式"}} ] } } } } ] } } } } } // 等同于 GET /my_demo/_search { "query": { "nested": { "path": "projectType.attr", "query": { "bool": { "must": [{ "match": { "projectType.attr.name": "款式" } }] } } } } } # 属性 + 属性值 测试(1条) GET /my_demo/_search { "query": { "nested": { "path": "projectType.attr", "query": { "bool": { "must": [ { "match": { "projectType.attr.name": "款式" }}, { "match": { "projectType.attr.value": "黑色" }} ] } } } } } # 查询属性含有(款式 或 产地)的数据(2条) GET /my_demo/_search { "query": { "nested": { "path": "projectType.attr", "query": { "bool": { "must": [ { "terms": { "projectType.attr.name": ["款式", "产地"] }} ] } } } } } # 遗留问题:查询属性必须含有(款式 和 产地)的数据
5、聚合查询测试
# 按照 类目 分组 GET /my_demo/_search { "size": 0, "aggs": { "group_by_projectType": { "terms": { "field": "categoryCode" } } } } # 按照 类目 分组(文档数量不少于10条) GET /my_demo/_search { "size": 0, "aggs": { "group_by_projectType": { "terms": { "field": "categoryCode", "min_doc_count": 10 } } } } # 按照 项目类型名称 分组 GET /my_demo/_search { "size": 0, "aggs": { "group_by_projectType": { "nested": { "path": "projectType" }, "aggs": { "group_by_projectType_name": { "terms": { "field": "projectType.name" } } } } } } # 按照 项目类型下的属性名称 分组 GET /my_demo/_search { "size": 0, "aggs": { "group_by_projectType": { "nested": { "path": "projectType" }, "aggs": { "group_by_projectType_name": { "nested": { "path": "projectType.attr" }, "aggs": { "group_by_projectType_attr_name": { "terms": { "field": "projectType.attr.name" } } } } } } } }
以上是统计了所有的数据,产生的数据结果如下图
接下来是统计:分组后的分组数据
# 统计 各个项目类型下,各个属性的 分组数据 GET /my_demo/_search { "size": 0, "aggs": { "group_by_projectType": { "nested": { "path": "projectType" }, "aggs": { "group_by_projectType_name": { "terms": { "field": "projectType.name" }, "aggs": { "group_by_projectType_attr": { "nested": { "path": "projectType.attr" }, "aggs": { "group_by_projectType_attr_name": { "terms": { "field": "projectType.attr.name" } } } } } } } } } } # 统计 各个项目类型下,各个属性,各个属性值的 分组数据 GET /my_demo/_search { "size": 0, "aggs": { "group_by_projectType": { "nested": { "path": "projectType" }, "aggs": { "group_by_projectType_name": { "terms": { "field": "projectType.name" }, "aggs": { "group_by_projectType_attr": { "nested": { "path": "projectType.attr" }, "aggs": { "group_by_projectType_attr_name": { "terms": { "field": "projectType.attr.name" }, "aggs": { "group_by_projectType_attr_value": { "terms": { "field": "projectType.attr.value" } } } } } } } } } } } }
执行结果如下图
Java API 代码实现
@Test public void nestedAggDemo2() throws Exception { int minDocCount = 10; // 1、聚合构建 SearchSourceBuilder builder = new SearchSourceBuilder(); builder.size(0); NestedAggregationBuilder aggregationBuilder = AggregationBuilders.nested("group_project_type", "projects") .subAggregation(AggregationBuilders.terms("group_project_type_name").minDocCount(minDocCount).field("projects.typeName") .subAggregation(AggregationBuilders.nested("group_attr", "projects.attrs") .subAggregation(AggregationBuilders.terms("group_attr_name").minDocCount(minDocCount).field("projects.attrs.name") .subAggregation(AggregationBuilders.terms("group_attr_value").minDocCount(minDocCount).field("projects.attrs.values")))) ); builder.aggregation(aggregationBuilder); // 2、查询对象 SearchRequest request = new SearchRequest(index_name); request.source(builder); // 3、查询 SearchResponse response = client.search(request, RequestOptions.DEFAULT); ParsedNested projectTypeNested = response.getAggregations().get("group_project_type"); System.out.println("项目类型:" + projectTypeNested.getDocCount()); ParsedStringTerms projectTypeTerms = projectTypeNested.getAggregations().get("group_project_type_name"); if (CollectionUtils.isNotEmpty(projectTypeTerms.getBuckets())) { for (Terms.Bucket projectTypeBucket : projectTypeTerms.getBuckets()) { System.out.println("- " + projectTypeBucket.getKeyAsString() + ":" + projectTypeBucket.getDocCount()); ParsedNested attrNested = projectTypeBucket.getAggregations().get("group_attr"); System.out.println("\t 属性:" + attrNested.getDocCount()); ParsedStringTerms attrTerms = attrNested.getAggregations().get("group_attr_name"); if (CollectionUtils.isNotEmpty(attrTerms.getBuckets())) { for (Terms.Bucket attrBucket : attrTerms.getBuckets()) { System.out.println("\t -- " + attrBucket.getKeyAsString() + ":" + attrBucket.getDocCount()); ParsedStringTerms attrValueTerms = attrBucket.getAggregations().get("group_attr_value"); if (CollectionUtils.isNotEmpty(attrValueTerms.getBuckets())) { for (Terms.Bucket valueBucket : attrValueTerms.getBuckets()) { System.out.println("\t\t --- " + valueBucket.getKeyAsString() + ":" + valueBucket.getDocCount()); } } } } } } }
执行结果
项目类型:969 - 白莲新增项目类型测试:467 属性:467 -- 样式:467 --- 镶钻:467 - HA4D面部:87 属性:261 -- 作用部位:87 --- 面部:87 -- 品牌:87 --- HA4D:87 -- 服务时长:87 --- 50:87 - 白莲看图选购项目类型04:70 属性:70 -- 操作时长:70 --- 60:70 - 明镜描述测试:54 属性:54 -- 服务时长-白:54 --- 60:54 - 馨迪蕊拉局部按摩:26 属性:78 -- 作用部位:26 --- 背部:16 -- 品牌:26 --- 馨迪蕊拉:26 -- 服务时长:26 --- 45:25 - 淡然测试项目类型0511_02:22 属性:22 -- 治疗周期:22 --- 一天:15 - 白莲看图选购项目类型01:22 属性:22 -- 服务时长(分钟):22 --- 70:21 - 天芮面部:21 属性:63 -- 作用部位:21 --- 面部:21 -- 品牌:21 --- 天芮:21 -- 服务时长:21 - 爱仕兰面部:20 属性:60 -- 作用部位:20 --- 面部:20 -- 品牌:20 --- 爱仕兰:20 -- 服务时长:20 --- 20:20 - Refa面部:18 属性:54 -- 作用部位:18 --- 手部:18 --- 脊柱:18 --- 腿部:18 -- 品牌:18 --- 馨迪蕊拉:18 -- 服务时长:18 --- 130:18
未经允许请勿转载:程序喵 » ElasticSearch 存储树形结构数据,统计树形数据。