警告:此功能处于技术预览阶段,可能会在未来版本中更改或删除。 Elastic 将尽最大努力解决任何问题,但技术预览版中的功能不受官方 GA 功能的支持 SLA 的约束。在 Elastic Stack 8.11 中已经提供预览版。
目录
Elasticsearch 查询语言 (ES|QL) 是一种支持迭代探索数据的查询语言。
ES|QL 查询由一系列由管道分隔的命令组成。 每个查询都以源命令(FROM, ROW, SHOW <item>)开始。 源命令会生成一个表,通常包含来自 Elasticsearch 的数据。
源命令后面可以跟一个或多个处理命令。 处理命令通过添加、删除或更改行和列来更改输入表。
你可以链接处理命令,并用竖线字符分隔:|。 每个处理命令都作用于前一个命令的输出表。
查询的结果是最终处理命令生成的表。
运行 ES|QL 查询
ES|QL API
使用 _query 端点运行 ES|QL 查询:
POST /_query
{
"query": """
FROM library
| EVAL year = DATE_TRUNC(1 YEARS, release_date)
| STATS MAX(page_count) BY year
| SORT year
| LIMIT 5
"""
}
结果按行返回:
{
"columns": [
{ "name": "MAX(page_count)", "type": "integer"},
{ "name": "year" , "type": "date"}
],
"values": [
[268, "1932-01-01T00:00:00.000Z"],
[224, "1951-01-01T00:00:00.000Z"],
[227, "1953-01-01T00:00:00.000Z"],
[335, "1959-01-01T00:00:00.000Z"],
[604, "1965-01-01T00:00:00.000Z"]
]
}
默认情况下,结果以 JSON 形式返回。 要返回文本、CSV 或 TSV 格式的结果,请使用 format 参数:
POST /_query?format=txt
{
"query": """
FROM library
| EVAL year = DATE_TRUNC(1 YEARS, release_date)
| STATS MAX(page_count) BY year
| SORT year
| LIMIT 5
"""
}
上述查询的 LIMIT 命令将结果限制为 5 行。
如果未指定,LIMIT 默认为 500。无论 LIMIT 值如何,单个查询都不会返回超过 10,000 行。
Kibana
在 Discover 中使用 ES|QL 探索数据集。 从数据视图下拉列表中,选择 Try ES|QL 开始。
注意:Discover 和 Lens 中的 ES|QL 查询受时间过滤器选择的时间范围的限制。
限制
- ES|QL 目前支持以下字段类型:
- alias
- boolean
- data
- double(float、half_float、scaled_float 表示为 double)
- ip
- keyword 系列,包括 keyword、constant_keyword 和 wildcard
- int(short 和 byte 均表示为 int)
- long
- null
- text
- unsigned_long
- version
- 无论 LIMIT 命令的值如何,单个查询都不会返回超过 10,000 行。
ES|QL 语法参考
基本语法
ES|QL 查询由一个源命令组成,后跟一系列可选的处理命令,并用竖线字符分隔:|。 例如:
source-command
| processing-command1
| processing-command2
查询的结果是最终处理命令生成的表。
为了便于阅读,本文档将每个处理命令放在一个新行中。 但是,你可以将 ES|QL 查询编写为一行。 以下查询与前一个查询相同:
source-command | processing-command1 | processing-command2
注释
ES|QL 使用 C++ 风格的注释:
- 双斜杠 // 用于单行注释
- /* 和 */ 用于块注释
// Query the employees index
FROM employees
| WHERE height > 2
FROM /* Query the employees index */ employees
| WHERE height > 2
FROM employees
/* Query the
* employees
* index */
| WHERE height > 2
运算符
支持以下二进制比较运算符:
- 平等:==
- 不等式:!=
- 小于:<
- 小于或等于:<=
- 大于:>
- 大于或等于:>=
IN 运算符允许测试字段或表达式是否等于文字 (literals)、字段 (fields) 或表达式 (expressions) 列表中的元素:
ROW a = 1, b = 4, c = 3
| WHERE c-a IN (3, b / 2, a)
对于使用通配符或正则表达式的字符串比较,请使用 LIKE 或 RLIKE:
- 使用 LIKE 来匹配使用通配符的字符串。 支持以下通配符:
- * 匹配零个或多个字符。
- ? 匹配一个字符。
FROM employees
| WHERE first_name LIKE "?b*"
| KEEP first_name, last_name
- 使用 RLIKE 使用正则表达式来匹配字符串:
FROM employees
| WHERE first_name RLIKE ".leja.*"
| KEEP first_name, last_name
支持以下布尔运算符:
- AND
- OR
- NOT
Predicates - 谓词
对于 NULL 比较,请使用 IS NULL 和 IS NOT NULL 谓词:
FROM employees
| WHERE birth_date IS NULL
| KEEP first_name, last_name
| SORT first_name
| LIMIT 3
first_name:keyword | last_name:keyword |
---|---|
Basil | Tramer |
Florian | Syrotiuk |
Lucien | Rosenbaum |
FROM employees
| WHERE is_rehired IS NOT NULL
| STATS count(emp_no)
count(emp_no):long |
---|
84 |
Timespan literals
日期时间间隔和时间跨度可以使用时间跨度文字来表示。 时间跨度文字是数字和限定符的组合。 支持这些限定符:
millisecond
/milliseconds
second
/seconds
minute
/minutes
hour
/hours
day
/days
week
/weeks
month
/months
year
/years
时间跨度文字对空格不敏感。 这些表达式都是有效的:
1day
1 day
1 day
ES|QL 源命令
ES|QL 源命令会生成一个表,通常包含来自 Elasticsearch 的数据。
ES|QL 支持以下源命令:
- FROM
- ROW
- SHOW <item>
FROM
FROM source 命令返回一个表,其中包含来自数据流、索引或别名的最多 10,000 个文档。 结果表中的每一行代表一个文档。 每列对应一个字段,并且可以通过该字段的名称进行访问。
FROM employees
你可以使用 date math 来引用索引、别名和数据流。 这对于时间序列数据很有用,例如访问今天的索引:
FROM <logs-{now/d}>
使用逗号分隔的列表或通配符查询多个数据流、索引或别名:
FROM employees-00001,other-employees-*
使用 METADATA 指令启用元数据字段:
FROM employees [METADATA _id]
ROW
ROW source 命令生成一行,其中包含一个或多个列,这些列具有你指定的值。 这对于测试很有用。
ROW a = 1, b = "two", c = null
a:integer | b:keyword | c:null |
---|---|---|
1 | "two" | null |
使用方括号创建多值列:
ROW a = [2, 1]
ROW 支持使用函数:
ROW a = ROUND(1.23, 0)
SHOW <item>
SHOW <item> source 命令返回有关部署及其功能的信息:
- 使用 SHOW INFO 返回部署的版本、构建日期和哈希值。
- 使用 SHOW FUNCTIONS 返回所有支持的函数的列表以及每个函数的概要。
ES|QL 处理命令
ES|QL 处理命令通过添加、删除或更改行和列来更改输入表。
ES|QL 支持这些处理命令:
- DISSECT
- DROP
- ENRICH
- EVAL
- GROK
- KEEP
- LIMIT
- MV_EXPAND
- RENAME
- SORT
- STATS ... BY
- WHERE
DISSECT
DISSECT 使你能够从字符串中提取结构化数据。 DISSECT 将字符串与基于分隔符的模式进行匹配,并将指定的键提取为列。
有关 dissect 模式的语法,请参阅 dissect processor 文档。
ROW a = "1953-01-23T12:15:00Z - some text - 127.0.0.1;"
| DISSECT a "%{Y}-%{M}-%{D}T%{h}:%{m}:%{s}Z - %{msg} - %{ip};"
| KEEP Y, M, D, h, m, s, msg, ip
Y:keyword | M:keyword | D:keyword | h:keyword | m:keyword | s:keyword | msg:keyword | ip:keyword |
---|---|---|---|---|---|---|---|
1953 | 01 | 23 | 12 | 15 | 00 |
DROP
使用 DROP 删除列:
FROM employees
| DROP height
你可以使用通配符删除名称与模式匹配的所有列,而不是按名称指定每个列:
FROM employees
| DROP height*
ENRICH
你可以使用 ENRICH 将现有索引中的数据添加到传入记录中。 它与 ingest enrich 类似,但它在查询时工作。
ROW language_code = "1"
| ENRICH languages_policy
language_code:keyword | language_name:keyword |
---|---|
1 | English |
ENRICH 需要执行 enrich policy。 丰富策略定义了一个匹配字段(关键字段)和一组丰富字段。
ENRICH 将根据匹配字段值在 enrich index 中查找记录。 输入数据集中的匹配键可以使用 ON <field-name> 定义; 如果未指定,则将在与 enrich policy 中定义的匹配字段同名的字段上执行匹配。
ROW a = "1"
| ENRICH languages_policy ON a
a:keyword | language_name:keyword |
---|---|
1 | English |
你可以使用 WITH <field1>, <field2>... 语法指定必须将哪些属性(在策略中定义为丰富字段的属性之间)添加到结果中。
ROW a = "1"
| ENRICH languages_policy ON a WITH language_name
a:keyword | language_name:keyword |
---|---|
1 | English |
还可以使用 WITH new_name=<field1> 重命名属性
ROW a = "1"
| ENRICH languages_policy ON a WITH name = language_name
a:keyword | name:keyword |
---|---|
1 | English |
默认情况下(如果未定义 WITH),ENRICH 会将 enrich policy 中定义的所有丰富字段添加到结果中。
如果发生名称冲突,新创建的字段将覆盖现有字段。
EVAL
EVAL 使你能够附加新列:
FROM employees
| SORT emp_no
| KEEP first_name, last_name, height
| EVAL height_feet = height * 3.281, height_cm = height * 100
first_name:keyword | last_name:keyword | height:double | height_feet:double | height_cm:double |
---|---|---|---|---|
Georgi | Facello | 2.03 | 6.66043 | 202.99999999999997 |
如果指定的列已存在,则现有列将被删除,新列将追加到表中:
FROM employees
| SORT emp_no
| KEEP first_name, last_name, height
| EVAL height = height * 3.281
first_name:keyword | last_name:keyword | height:double |
---|---|---|
Georgi | Facello | 6.66043 |
Functions
EVAL 支持各种计算值的函数。 请参阅函数了解更多信息。
GROK
GROK 使你能够从字符串中提取结构化数据。 GROK 基于正则表达式将字符串与模式进行匹配,并将指定的模式提取为列。
有关 grok 模式的语法,请参阅 grok 处理器文档。
例如:
ROW a = "1953-01-23T12:15:00Z 127.0.0.1 some.email@foo.com 42"
| GROK a "%{TIMESTAMP_ISO8601:date} %{IP:ip} %{EMAILADDRESS:email} %{NUMBER:num:int}"
| KEEP date, ip, email, num
date:keyword | ip:keyword | email:keyword | num:integer |
---|---|---|---|
1953-01-23T12:15:00Z | 127.0.0.1 | 42 |
KEEP
KEEP 命令使你能够指定返回哪些列以及返回它们的顺序。
要限制返回的列,请使用以逗号分隔的列名称列表。 列按指定顺序返回:
FROM employees
| KEEP emp_no, first_name, last_name, height
emp_no:integer | first_name:keyword | last_name:keyword | height:double |
---|---|---|---|
10001 | Georgi | Facello | 2.03 |
10002 | Bezalel | Simmel | 2.08 |
10003 | Parto | Bamford | 1.83 |
10004 | Chirstian | Koblick | 1.78 |
10005 | Kyoichi | Maliniak | 2.05 |
你可以使用通配符返回名称与模式匹配的所有列,而不是按名称指定每个列:
FROM employees
| KEEP h*
星号通配符 (*) 本身会转换为与其他参数不匹配的所有列。 此查询将首先返回名称以 h 开头的所有列,然后是所有其他列:
FROM employees
| KEEP h*, *
LIMIT
语法:
LIMIT max_number_of_rows
参数:
max_number_of_rows: 要返回的最大行数。
描述:
LIMIT 处理命令使您能够限制返回的行数。 无论 LIMIT 命令的值如何,查询都不会返回超过 10,000 行。
此限制仅适用于查询检索的行数。 查询和聚合在完整数据集上运行。
为了克服这个限制:
- 通过修改查询以仅返回相关数据来减少结果集大小。 使用 WHERE 选择数据的较小子集。
- 将任何查询后处理转移到查询本身。 你可以使用 ES|QL STATS ... BY 命令来聚合查询中的数据。
可以使用这些动态集群设置更改默认和最大限制:
esql.query.result_truncation_default_size
esql.query.result_truncation_max_size
LIMIT 处理命令使你能够限制行数:
FROM employees
| SORT emp_no ASC
| LIMIT 5
如果未指定,LIMIT 默认为 500。无论 LIMIT 值如何,单个查询都不会返回超过 10,000 行。
MV_EXPAND
MV_EXPAND 处理命令将多值(multivalued)字段扩展为每个值一行,并复制其他字段:
ROW a=[1,2,3], b="b", j=["a","b"]
| MV_EXPAND a
a:integer | b:keyword | j:keyword |
---|---|---|
1 | b | ["a", "b"] |
2 | b | ["a", "b"] |
3 | b | ["a", "b"] |
RENAME
使用 RENAME 使用以下语法重命名列:
RENAME <old-name> AS <new-name>
例如:
FROM employees
| KEEP first_name, last_name, still_hired
| RENAME still_hired AS employed
如果具有新名称的列已存在,它将被新列替换。
可以使用单个 RENAME 命令重命名多个列:
FROM employees
| KEEP first_name, last_name
| RENAME first_name AS fn, last_name AS ln
SORT
使用 SORT 命令对一个或多个字段上的行进行排序:
FROM employees
| KEEP first_name, last_name, height
| SORT height
默认排序顺序为升序。 使用 ASC 或 DESC 设置显式排序顺序:
FROM employees
| KEEP first_name, last_name, height
| SORT height DESC
具有相同排序键的两行被视为相等。 你可以提供额外的排序表达式来分裁定:
FROM employees
| KEEP first_name, last_name, height
| SORT height DESC, first_name ASC
null
values
默认情况下,null 值被视为大于任何其他值。 对于升序排序,空值排在最后,而对于降序排序,空值排在最前面。 你可以通过提供 NULLS FIRST 或 NULLS LAST 来更改它:
FROM employees
| KEEP first_name, last_name, height
| SORT first_name ASC NULLS FIRST
STATS ... BY
使用 STATS ... BY 根据公共值对行进行分组,并计算分组行上的一个或多个聚合值。
FROM employees
| STATS count = COUNT(emp_no) BY languages
| SORT languages
count:long | languages:integer |
---|---|
15 | 1 |
19 | 2 |
17 | 3 |
18 | 4 |
21 | 5 |
10 | null |
如果省略 BY,则输出表仅包含一行,并且聚合应用于整个数据集:
FROM employees
| STATS avg_lang = AVG(languages)
avg_lang:double |
---|
3.1222222222222222 |
可以计算多个值:
FROM employees
| STATS avg_lang = AVG(languages), max_lang = MAX(languages)
还可以按多个值进行分组(仅支持 long 字段和 keyword 族字段):
FROM employees
| EVAL hired = DATE_FORMAT("YYYY", hire_date)
| STATS avg_salary = AVG(salary) BY hired, languages.long
| EVAL avg_salary = ROUND(avg_salary)
| SORT hired, languages.long
支持以下聚合函数:
注意:没有任何 group 的 STATS 比添加 group 快得多。
注意:目前,对单列进行分组比对多列进行分组要优化得多。 在一些测试中,我们发现对单个关键字列进行分组比对两个关键字列进行分组要快五倍。 不要尝试通过使用 CONCAT 之类的方法将两列组合在一起然后分组来解决此问题 - 这不会更快。
例子
计算统计数据并按另一列的值进行分组:
FROM employees
| STATS count = COUNT(emp_no) BY languages
| SORT languages
count:long | languages:integer |
---|---|
15 | 1 |
19 | 2 |
17 | 3 |
18 | 4 |
21 | 5 |
10 | null |
省略 BY 返回一行,并将聚合应用于整个数据集:
FROM employees
| STATS avg_lang = AVG(languages)
avg_lang:double
3.1222222222222222
可以计算多个值:
FROM employees
| STATS avg_lang = AVG(languages), max_lang = MAX(languages)
还可以按多个值进行分组(仅支持 long 字段和 keyword 系列字段):
FROM employees
| EVAL hired = DATE_FORMAT("YYYY", hire_date)
| STATS avg_salary = AVG(salary) BY hired, languages.long
| EVAL avg_salary = ROUND(avg_salary)
| SORT hired, languages.long
WHERE
语法:
WHERE expression
参数:
expression: 布尔表达式。
描述:
WHERE 处理命令生成一个表,其中包含输入表中所提供的条件计算结果为 true 的所有行。
使用 WHERE 生成一个表,其中包含输入表中所提供的条件评估为 true 的所有行:
FROM employees
| KEEP first_name, last_name, still_hired
| WHERE still_hired == true
如果 still_hired 是布尔字段,则可以简化为:
FROM employees
| KEEP first_name, last_name, still_hired
| WHERE still_hired
使用函数:
FROM employees
| KEEP first_name, last_name, height
| WHERE LENGTH(first_name) < 4
运算符
有关支持的运算符的概述,请参阅上面的运算符部分。
函数
WHERE 支持各种计算值的函数。 请参阅函数了解更多信息。
FROM employees
| KEEP first_name, last_name, height
| WHERE length(first_name) < 4
ES|QL例子
聚合和丰富 Windows 事件日志
FROM logs-*
| WHERE event.code IS NOT NULL
| STATS event_code_count = count(event.code) by event.code,host.name
| ENRICH win_events on event.code with event_description
| WHERE event_description IS NOT NULL and host.name IS NOT NULL
| RENAME event_description as event.description
| SORT event_code_count desc
| KEEP event_code_count,event.code,host.name,event.description
- 它首先从与模式 “logs-*” 匹配的索引中查询日志。
- 过滤 “event.code” 字段不为空的事件。
- 按 “event.code” 和 “host.name” 聚合事件计数。
- 使用 “EVENT_DESCRIPTION” 字段丰富事件的附加信息。
- 过滤掉 “EVENT_DESCRIPTION” 或 “host.name” 为空的事件。
- 将 “EVENT_DESCRIPTION” 重命名为 “event.description”。
- 按 “event_code_count” 降序对结果进行排序。
- 仅保留选定的字段:“event_code_count”、“event.code”、“host.name” 和 “event.description”。
对来自进程 curl.exe 的出站流量求和
FROM logs-endpoint
| WHERE process.name == "curl.exe"
| STATS bytes = SUM(destination.bytes) BY destination.address
| EVAL kb = bytes/1024
| SORT kb desc
| LIMIT 10
| KEEP kb,destination.address
- 从 “logs-endpoint” 源查询日志。
- 过滤 “process.name” 字段为 “curl.exe” 的事件。
- 计算发送到目标地址的字节总和并将其转换为千字节 (KB)。
- 按 “kb”(千字节)降序对结果进行排序。
- 将输出限制为前 10 个结果。
- 仅保留 “kb” 和 “destination.address” 字段。
操纵 DNS 日志以查找每个注册域的大量唯一 dns 查询
FROM logs-*
| GROK dns.question.name "%{DATA}\\.%{GREEDYDATA:dns.question.registered_domain:string}"
| STATS unique_queries = count_distinct(dns.question.name) by dns.question.registered_domain, process.name
| WHERE unique_queries > 10
| SORT unique_queries DESC
| RENAME unique_queries AS `Unique Queries`, dns.question.registered_domain AS `Registered Domain`, process.name AS `Process`
- 从匹配 “logs-*” 的索引中查询日志。
- 使用 “grok” 模式从 “dns.question.name” 字段中提取注册域。
- 计算每个注册域和进程名称的唯一 DNS 查询计数。
- 过滤 “unique_queries” 大于 10 的结果。
- 按 “unique_queries” 降序对结果进行排序。
- 为了清晰起见,将字段重命名为:“unique_queries”为“Unique Queries”,“dns.question.registered_domain”为“Registered Domain”,“process.name” 为“Process”。
识别大量出站用户连接
FROM logs-*
| WHERE NOT CIDR_MATCH(destination.ip, "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16")
| STATS destcount = COUNT(destination.ip) BY user.name, host.name
| ENRICH ldap_lookup_new ON user.name
| WHERE group.name IS NOT NULL
| EVAL follow_up = CASE(destcount >= 100, "true","false")
| SORT destcount desc
| KEEP destcount, host.name, user.name, group.name, follow_up
- 从匹配 “logs-*” 的索引中查询日志。
- 过滤掉目标 IP 地址属于私有 IP 地址范围(例如 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)的事件。
- 按 “user.name” 和 “host.name” 计算唯一目标 IP 的计数。
- 使用 LDAP 组信息丰富 “user.name” 字段。
- 过滤掉 “group.name” 不为空的结果。
- 使用 “CASE” 语句创建 “follow_up” 字段,当 “destcount” 大于或等于 100 时将其设置为 “true”,否则设置为 “false”。
- 按 “destcount” 降序对结果进行排序。
- 保留选定字段:“destcount”、“host.name”、“user.name”、“group.name” 和 “follow_up”。