65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2017 年 4 月 3 日

CPOL

4分钟阅读

viewsIcon

32823

使用 Painless 脚本处理数据

引言

我又来分享 Elasticsearch 的新体验了!

不久前,我被指派提出一个解决方案,用于根据一对日期字段计算分桶。起初,我心想,“嗯,小菜一碟!网上有很多资料。

直到我意识到,对于这个特定场景,我必须使用一些动态脚本来生成此聚合。

虽然确实可以在论坛上找到大量的资料和讨论,但有些细微之处是我在编写代码时才学到的。这是开发过程中一贯的艰辛!

闲话少说,直接开始吧!

需求

让我们假设您有以下表格作为数据源

Data source

您的应用程序必须能够计算“START DATE”和“CLOSED DATE”之间的差异,将结果汇总到以下分桶中

日期差(以 **分钟** 为单位)

  • 0 到 30 分钟
  • 31 到 60 分钟
  • 大于 1 小时

日期差(以 **小时** 为单位)

  • 0 到 2 小时
  • 2 到 24 小时
  • 大于 1 天

日期差(以 **天** 为单位)

  • 0 到 3 天
  • 3 到 7 天
  • 大于一周

“小菜一碟,对吧?”

创建索引

在我之前的文章中,我已经演示了如何创建和映射新索引。不过,自从那时以来,一些事情发生了变化。现在,使用 Elastic 5,您应该按照以下步骤操作

  1. 创建您的索引结构(映射)
    PUT logs 
    {
      "mappings": {
        "userlog": {
          "properties": {
            "name": {
              "properties": {
                "ASSIGNEE": {
                  "type": "text"
                }
              }
            },
            "START DATE": {
              "type": "date"
            },
            "CLOSED DATE": {
              "type": "date"
            }
          }
        }
      }
    }
  2. 添加一些文档(与上面表格中的数据相同)
    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"}
  3. 如果一切顺利,您应该能够使用此命令搜索您刚刚插入的文档(行)
    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”值。” 在创建这些分桶时,请牢记这一点。

这就是前进的方向

  1. 计算日期差。
  2. 根据您的需要格式化结果。
  3. 将它们分到分桶中。

因此,要按 **小时** 获取分桶,查询将是

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) "
                  }
                }
              }
            ]
          }
        }
      ]
    }
  }
}

如您所见,此命令与之前的命令在两个方面有所不同

  1. 它不是一个聚合请求,我们纯粹在获取原始数据。
  2. Painless 脚本已调整为选择满足我们设定的标准的文档(“表达式结果” **>= 零 AND** “表达式结果” **<= 3**)。

结论

Elasticsearch 在每个新版本中都在不断创新,开发社区也在不断壮大。未来,他们将移除第三方语言,只保留 Painless。

到目前为止,它已被证明与前任一样高效。

我展示的这种技术非常健壮和灵活。特别是在需要处理基于日期(或常规)计算和分桶的高级报告时。这是一个常见的需求。

上面的示例只是开始,我很快会分享更多。

下次见!

历史

  • 2017 年 4 月 3 日:初始版本
© . All rights reserved.