一、背景
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 存储树形结构数据,统计树形数据。
程序喵