使用 Painless 脚本计算日期并在 Elasticsearch 中生成存储桶





5.00/5 (3投票s)
使用 Painless 脚本处理数据
引言
我又来分享 Elasticsearch 的新体验了!
不久前,我被指派提出一个解决方案,用于根据一对日期字段计算分桶。起初,我心想,“嗯,小菜一碟!网上有很多资料。”
直到我意识到,对于这个特定场景,我必须使用一些动态脚本来生成此聚合。
虽然确实可以在论坛上找到大量的资料和讨论,但有些细微之处是我在编写代码时才学到的。这是开发过程中一贯的艰辛!
闲话少说,直接开始吧!
需求
让我们假设您有以下表格作为数据源
您的应用程序必须能够计算“START DATE
”和“CLOSED DATE
”之间的差异,并将结果汇总到以下分桶中
日期差(以 **分钟** 为单位)
- 0 到 30 分钟
- 31 到 60 分钟
- 大于 1 小时
日期差(以 **小时** 为单位)
- 0 到 2 小时
- 2 到 24 小时
- 大于 1 天
日期差(以 **天** 为单位)
- 0 到 3 天
- 3 到 7 天
- 大于一周
“小菜一碟,对吧?”
创建索引
在我之前的文章中,我已经演示了如何创建和映射新索引。不过,自从那时以来,一些事情发生了变化。现在,使用 Elastic 5,您应该按照以下步骤操作
- 创建您的索引结构(映射)
PUT logs { "mappings": { "userlog": { "properties": { "name": { "properties": { "ASSIGNEE": { "type": "text" } } }, "START DATE": { "type": "date" }, "CLOSED DATE": { "type": "date" } } } } }
- 添加一些文档(与上面表格中的数据相同)
PUT logs/userlog/_bulk?refresh {"index":{"_id":1}} {"ASSIGNEE":"Person AA","START DATE":"2016-01-01T14:00:56z", "CLOSED DATE":"2016-01-10T14:00:50z"} {"index":{"_id":2}} {"ASSIGNEE":"Person CC","START DATE":"2016-01-09T15:01:52z", "CLOSED DATE":"2016-01-19T15:45:41z"} {"index":{"_id":3}} {"ASSIGNEE":"Person BB","START DATE":"2016-01-09T15:01:53z", "CLOSED DATE":"2016-01-20T16:50:41z"} {"index":{"_id":4}} {"ASSIGNEE":"Person BB","START DATE":"2016-01-09T15:01:53z", "CLOSED DATE":"2016-01-19T16:00:41z"} {"index":{"_id":5}} {"ASSIGNEE":"Person CC","START DATE":"2016-01-09T15:01:53z", "CLOSED DATE":"2016-01-19T15:30:41z"} {"index":{"_id":6}} {"ASSIGNEE":"Person EE","START DATE":"2016-01-09T15:01:54z", "CLOSED DATE":"2016-01-09T17:00:41z"} {"index":{"_id":7}} {"ASSIGNEE":"Person EE","START DATE":"2016-01-09T15:01:55z", "CLOSED DATE":"2016-01-19T15:00:41z"} {"index":{"_id":8}} {"ASSIGNEE":"Person CC","START DATE":"2016-01-09T15:01:55z", "CLOSED DATE":"2016-01-10T17:00:41z"} {"index":{"_id":9}} {"ASSIGNEE":"Person AA","START DATE":"2016-01-09T15:01:56z", "CLOSED DATE":"2016-01-19T15:30:41z"} {"index":{"_id":10}} {"ASSIGNEE":"Person BB","START DATE":"2016-01-09T15:01:56z", "CLOSED DATE":"2016-01-19T15:00:41z"} {"index":{"_id":11}} {"ASSIGNEE":"Person BB","START DATE":"2016-01-09T15:01:56z", "CLOSED DATE":"2016-01-19T15:00:41z"} {"index":{"_id":12}} {"ASSIGNEE":"Person BB","START DATE":"2016-01-09T15:01:57z", "CLOSED DATE":"2016-01-19T15:00:41z"} {"index":{"_id":13}} {"ASSIGNEE":"Person AA","START DATE":"2016-01-09T15:01:57z", "CLOSED DATE":"2016-01-10T14:00:50z"} {"index":{"_id":14}} {"ASSIGNEE":"Person EE","START DATE":"2016-01-09T15:01:58z", "CLOSED DATE":"2016-01-19T15:00:41z"} {"index":{"_id":15}} {"ASSIGNEE":"Person CC","START DATE":"2016-01-09T15:01:58z", "CLOSED DATE":"2016-01-10T14:00:50z"} {"index":{"_id":16}} {"ASSIGNEE":"Person BB","START DATE":"2016-01-09T15:01:59z", "CLOSED DATE":"2016-01-19T15:00:41z"} {"index":{"_id":17}} {"ASSIGNEE":"Person AA","START DATE":"2016-01-09T15:01:59z", "CLOSED DATE":"2016-01-09T15:45:41z"} {"index":{"_id":18}} {"ASSIGNEE":"Person EE","START DATE":"2016-01-09T15:02:00z", "CLOSED DATE":"2016-01-19T15:00:41z"} {"index":{"_id":19}} {"ASSIGNEE":"Person BB","START DATE":"2016-01-09T15:02:00z", "CLOSED DATE":"2016-01-10T14:00:50z"} {"index":{"_id":20}} {"ASSIGNEE":"Person CC","START DATE":"2016-01-09T15:02:00z", "CLOSED DATE":"2016-01-20T15:00:41z"} {"index":{"_id":21}} {"ASSIGNEE":"Person AA","START DATE":"2016-01-09T15:02:01z", "CLOSED DATE":"2016-01-19T15:30:41z"} {"index":{"_id":22}} {"ASSIGNEE":"Person BB","START DATE":"2016-01-09T15:02:01z", "CLOSED DATE":"2016-01-10T14:00:50z"} {"index":{"_id":23}} {"ASSIGNEE":"Person CC","START DATE":"2016-01-09T15:02:02z", "CLOSED DATE":"2016-02-01T15:00:41z"} {"index":{"_id":24}} {"ASSIGNEE":"Person AA","START DATE":"2016-01-09T15:02:02z", "CLOSED DATE":"2016-01-19T15:00:41z"} {"index":{"_id":25}} {"ASSIGNEE":"Person BB","START DATE":"2016-01-09T15:02:03z", "CLOSED DATE":"2016-01-09T20:00:41z"}
- 如果一切顺利,您应该能够使用此命令搜索您刚刚插入的文档(行)
GET logs/userlog/_search
介绍 Painless 脚本
正如我提到的,对于这个演示,我使用的是最新的 Elastic 版本。这对社区来说是一个重大事件,Elastic 公司对产品进行了很多更改,其中一项更改是引入了一种名为 *Painless* 的新脚本语言。
这并非意味着我们以前无法使用自定义脚本。Elastic 在产品中嵌入了几种第三方语言。我个人与 Groovy 相处得很好。
然而,我的这篇文章基于最新的版本。因此,如果您不使用 Painless,即使这些语言相当相似,您也必须预料到它们之间会存在细微的差异。
生成范围分桶
直奔主题,这是按 **分钟** 计算范围的查询
GET logs/userlog/_search
{
"size": 0,
"aggs": {
"groupby": {
"range": {
"script": {
"inline": "((doc['CLOSED DATE'].value - doc['START DATE'].value) / (3600000.0/60))"
},
"ranges": [
{
"from": 0.0,
"to": 30.0,
"key": "From 0 to 30 minutes"
},
{
"from": 30.0,
"to": 60.0,
"key": "From 30 to 60 minutes"
},
{
"from": 60.0,
"key": "Greater than 1 hour"
}
]
}
}
}
}
解释
首先,我们计算日期差(doc['CLOSED DATE'].value - doc['START DATE'].value
),并除以该表达式的结果,以便将其格式化为分钟(3600000.0/60)。
在后台,每个文档(行)都将以这种方式计算,得到一个分钟数。这些分钟数将通过范围聚合分组到分桶中。
Elastic 页面上的一条重要说明(如果您错过了):“请注意,此聚合包含每个范围的“from”值,但不包含“to”值。” 在创建这些分桶时,请牢记这一点。
这就是前进的方向
- 计算日期差。
- 根据您的需要格式化结果。
- 将它们分到分桶中。
因此,要按 **小时** 获取分桶,查询将是
GET logs/userlog/_search
{
"size": 0,
"aggs": {
"groupby": {
"range": {
"script": {
"inline": "((doc['CLOSED DATE'].value - doc['START DATE'].value) / 3600000.0)"
},
"ranges": [
{
"from": 0.0,
"to": 2.0,
"key": "From 0 to 2 hours"
},
{
"from": 2.0,
"to": 24.0,
"key": "From 2 to 24 hours"
},
{
"from": 24.0,
"key": "Greater than 1 day"
}
]
}
}
}
}
最后是按 **天**
GET logs/userlog/_search
{
"size": 0,
"aggs": {
"groupby": {
"range": {
"script": {
"inline": "((doc['CLOSED DATE'].value - doc['START DATE'].value) / (3600000.0*24))"
},
"ranges": [
{
"from": 0.0,
"to": 3.0,
"key": "From 0 to 3 days"
},
{
"from": 3.0,
"to": 7.0,
"key": "From 3 to 7 days"
},
{
"from": 7.0,
"key": "Greater than a week"
}
]
}
}
}
}
奖励:通过范围搜索原始数据
如果我告诉您,我们可以获取以上所有这些漂亮分桶的行怎么办?正如您所料,解决方案需要更多的 Painless 脚本!
例如,要能够获取所有符合以下条件的行
A:日期差以 **天** 为单位。B:差值必须在 **0 到 3** 天之间。
查询将是
GET logs/userlog/_search
{
"from": 0,
"size": 100,
"query": {
"bool": {
"filter": [
{
"bool": {
"filter": [
{
"script": {
"script": {
"inline": " (((doc['CLOSED DATE'].value - doc['START DATE'].value) /
(3600000.0*24)) >= 0 && ((doc['CLOSED DATE'].value -
doc['START DATE'].value) / (3600000.0*24)) < 3) "
}
}
}
]
}
}
]
}
}
}
如您所见,此命令与之前的命令在两个方面有所不同
- 它不是一个聚合请求,我们纯粹在获取原始数据。
- Painless 脚本已调整为选择满足我们设定的标准的文档(“表达式结果” **>= 零 AND** “表达式结果” **<= 3**)。
结论
Elasticsearch 在每个新版本中都在不断创新,开发社区也在不断壮大。未来,他们将移除第三方语言,只保留 Painless。
到目前为止,它已被证明与前任一样高效。
我展示的这种技术非常健壮和灵活。特别是在需要处理基于日期(或常规)计算和分桶的高级报告时。这是一个常见的需求。
上面的示例只是开始,我很快会分享更多。
下次见!
历史
- 2017 年 4 月 3 日:初始版本