从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. SpringMVC对日期类型的转换

    在做web开发的时候,页面传入的都是String类型,SpringMVC可以对一些基本的类型进行转换,但是对于日期类的转换可能就需要我们配置. 1.如果查询类使我们自己写,那么在属性前面加上@Date ...

  2. spring spel

    •Spring 表达式语言(简称SpEL):是一个支持运行时查询和操作对象图的强大的表达式语言.   •语法类似于 EL:SpEL 使用 #{…} 作为定界符,所有在大框号中的字符都将被认为是 SpE ...

  3. 在打开vs解决方案时,怎样让所以打开的项目自动折叠

    使用VS 2010中的扩展性,搜PowerCommands,PowerCommands扩展在Visual Studio 2010中添加了数十个有用的的命令, Collapse Projects(折叠项 ...

  4. servlet3.0,web.xml的metadata-complete的作用

    metadata-complete是servlet3.0规范中的新增的属性,该属性接受两个属性值,true或false.当该属性值为true时,该web应用将不会加载Annotation配置的web组 ...

  5. 数列平方根的和 java

    题目描述: 数列的定义如下:数列的第一项为n,以后各项为前一项的平方根,求数列的前m项的和. 输入 输入数据有多组,每组占一行,由两个整数n(n<10000)和m(m<1000)组成,n和 ...

  6. hdu 4499 Cannon(暴力)

    题目链接:hdu 4499 Cannon 题目大意:给出一个n*m的棋盘,上面已经存在了k个棋子,给出棋子的位置,然后求能够在这种棋盘上放多少个炮,要求后放置上去的炮相互之间不能攻击. 解题思路:枚举 ...

  7. 在tornado中使用celery实现异步任务处理之中的一个

    一.简单介绍 tornado-celery是用于Tornado web框架的非堵塞 celeryclient. 通过tornado-celery能够将耗时任务增加到任务队列中处理, 在celery中创 ...

  8. 【iOS7一些总结】9、与列表显示(在):列表显示UITableView

    列表显示,顾名思义它是在一个列表视图的形式显示在屏幕上的数据的内容.于ios在列表视图UITableView达到.这个类在实际应用中频繁,是很easy理解.这里将UITableView的主要使用方法总 ...

  9. Swift区间运算符

    Swift 提供了两个方便表达一个区间的值的运算符. 闭区间运算符 闭区间运算符(a...b)定义一个包含从a到b(包括a和b)的所有值的区间. ‌ 闭区间运算符在迭代一个区间的所有值时是非常有用的, ...

  10. Chartist.js-同时画柱状图和折线图

    最近几天都在研究chartist,因为echarts生成的图是位图,导成PDF的时候不够清晰.而chartist是搜到的免费插件中呼声较高的,基于SVG. 今天主要是想举一些代码例子给大家,介绍下如何 ...