ElasticSearch父子关联文档实现关系型数据库join操作

声明:以下只针对es6.0以下版本,原因是阿里云上售卖的es服务器是5.5.3版本,所以在写业务的时候是针对6.0以下版本的操作!

6.0以下版本如果想实现关系型数据库中一对多的操作,有两种常用方法:

1. 非规范化数据

比如mysql描述订单以及订单详情 :  order(id, order_no, amount)    ->  order_detail(id, order_id, commodity, price) , 在关系型数据库中一个订单对应多个订单详情,详情表通过order_id与订单表关联。

那么在es中可以通过冗余数据描述这种关系, 索引如下:

PUT order {
    "mappings": {
        "order": {                                 --> 指定文档名(订单表)
            "properties": {                        --> 指定字段以及类型
                "id": {
                    "type": "integer"
                },
                "order_no": {
                    "type": "long"
                },
                "amount": {
                    "type": "double"
                },
                "order_detail": {                 --> 描述订单详情
                    "properties": {
                        "id": {
                            "type": "integer"
                        },
                        "order_id": {
                            "type": "integer"
                        },
                        "commodity": {
                            "type": "text"
                        },
                        "price": {
                            "type": "double"
                        }
                    }
                }
            }
        }
    }
}

该索引就是把订单以及订单详情冗余在一起。订单详情可以理解成java实体对象中的集合对象。

2. 父子文档关联

父子文档关联类似于join的操作,通过建立索引的时候描述文档之间的关系

父-子关系文档 在实质上类似于 nested model :允许将一个对象实体和另外一个对象实体关联起来。而这两种类型的主要区别是:在 nested objects 文档中,所有对象都是在同一个文档中,而在父-子关系文档中,父对象和子对象都是完全独立的文档。

父-子关系的主要作用是允许把一个 type 的文档和另外一个 type 的文档关联起来,构成一对多的关系:一个父文档可以对应多个子文档 。与 nested objects 相比,父-子关系的主要优势有:

  • 更新父文档时,不会重新索引子文档。
  • 创建,修改或删除子文档时,不会影响父文档或其他子文档。这一点在这种场景下尤其有用:子文档数量较多,并且子文档创建和修改的频率高时。
  • 子文档可以作为搜索结果独立返回。

Elasticsearch 维护了一个父文档和子文档的映射关系,得益于这个映射,父-子文档关联查询操作非常快。但是这个映射也对父-子文档关系有个限制条件:父文档和其所有子文档,都必须要存储在同一个分片中。

父-子文档ID映射存储在 Doc Values 中。当映射完全在内存中时, Doc Values 提供对映射的快速处理能力,另一方面当映射非常大时,可以通过溢出到磁盘提供足够的扩展能力

建立索引:

PUT order
{
  "mappings": {
    "order": {            --> 父文档
      "properties": {     --> 父文档字段属性
        "id": {
          "type": "integer"
        },
        "order_no": {
          "type": "long"
        },
        "amount": {
          "type": "double"
        }
      }
    },
    "order_detail": {    --> 子文档
      "_parent": {       --> 指定子文档的父亲
        "type": "order"
      },
      "properties": {
        "id": { "type": "integer" },
        "order_id": { "type": "integer" },
        "commodity": { "type": "text" },
        "price": { "type": "double" }
      }
    }
  }
}

创建父文档:

PUT /order/order/1        --> PUT /索引/文档/唯一标识(不写默认生成)
{
  "id": "1",
  "order_no": "123456",
  "amount": "20"
}

创建子文档:

PUT /order/order_detail/1?parent=1      -->  在创建子文档的时候需要使用parent=?指定父文档是谁!!!该处指定父文档为标识为1的order
{
  "id": "1",
  "order_id": "1",
  "commodity": "小米",
  "price": "15"
}

PUT order/order_detail/2?parent=1
{
  "id": "2",
  "order_id": "1",
  "commodity": "番茄",
  "price": "5"
}

查询父文档,并显示所有子文档:

GET order/order/_search
{
  "query": {
    "has_child": {
      "type": "order_detail",
      "query": {
        "match_all": {}
      },
      "inner_hits": {}            --> 显示所有子文档
    }
  }
}

查询结果:

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 1,
    "hits": [
      {
        "_index": "order",
        "_type": "order",
        "_id": "1",
        "_score": 1,
        "_source": {
          "id": "1",
          "order_no": "123456",
          "amount": "20"
        },
        "inner_hits": {
          "order_detail": {
            "hits": {
              "total": 2,
              "max_score": 1,
              "hits": [
                {
                  "_type": "order_detail",
                  "_id": "1",
                  "_score": 1,
                  "_routing": "1",
                  "_parent": "1",
                  "_source": {
                    "id": "1",
                    "order_id": "1",
                    "commodity": "小米",
                    "price": "15"
                  }
                },
                {
                  "_type": "order_detail",
                  "_id": "2",
                  "_score": 1,
                  "_routing": "1",
                  "_parent": "1",
                  "_source": {
                    "id": "2",
                    "order_id": "1",
                    "commodity": "番茄",
                    "price": "5"
                  }
                }
              ]
            }
          }
        }
      }
    ]
  }
}

复杂父子文档聚合查询:  

GET order/order/_search
{
  "size": 0,                  
  "aggs": {
    "sum_amount": {              -->指定聚合文档order,求amount字段sum值,并取名为sum_amount
      "sum": {
        "field": "amount"
      }
    },
    "detail": {
      "children": {              --> 指定子文档为order_detail
        "type": "order_detail"
      },
      "aggs": {                  --> 子文档聚合操作,求子文档price字段sum值,并取名为sum_price
        "sum_price": {
          "sum": {
            "field": "price"
          }
        }
      }
    }
  }
}

查询结果:

{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 1,            --> order文档一共一条记录
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "sum_amount": {        --> 父文档sum(amount)结果
      "value": 20
    },
    "detail": {            --> 子文档
      "doc_count": 2,      --> 子文档一共两条记录
      "sum_price": {       --> 子文档sum(price)结果
        "value": 20
      }
    }
  }
}

注意:es不支持聚合后的结果进行分页操作!!!

展开阅读全文

没有更多推荐了,返回首页