组合查询
基于Elsticsearch5.4.3版本如何执行如下的sql查询
SELECT product FROM products WHERE (price = 20 OR productID = "XHDK-A-1293-#fJ3") AND (price != 30)
对应的DSL语句
GET /my_store/products/_search
{
"query" : {
"bool" : {
"filter" : {
"bool" : {
"should" : [
{ "term" : {"price" : 20}},
{ "term" : {"productID" : "XHDK-A-1293-#fJ3"}}
],
"must_not" : {
"term" : {"price" : 30}
}
}
}
}
}
}
SELECT document
FROM products
WHERE productID = "KDKE-B-9947-#kL5"
OR ( productID = "JODL-X-1937-#pV7"
AND price = 30 )
对应的DSL语句
GET /my_store/products/_search
{
"from": 0,
"size": 20,
"sort": [
{
"deal_time": "ASC"
}
],
"query": {
"bool": {
"must_not": [
{
"terms": {
"city.keyword": [
"XXXXXXXXXXX"
]
}
}
],
"filter": {
"bool": {
"should": [
{
"terms": {
"source.keyword": [
"XXXXX",
"XXXXXXXXX",
"XXXXXXXXX"
]
}
},
{
"bool": {
"must": [
{
"terms": {
"source.keyword": [
"XXXX",
"XXXXXXXXXXXXXX"
]
}
},
{
"range": {
"time": {
"gte": "20190301000000"
}
}
}
]
}
}
]
}
},
"must": [
{
"bool": {
"must": [
{
"range": {
"id": {
"lt": 9000000000000000
}
}
}
]
}
}
]
}
}
}
数据建模
嵌套文档
由于在 Elasticsearch 中单个文档的增删改都是原子性操作,那么将相关实体数据都存储在同一文档中也就理所当然。 比如说,我们可以将订单及其明细数据存储在一个文档中。如果我们依赖字段自动映射,那么明细字段会自动映射为 object 类型。这就是如果明细数据是数组类型的话,会被扁平化处理。因为es支持 JSON 还有 null 值,数组,和对象。内部对象会被映射为info.model
,内部对象数组的话由于数组在es中会被打破相关性,所以会导致查询的结果不准确。这时候可以使用嵌套文档来处理这种场景。
- 创建文档
PUT /my_index/order/1
{
"id": "00000000000000000000000001212212",
"produceName": "Apple/苹果 iPhone 8",
"tags": [ "手机", "通讯设备" ],
"info":{
"model":"金属",
},
"transactions": [
{
"transactionId": "20190319122312",
"name": "White",
"comment": "购买套餐1",
"content": "乞丐版",
"money": 4888,
"stars": 4,
"date": "2019-03-19"
},
{
"transactionId": "20190318122312",
"name": "Alice",
"comment": "购买套餐2",
"content": "土豪专属",
"money": 6999,
"stars": 5,
"date": "2019-03-18"
}
]
}
上述的文档被存放时,由于es的自动映射会把交易流这个数组映射为object类型。所以文档的中交易编号会以数组的形式来扁平化transactions.transactionId:[20190318122312,20190319122312]
存储,其余字段以此类推,进而进行检索时候就会丢失相关性.导致查询的数据不准确,可能就会查询到start是5的乞丐版的交易流,这个时候我们要更改这个映射关系为嵌套文档。查询的时候也采用嵌套文档的查询
- 更改映射
更改映射的语法如下使用put请求在properties中定义为transactions的type为nested即可。
PUT /my_index
{
"mappings": {
"order": {
"properties": {
"transactions": {
"type": "nested",
"properties": {
"transactionId": { "type": "long" },
"name": { "type": "string" },
"comment": { "type": "string" },
"content": { "type": "string" },
"money": { "type": "short" },
"stars": { "type": "short" },
"date": { "type": "date" }
}
}
}
}
}
}
- 嵌套文档查询
查询语法如下,因为交易流被指定为嵌套文档之后就隐藏在根文档中,无法向搜索根文档中的produceName
使用mathch之类的搜索到。必须使用nested子句才可以检索到。
GET /my_index/order/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"produceName": "苹果"
}
},
{
"nested": {
"path": "transactions",
"query": {
"bool": {
"must": [
{
"match": {
"transactions.name": "Alice"
}
},
{
"match": {
"transactions.money": 6999
}
}
]
}
}
}
}
]
}}}
父子文档模型
场景:有一个公司在多个城市有分公司,并且每一个分公司下面都有很多员工。有这样的需求:按照分公司、员工的维度去搜索,并且把员工和他们工作的分公司联系起。在应用层实现当然也可以 先去查询该地的分公司,然后再去分公司文档查询员工。或者使用不规则文档存储也可以。
例如如下的不规则文档存储可以可以实现的。就是冗余存储。
PUT /myIndex/company/branchcompany/1
{
"title": "富土康河南分部",
"business": "回收二手航母,维修原子弹。。。",
"employee": {
"id": 1,
"name": "张全蛋" ,
"age": 35
}
}
使用父子文档模型存储
更改员工的文档映射
PUT /company
{
"mappings": {
"branchcompany": {},
"employee": {
"_parent": {
"type": "branchcompany"
}
}
}
}
父文档的的索引添加和正常的文档添加一样,唯一不同的是在子文档添加的时候需要添加parent参数,该参数的值
为父文档的_id
;父文档 ID 有两个作用:创建了父文档和子文档之间的关系,并且保证了父文档和子文档都在同一个分片上。
PUT /company/employee/1?parent=1
{
"id": 1,
"name": "张全蛋",
"age": "25",
"hobby": "回收二手航母,维修原子弹"
}
PUT /company/employee/2?parent=1
{
"id": 12,
"name": "黄佳星",
"age": "24",
"hobby": "sleep"
}
has_child 的查询和过滤可以通过子文档的内容来查询父文档,使用如下查询即可查询到张全蛋的信息了。
GET /company/branchcompany/_search
{
"query": {
"has_child": {
"type": "employee",
"query": {
"range": {
"age": {
"gt": "24"
}
}
}
}
}
}
使用 has_parent 语句可以基于父文档来查询子文档。查询将会返回所有在 富土康河南分部
工作的员工
GET /company/employee/_search
{
"query": {
"has_parent": {
"type": "branchcompany",
"query": {
"match_phrase": {
"title": "富土康河南分部"
}
}
}
}
}
基于父子文档这种建模方式,我们可以建立多种层级关系的模式。当然层级越深,检索的性能就会很差。而且要保证所有的祖孙文档要在同一个分片上。所以在创建子孙辈的文档时需要指定routing
用来指定该子孙文档建立在祖宗辈的分片上。
分页查询
深分页(from+size)
考虑到分页过深以及一次请求太多结果的情况,结果集在返回之前先进行排序。但一个请求经常跨越多个分片,每个分片都产生自己的排序结果,这些结果需要进行集中排序以保证整体顺序是正确的。当我们请求结果的第一页(结果从 1 到 10 ),每一个分片产生前 10 的结果,并且返回给 协调节点 ,协调节点对 50 个结果排序得到全部结果的前 10 个。最坏情况下当我们请求第 1000 页--结果从 10001 到 10010 。所有都以相同的方式工作除了每个分片不得不产生前10010个结果以外。 然后协调节点对全部 50050 个结果排序最后丢弃掉这些结果中的 50040 个结果。这就可以看到,在分布式系统中,对结果排序的成本随分页的深度成指数上升。
{
"from": 0,
"size": 20,
"query" : {
"bool" : {
"must_not" : [ ],
"must" : [ {
"bool" : {
"must" : [ {
"term" : {
"id" : 12121121
}
} ]
}
}, {
"bool" : {
"should" : [ ]
}
} ]
}
},
"sort" : [ ]
}
scroll模式
由于es深分页(from+size)模式有【index.max_result_window】限制默认是小于10000 而且性能较低,所以采用游标方式来高效获取全部数据。游标模式的下的分页不具有实时性。属于快照模式。第一次访问时候打开游标会返回_scroll_id
用于之后的查询,第二次的查询需要携带者第一次返回的_scroll_id
。
http://x.x.x.x:9200/my_index/order/_search?scroll=2m post
{
"size": 2995,
"sort": [{
"id": "desc"
}],
"_source": ["id"],
"query": {
"bool": {
"must_not": [],
"must": [{
"bool": {
"must": [{
"term": {
"status": "SUCCEEDED"
}
}]
}
}, {
"bool": {
"should": []
}
}]
}
}
}
打开游标返回结果
{
"_scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAJvI4WVFB6OGYyQVlTUDI1VE5HZXI3Sm1FQQ==",
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 5450,
"max_score": null,
"hits": [{
"_index": "my_index",
"_type": "order",
"_id": "1000040677",
"_score": null,
"_source": {
"id": "1000040677"
},
"sort": [
1000040677
]
}]
}
}
再查询需要包含一个_scroll_id,并且在没有数据时候返回的hits节点的hits数据节点为空数组。
http://x.x.x.x:9200/_search/scroll post
{
"scroll" : "2m",
"scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAtMmpoWeDlqSUZMeDNRdC11akN5SFl5SS1mdw=="
}
有数据返回结果
{
"_scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAtPBkUWeDlqSUZMeDNRdC11akN5SFl5SS1mdw==",
"took": 4,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 5450,
"max_score": null,
"hits": [{
"_index": "my_index",
"_type": "order",
"_id": "1000040671",
"_score": null,
"_source": {
"id": "1000040671"
},
"sort": [
1000040671
]
}]
}
}
无数据返回结果
{
"_scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAJvCIWVFB6OGYyQVlTUDI1VE5HZXI3Sm1FQQ==",
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 5450,
"max_score": null,
"hits": []
}
}
清除游标
http://x.x.x.x:9200/_search/scroll?scroll_id={scroll_id} DELETE
Search After模式
Pagination of results can be done by using the from and size but the cost becomes prohibitive when the deep pagination is reached. The index.max_result_window which defaults to 10,000 is a safeguard, search requests take heap memory and time proportional to from + size. The Scroll api is recommended for efficient deep scrolling but scroll contexts are costly and it is not recommended to use it for real time user requests. The search_after parameter circumvents this problem by providing a live cursor. The idea is to use the results from the previous page to help the retrieval of the next page. --摘自官网 最佳解释。提升逼格
针对深分页和快照模式的游标分页的试用场景。针对数据fetch不太多,而且集群规模不大的的情况 可以试用深分页,针对数据要求实时性不是很高的情况,可以使用scroll模式,但是需要实时性比较高的情况,就需要使用Elasticsearch提供的第三种分页模式了。该模式需要配合排序字段来使用,下一次的查询使用活动的光标去fetch下一页的数据。也就是说需要把第一次返回文档的最后一个排序字段的sort结果放在search_after
节点中。和游标查询每次传输scroll_id的方式类似,但是search_after的方式可以感知最新的文档结果,也就说改分页查询是实时的。使用Search After查询模式。from不设置,或者设置为0或者-1
http://x.x.x.x:9200/my_index/order/_search post
{
"size": 10,
"sort": [
{
"id": "desc"
}
],
"_source": [
"id"
],
"search_after": [
1000040592//该值为上一次查询的最后一个文档的sort的值
],
"query": {
"bool": {
"must_not": [],
"must": [
{
"bool": {
"must": [
{
"term": {
"status": "SUCCEEDED"
}
}
]
}
},
{
"bool": {
"should": []
}
}
]
}
}
}