暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

ElasticSearch 如何像 MySQL 一样做多表联合查询?

北漂码农有话说 2021-02-24
3490

今天我们来看 Es 中的嵌套查询与父子文档,这两个东西很有来头!关系型数据库中的多表联合查询需求在 Es 中可以用它们来解决。

视频:

以下是视频笔记:

注意,笔记只是视频内容的一个简要记录,因此笔记内容比较简单,完整的内容可以查看视频。

关系型数据库中有表的关联关系,在 es 中,我们也有类似的需求,例如订单表和商品表,在 es 中,这样的一对多一般来说有两种方式:

  • 嵌套文档(nested)
  • 父子文档

18.1 嵌套文档

假设:有一个电影文档,每个电影都有演员信息:

PUT movies
{
  "mappings": {
    "properties": {
      "actors":{
        "type""nested"
      }
    }
  }
}

PUT movies/_doc/1
{
  "name":"霸王别姬",
  "actors":[
    {
      "name":"张国荣",
      "gender":"男"
    },
    {
      "name":"巩俐",
      "gender":"女"
    }
    ]
}

注意 actors 类型要是 nested,具体原因参考 10.2.3 小节。

缺点

查看文档数量:

GET _cat/indices?v

查看结果如下:

image-20201119162958456

这是因为 nested 文档在 es 内部其实也是独立的 lucene 文档,只是在我们查询的时候,es 内部帮我们做了 join 处理,所以最终看起来就像一个独立文档一样。因此这种方案性能并不是特别好。

18.2 嵌套查询

这个用来查询嵌套文档:

GET movies/_search
{
  "query": {
    "nested": {
      "path""actors",
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "actors.name""张国荣"
              }
            },
            {
              "match": {
                "actors.gender""男"
              }
            }
          ]
        }
      }
    }
  }
}

18.3 父子文档

相比于嵌套文档,父子文档主要有如下优势:

  • 更新父文档时,不会重新索引子文档
  • 创建、修改或者删除子文档时,不会影响父文档或者其他的子文档。
  • 子文档可以作为搜索结果独立返回。

例如学生和班级的关系:

PUT stu_class
{
  "mappings": {
    "properties": {
      "name":{
        "type""keyword"
      },
      "s_c":{
        "type""join",
        "relations":{
          "class":"student"
        }
      }
    }
  }
}

s_c
表示父子文档关系的名字,可以自定义。join 表示这是一个父子文档。relations 里边,class 这个位置是 parent,student 这个位置是 child。

接下来,插入两个父文档:

PUT stu_class/_doc/1
{
  "name":"一班",
  "s_c":{
    "name":"class"
  }
}
PUT stu_class/_doc/2
{
  "name":"二班",
  "s_c":{
    "name":"class"
  }
}

再来添加三个子文档:

PUT stu_class/_doc/3?routing=1
{
  "name":"zhangsan",
  "s_c":{
    "name":"student",
    "parent":1
  }
}
PUT stu_class/_doc/4?routing=1
{
  "name":"lisi",
  "s_c":{
    "name":"student",
    "parent":1
  }
}
PUT stu_class/_doc/5?routing=2
{
  "name":"wangwu",
  "s_c":{
    "name":"student",
    "parent":2
  }
}

首先大家可以看到,子文档都是独立的文档。特别需要注意的地方是,子文档需要和父文档在同一个分片上,所以 routing 关键字的值为父文档的 id。另外,name 属性表明这是一个子文档。

父子文档需要注意的地方:

  1. 每个索引只能定义一个 join filed
  2. 父子文档需要在同一个分片上(查询,修改需要routing)
  3. 可以向一个已经存在的 join filed 上新增关系

18.4 has_child query

通过子文档查询父文档使用 has_child
query。

GET stu_class/_search
{
  "query": {
    "has_child": {
      "type""student",
      "query": {
        "match": {
          "name""wangwu"
        }
      }
    }
  }
}

查询 wangwu 所属的班级。

18.5 has_parent query

通过父文档查询子文档:

GET stu_class/_search
{
  "query": {
    "has_parent": {
      "parent_type""class",
      "query": {
        "match": {
          "name""二班"
        }
      }
    }
  }
}

查询二班的学生。但是大家注意,这种查询没有评分。

可以使用 parent id 查询子文档:

GET stu_class/_search
{
  "query": {
    "parent_id":{
      "type":"student",
      "id":1
    }
  }
}

通过 parent id 查询,默认情况下使用相关性计算分数。

18.6 小结

整体上来说:

  1. 普通子对象实现一对多,会损失子文档的边界,子对象之间的属性关系丢失。
  2. nested 可以解决第 1 点的问题,但是 nested 有两个缺点:更新主文档的时候要全部更新,不支持子文档属于多个主文档。
  3. 父子文档解决 1、2 点的问题,但是它主要适用于写多读少的场景。
文章转载自北漂码农有话说,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论