从SQL领域来的用户,对于ES的文件关系维护方式会感到很不习惯。毕竟,ES是分布式数据库只能高效处理独个扁平类型文件,无法支持关系式数据库那样的文件拼接。但是,任何数据库应用都无法避免树型文件关系,因为这是业务模式需要的表现形式。在ES里,无论nested或join类型的数据,父-子关系的数据文件实际上是放在同一个索引index里的。在ES里已经没有数据表(doc_type)的概念。但从操作层面上ES提供了relation类型来支持父-子数据关系操作。所以,nested数据类型一般用来表达比较固定的嵌入数据。因为每次更新都需要重新对文件进行一次索引。join类型的数据则可以对数据关系的两头分别独立进行更新,方便很多。

下面我们现示范一下nested数据类型的使用。在mapping里可以申明nested数据类型来代表嵌入文件,如下:

  val fruitMapping = client.execute(
putMapping("fruits").fields(
KeywordField("code"),
SearchAsYouTypeField("name")
.fields(KeywordField("keyword")),
floatField("price"),
NestedField("location").fields(
KeywordField("shopid"),
textField("shopname"),
longField("qty"))
)
).await

这段代码产生了下面的mapping:

{
"fruits" : {
"mappings" : {
"properties" : {
"code" : {
"type" : "keyword"
},
"location" : {
"type" : "nested",
"properties" : {
"qty" : {
"type" : "long"
},
"shopid" : {
"type" : "keyword"
},
"shopname" : {
"type" : "text"
}
}
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword"
}
}
},
"price" : {
"type" : "float"
}
}
}
}
}

location是个nested类型字段,内嵌文件格式含shopid,shopname,qty各字段。下面的例子里向fruits索引添加了几个包含了location的文件:

  val f1 = indexInto("fruits").id("f001")
.fields(
"code" -> "f001",
"name" -> "东莞荔枝",
"price" -> 11.5,
"location" -> List(Map(
"shopid" -> "s001",
"shopname" -> "中心店",
"qty" -> 500.0
),
Map(
"shopid" -> "s002",
"shopname" -> "东门店",
"qty" -> 0.0
)
)
)
val f2 = indexInto("fruits").id("f002")
.fields(
"code" -> "f002",
"name" -> "陕西富士苹果",
"price" -> 11.5,
"location" -> List(Map(
"shopid" -> "s001",
"shopname" -> "中心店",
"qty" -> 300.0
),
Map(
"shopid" -> "s003",
"shopname" -> "龙岗店",
"qty" -> 200.0
)
)
)
val f3 = indexInto("fruits").id("f003")
.fields(
"code" -> "f003",
"name" -> "进口菲律宾香蕉",
"price" -> 5.3,
"location" -> List(Map(
"shopid" -> "s001",
"shopname" -> "中心店",
"qty" -> 300.0
),
Map(
"shopid" -> "s003",
"shopname" -> "龙岗店",
"qty" -> 200.0
),
Map(
"shopid" -> "s002",
"shopname" -> "东门店",
"qty" -> 200.0
)
)
)
val newIndex = for {
_ <- client.execute(f1)
_ <- client.execute(f2)
_ <- client.execute(f3)
} yield ("成功增添三条记录") newIndex.onComplete {
case Success(trb) => println(s"${trb}")
case Failure(err) => println(s"error: ${err.getMessage}")
}

用elastic4s可以比较方便的进行nested类型数据更新。下面是个更新nested文件的例子:

  val f002 = client.execute(get("fruits","f002").fetchSourceInclude("location")).await
val locs: List[Map[String,Any]] = f002.result.source("location").asInstanceOf[List[Map[String,Any]]]
val newloc = Map("shopid" -> "s004","shopname" -> "宝安店", "qty" -> )
val newlocs = locs.foldLeft(List[Map[String,Any]]()) { (b, m) =>
if (m("shopid") != newloc("shopid"))
m :: b
else b
} val newdoc = updateById("fruits","f002")
.doc(
Map(
"location" -> (newloc :: newlocs)
)
)

在上面这个例子里:需要把一条新的嵌入文件s004更新到f002文件里。我们先把f002里原来的location取出,去掉s004节点,然后将新节点加入location清单,再更新update f002文件。

刚才提到过:join类型实际上还是在同一个索引里实现的。比如我希望记录每个fruit的进货历史,也就是说现在fruit下需要增加一个子文件purchase_history。这个purchase_history也是在同一个mapping里定义的:

  val fruitMapping = client.execute(
putMapping("fruits").fields(
KeywordField("code"),
SearchAsYouTypeField("name")
.fields(KeywordField("keyword")),
floatField("price"),
NestedField("location").fields(
KeywordField("shopid"),
textField("shopname"),
longField("qty")),
//purchase_history
keywordField("supplier_code"),
textField("supplier_name"),
dateField("purchase_date")
.ignoreMalformed(true)
.format("strict_date_optional_time||epoch_millis"),
joinField("purchase_history")
.relation("fruit","purchase")
)
).await

下面是关于上层父文件的索引indexing操作的例子:

  val f1 = indexInto("fruits").id("f001").routing("f001")
.fields(
"code" -> "f001",
"name" -> "东莞荔枝",
"price" -> 11.5,
"location" -> List(Map(
"shopid" -> "s001",
"shopname" -> "中心店",
"qty" -> 500.0
),
Map(
"shopid" -> "s002",
"shopname" -> "东门店",
"qty" -> 0.0
)
),
"purchase_history" -> "fruit"
)
val f2 = indexInto("fruits").id("f002").routing("f002")
.fields(
"code" -> "f002",
"name" -> "陕西富士苹果",
"price" -> 11.5,
"location" -> List(Map(
"shopid" -> "s001",
"shopname" -> "中心店",
"qty" -> 300.0
),
Map(
"shopid" -> "s003",
"shopname" -> "龙岗店",
"qty" -> 200.0
)
),
"purchase_history" -> "fruit"
)
val f3 = indexInto("fruits").id("f003").routing("f003")
.fields(
"code" -> "f003",
"name" -> "进口菲律宾香蕉",
"price" -> 5.3,
"location" -> List(Map(
"shopid" -> "s001",
"shopname" -> "中心店",
"qty" -> 300.0
),
Map(
"shopid" -> "s003",
"shopname" -> "龙岗店",
"qty" -> 200.0
),
Map(
"shopid" -> "s002",
"shopname" -> "东门店",
"qty" -> 200.0
)
),
"purchase_history" -> "fruit"
)
val newIndex = for {
_ <- client.execute(f1)
_ <- client.execute(f2)
_ <- client.execute(f3)
} yield ("成功增添三条记录")

elastic4s子文件的索引操作示范如下:

  val h1 = indexInto("fruits").id("h001").routing("f003")
.fields(
"supplier_code" -> "v001",
"supplier_name" -> "百果园",
"purchase_date" -> "2020-02-09",
"purchase_history" -> Child("purchase", "f003")) val h2 = indexInto("fruits").id("h002").routing("f002")
.fields(
"supplier_code" -> "v001",
"supplier_name" -> "百果园",
"purchase_date" -> "2019-10-11",
"purchase_history" -> Child("purchase", "f002")) val h3 = indexInto("fruits").id("h003").routing("f002")
.fields(
"supplier_code" -> "v002",
"supplier_name" -> "华南城花果批发市场",
"purchase_date" -> "2020-01-23",
"purchase_history" -> Child("purchase", "f002")) val childIndex = for {
_ <- client.execute(h1)
_ <- client.execute(h2)
_ <- client.execute(h3)
} yield ("成功增添三条子记录")

好了,现在这个fruits索引里已经包含了nested,join两种嵌入文件数据。下面我们就试试各种的读取方式。首先nested类型数据可以通过nestedQuery读取:

  val qNested = search("fruits").query(
nestedQuery("location").query(
matchQuery("location.shopname","中心")
)
)
println(s"${qNested.show}")
val nestedResult = client.execute(qNested).await
if(nestedResult.isSuccess)
nestedResult.result.hits.hits.foreach(m => println(s"${m.sourceAsMap}"))
else println(s"Error: ${nestedResult.error.causedBy.getOrElse("unknown")}") ... POST:/fruits/_search?
StringEntity({"query":{"nested":{"path":"location","query":{"match":{"location.shopname":{"query":"中心"}}}}}},Some(application/json))
HashMap(name -> 东莞荔枝, location -> List(Map(shopid -> s001, shopname -> 中心店, qty -> 500.0), Map(shopid -> s002, shopname -> 东门店, qty -> 0.0)), price -> 11.5, purchase_history -> fruit, code -> f001)
HashMap(name -> 进口菲律宾香蕉, location -> List(Map(shopid -> s001, shopname -> 中心店, qty -> 300.0), Map(shopid -> s003, shopname -> 龙岗店, qty -> 200.0), Map(shopid -> s002, shopname -> 东门店, qty -> 200.0)), price -> 5.3, purchase_history -> fruit, code -> f003)
HashMap(name -> 陕西富士苹果, location -> List(Map(shopname -> 宝安店, qty -> , shopid -> s004), Map(shopname -> 龙岗店, qty -> 200.0, shopid -> s003), Map(shopname -> 中心店, qty -> 300.0, shopid -> s001)), price -> 11.5, purchase_history -> fruit, code -> f002)

join类型子文件可以通过子文件的ParentID Query读取:

  val qPid = search("fruits").query(
ParentIdQuery("purchase","f002")
)
println(s"${qPid.show}") val pidResult = client.execute(qPid).await
if(pidResult.isSuccess)
pidResult.result.hits.hits.foreach(m => println(s"${m.sourceAsMap}"))
else println(s"Error: ${pidResult.error.causedBy.getOrElse("unknown")}") ... POST:/fruits/_search?
StringEntity({"query":{"parent_id":{"type":"purchase","id":"f002"}}},Some(application/json))
Map(supplier_code -> v001, supplier_name -> 百果园, purchase_date -> --, purchase_history -> Map(name -> purchase, parent -> f002))
Map(supplier_code -> v002, supplier_name -> 华南城花果批发市场, purchase_date -> --, purchase_history -> Map(name -> purchase, parent -> f002))

join类型父辈文件可以通过搜索其子文件hasChild获取:

  val qHaschild = search("fruits").query(
hasChildQuery("purchase",
matchQuery("supplier_name","百果")
)
)
println(s"${qHaschild.show}")
val haschildResult = client.execute(qHaschild).await
if(haschildResult.isSuccess)
haschildResult.result.hits.hits.foreach(m => println(s"${m.sourceAsMap}"))
else println(s"Error: ${haschildResult.error.causedBy.getOrElse("unknown")}") ... POST:/fruits/_search?
StringEntity({"query":{"has_child":{"type":"purchase","score_mode":"none","query":{"match":{"supplier_name":{"query":"百果"}}}}}},Some(application/json))
HashMap(name -> 进口菲律宾香蕉, location -> List(Map(shopid -> s001, shopname -> 中心店, qty -> 300.0), Map(shopid -> s003, shopname -> 龙岗店, qty -> 200.0), Map(shopid -> s002, shopname -> 东门店, qty -> 200.0)), price -> 5.3, purchase_history -> fruit, code -> f003)
HashMap(name -> 陕西富士苹果, location -> List(Map(shopname -> 宝安店, qty -> , shopid -> s004), Map(shopname -> 龙岗店, qty -> 200.0, shopid -> s003), Map(shopname -> 中心店, qty -> 300.0, shopid -> s001)), price -> 11.5, purchase_history -> fruit, code -> f002)

join类型子文件也可以搜索其父辈文件获取:

 val qHasparent= search("fruits").query(
hasParentQuery("fruit",
nestedQuery("location").query(
matchQuery("location.shopname","中心")
),false
)
)
println(s"${qHasparent.show}")
val hasparentResult = client.execute(qHasparent).await
if(hasparentResult.isSuccess)
hasparentResult.result.hits.hits.foreach(m => println(s"${m.sourceAsMap}"))
else println(s"Error: ${hasparentResult.error.causedBy.getOrElse("unknown")}") ... OST:/fruits/_search?
StringEntity({"query":{"has_parent":{"parent_type":"fruit","query":{"nested":{"path":"location","query":{"match":{"location.shopname":{"query":"中心"}}}}}}}},Some(application/json))
Map(supplier_code -> v001, supplier_name -> 百果园, purchase_date -> --, purchase_history -> Map(name -> purchase, parent -> f003))
Map(supplier_code -> v001, supplier_name -> 百果园, purchase_date -> --, purchase_history -> Map(name -> purchase, parent -> f002))
Map(supplier_code -> v002, supplier_name -> 华南城花果批发市场, purchase_date -> --, purchase_history -> Map(name -> purchase, parent -> f002))

上面这个例子稍微复杂一点:我们想得出所有子文件,它们的父辈文件里嵌入nested文件包含location.shopname match "中心"。

这些例子主要展示了如何通过父子关系的一方取获取另一方的数据,如:通过子文件搜索获取对应的父文件或通过父文件获取对应的子文件。也就是说搜索目标和获取目标:父子、子父,不是同一种文件。我们可以通过inner_hits来同时获取符合搜索条件的文件。如nestedQuery.inner():

 val qNested = search("fruits").query(
nestedQuery("location").query(
matchQuery("location.shopname","中心")
).inner(InnerHit("locations"))
)
println(s"${qNested.show}")
val nestedResult = client.execute(qNested).await
if(nestedResult.isSuccess) {
nestedResult.result.hits.hits.foreach{ m =>
println(s"${m.sourceAsMap}")
m.innerHits.foreach { i =>
val n = i._1
i._2.hits.foreach(h => println(s"$n, ${h.source}"))
}
}
} else println(s"Error: ${nestedResult.error.causedBy.getOrElse("unknown")}") ... POST:/fruits/_search?
StringEntity({"query":{"nested":{"path":"location","query":{"match":{"location.shopname":{"query":"中心"}}},"inner_hits":{"name":"locations"}}}},Some(application/json))
HashMap(name -> 东莞荔枝, location -> List(Map(shopid -> s001, shopname -> 中心店, qty -> 500.0), Map(shopid -> s002, shopname -> 东门店, qty -> 0.0)), price -> 11.5, purchase_history -> fruit, code -> f001)
locations, Map(shopid -> s001, shopname -> 中心店, qty -> 500.0)
HashMap(name -> 进口菲律宾香蕉, location -> List(Map(shopid -> s001, shopname -> 中心店, qty -> 300.0), Map(shopid -> s003, shopname -> 龙岗店, qty -> 200.0), Map(shopid -> s002, shopname -> 东门店, qty -> 200.0)), price -> 5.3, purchase_history -> fruit, code -> f003)
locations, Map(shopid -> s001, shopname -> 中心店, qty -> 300.0)
HashMap(name -> 陕西富士苹果, location -> List(Map(shopname -> 宝安店, qty -> , shopid -> s004), Map(shopname -> 龙岗店, qty -> 200.0, shopid -> s003), Map(shopname -> 中心店, qty -> 300.0, shopid -> s001)), price -> 11.5, purchase_history -> fruit, code -> f002)
locations, Map(shopname -> 中心店, qty -> 300.0, shopid -> s001)

hasChildQuery.innerHit():

  val qHaschild = search("fruits").query(
hasChildQuery("purchase",
matchQuery("supplier_name","百果")
).innerHit("purchases")
)
println(s"${qHaschild.show}")
val haschildResult = client.execute(qHaschild).await
if(haschildResult.isSuccess) {
haschildResult.result.hits.hits.foreach{m =>
println(s"${m.sourceAsMap}")
m.innerHits.foreach { i =>
val n = i._1
i._2.hits.foreach(h => println(s"$n, ${h.source}"))
}
}
} else println(s"Error: ${haschildResult.error.causedBy.getOrElse("unknown")}") ... POST:/fruits/_search?
StringEntity({"query":{"has_child":{"type":"purchase","score_mode":"none","query":{"match":{"supplier_name":{"query":"百果"}}},"inner_hits":{"name":"purchases"}}}},Some(application/json))
HashMap(name -> 进口菲律宾香蕉, location -> List(Map(shopid -> s001, shopname -> 中心店, qty -> 300.0), Map(shopid -> s003, shopname -> 龙岗店, qty -> 200.0), Map(shopid -> s002, shopname -> 东门店, qty -> 200.0)), price -> 5.3, purchase_history -> fruit, code -> f003)
purchases, Map(supplier_code -> v001, supplier_name -> 百果园, purchase_date -> --, purchase_history -> Map(name -> purchase, parent -> f003))
HashMap(name -> 陕西富士苹果, location -> List(Map(shopname -> 宝安店, qty -> , shopid -> s004), Map(shopname -> 龙岗店, qty -> 200.0, shopid -> s003), Map(shopname -> 中心店, qty -> 300.0, shopid -> s001)), price -> 11.5, purchase_history -> fruit, code -> f002)
purchases, Map(supplier_code -> v001, supplier_name -> 百果园, purchase_date -> --, purchase_history -> Map(name -> purchase, parent -> f002))
purchases, Map(supplier_code -> v002, supplier_name -> 华南城花果批发市场, purchase_date -> --, purchase_history -> Map(name -> purchase, parent -> f002))

hasParentQuery.innerHit():

  val qHasparent= search("fruits").query(
hasParentQuery("fruit",
nestedQuery("location").query(
matchQuery("location.shopname","中心")
),false
).innerHit(InnerHit("fruits"))
)
println(s"${qHasparent.show}")
val hasparentResult = client.execute(qHasparent).await
if(hasparentResult.isSuccess) {
hasparentResult.result.hits.hits.foreach{m =>
println(s"${m.sourceAsMap}")
m.innerHits.foreach { i =>
val n = i._1
i._2.hits.foreach(h => println(s"$n, ${h.source}"))
}
}
} else println(s"Error: ${hasparentResult.error.causedBy.getOrElse("unknown")}") ... POST:/fruits/_search?
StringEntity({"query":{"has_parent":{"parent_type":"fruit","query":{"nested":{"path":"location","query":{"match":{"location.shopname":{"query":"中心"}}}}},"inner_hits":{"name":"fruits"}}}},Some(application/json))
Map(supplier_code -> v001, supplier_name -> 百果园, purchase_date -> --, purchase_history -> Map(name -> purchase, parent -> f003))
fruits, HashMap(name -> 进口菲律宾香蕉, location -> List(Map(shopid -> s001, shopname -> 中心店, qty -> 300.0), Map(shopid -> s003, shopname -> 龙岗店, qty -> 200.0), Map(shopid -> s002, shopname -> 东门店, qty -> 200.0)), price -> 5.3, purchase_history -> fruit, code -> f003)
Map(supplier_code -> v001, supplier_name -> 百果园, purchase_date -> --, purchase_history -> Map(name -> purchase, parent -> f002))
fruits, HashMap(name -> 陕西富士苹果, location -> List(Map(shopname -> 宝安店, qty -> , shopid -> s004), Map(shopname -> 龙岗店, qty -> 200.0, shopid -> s003), Map(shopname -> 中心店, qty -> 300.0, shopid -> s001)), price -> 11.5, purchase_history -> fruit, code -> f002)
Map(supplier_code -> v002, supplier_name -> 华南城花果批发市场, purchase_date -> --, purchase_history -> Map(name -> purchase, parent -> f002))
fruits, HashMap(name -> 陕西富士苹果, location -> List(Map(shopname -> 宝安店, qty -> , shopid -> s004), Map(shopname -> 龙岗店, qty -> 200.0, shopid -> s003), Map(shopname -> 中心店, qty -> 300.0, shopid -> s001)), price -> 11.5, purchase_history -> fruit, code -> f002)

search(16)- elastic4s-内嵌文件:nested and join的更多相关文章

  1. ABP官方文档翻译 6.5 内嵌资源文件

    内嵌资源文件 介绍 创建内嵌文件 xproj/project.json形式 csproj形式 添加内嵌资源管理器 使用内嵌视图 使用内嵌资源 ASP.NET Core 配置 忽略文件 重写内嵌文件 介 ...

  2. 『Asp.Net 组件』Asp.Net 服务器组件 内嵌JS:让自己的控件动起来

    代码: using System; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace ...

  3. 『Asp.Net 组件』Asp.Net 服务器组件 内嵌CSS:将CSS封装到程序集中

    代码: <span style="font-family:Microsoft YaHei; font-size:12px">using System; using Sy ...

  4. C#中内嵌资源的读取

    起因 作为一个从Cpper转到C#并且直接从事WPF开发的萌新来说,正式编码过程中碰到了不少问题,一路上磕磕碰碰的.因为软件设计需求上的要求,需要将一些配置文件(XML.INI等)内嵌到程序中,等需要 ...

  5. 『Asp.Net 组件』Asp.Net 服务器组件 内嵌图片:自己的图片控件

    代码: using System; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace ...

  6. [ASP.NET Core 3框架揭秘] 文件系统[4]:程序集内嵌文件系统

    一个物理文件可以直接作为资源内嵌到编译生成的程序集中.借助于EmbeddedFileProvider,我们可以采用统一的编程方式来读取内嵌的资源文件,该类型定义在 "Microsoft.Ex ...

  7. SQL Server nested loop join 效率试验

    从很多网页上都看到,SQL Server有三种Join的算法, nested loop join, merge join, hash join. 其中最常用的就是nested loop join. 在 ...

  8. Elastic search中使用nested类型的内嵌对象

    在大数据的应用环境中,往往使用反范式设计来提高读写性能. 假设我们有个类似简书的系统,系统里有文章,用户也可以对文章进行赞赏.在关系型数据库中,如果按照数据库范式设计,需要两张表:一张文章表和一张赞赏 ...

  9. qmake.exe是在Qt安装编译时生成的,里面内嵌了Qt相关的一些路径(最简单的方法是保持一样的安装路径,最方便的办法是设置qt.conf文件)

    在网上直接下载别人编译好的Qt库,为自己使用省了不少事.但往往也会遇到些问题,其中Qt version is not properly installed,please run make instal ...

随机推荐

  1. html+css的用户注册界面

    注册界面样图 代码实现 html部分 <!DOCTYPE html> <html lang="en"> <head> <meta char ...

  2. 解决从dockerhub上下载debian:jessie失败

    解决从dockerhub上下载debian:jessie失败 笔者使用docker build 构建镜像出现下面的错误 Step 1/12 : FROM debian:jessie Get https ...

  3. PostgreSQL 10.0 preview 性能增强 - 分区表性能增强(plan阶段加速)

    标签 PostgreSQL , 10.0 , 分区表 , 子表 , 元信息搜索性能增强 背景 PostgreSQL 10.0 增强了分区表的子表搜索性能,对于涉及分区表包含子表特别多的QUERY,可以 ...

  4. Spring官网阅读(四)BeanDefinition(上)

    前面几篇文章已经学习了官网中的1.2,1.3,1.4三小结,主要是容器,Bean的实例化及Bean之间的依赖关系等.这篇文章,我们继续官网的学习,主要是BeanDefinition的相关知识,这是Sp ...

  5. 记录:通过ffmpeg rtsp转 http m3u8

    环境 Windows 10 大华rtsp直播 转 http请求m3u8 ffmpeg -rtsp_transport tcp -i "rtsp://账号:密码@IP:端口/cam/realm ...

  6. 【Spark】如何用Spark查询IP地址?

    文章目录 需求 思路 ip地址转换为Long类型的两种方法 ip地址转换数字地址的原理 第一种方法 第二种方法 步骤 一.在mysql创建数据库表 二.开发代码 需求 日常生活中,当我们打开地图时,会 ...

  7. java接口学习体会

    一.接口引进的意义 为了解决java的单继承不足,即java的类可以实现多个接口. 二.抽象类.接口的区别 三.如何创建接口? 声明接口的关键字是interface,声明类的关键字为class. im ...

  8. 基于 groovy 实现公式库

    formula 基于 groovy 实现的公式库 项目地址 Github 语法 公式名(参数) 比如: ECHO(大侠王波波) 支持公式嵌套: 公式名1(公式名2(参数), 参数) 比如: ECHO( ...

  9. acm的一些头文件和调试代码

    个人觉得单步调试麻烦且费时间,所以我两年时间里F4+watch基本没怎么用过,但由于"查看变量的值"这个需求总是存在的,并且调试时通常需要显示很多东西,printf写起来又比较蛋疼 ...

  10. [hdu4498]离散化,simpson求积分

    题意:,求这个函数在[0,100]上的图像的长度. 思路:采用离散化的思想,求出所有交点 ,把交点排序,把[0,100]分成若干个小区间,这样原函数在每个小区间上的图像属于某一个二次函数或者是一条直线 ...