作者:来自 Elastic Margaret Gu
探索用于 JavaScript 和 TypeScript 的 ES|QL 查询构建器,并通过实际示例讲解如何构建 ES|QL 查询。
开始动手使用 Elasticsearch:可以浏览 Elasticsearch Labs 仓库中的示例 notebooks,启动免费的云端试用,或在本地机器上直接体验 Elastic。
我们很高兴地宣布,Elasticsearch Query Language( ES|QL )查询构建器现已支持 JavaScript 和 TypeScript。它是一个流式(fluent)、类型安全的库,可以通过方法链式调用来构建 ES|QL 查询,并具备自动值转义以及完整的 IDE 支持;不再需要手动拼接原始字符串。
通过实际示例学习如何立即上手使用。
JavaScript 和 TypeScript 的 ES|QL 查询构建器
如果你曾在 JavaScript 中构建过 ES|QL 查询,你可能写过类似这样的代码:
``
1. const query = `FROM logs-*
2. | WHERE status_code >= ${minStatus}
3. AND host.name == ${hostname}
4. AND @timestamp >= "${startDate}"
5. | STATS error_count = COUNT(*) BY status_code
6. | SORT error_count DESC
7. | LIMIT 10`
``AI写代码
它看起来没问题,直到 hostname 变成 O'Brien's server,然后整个查询因为解析错误直接崩掉。或者直到某个用户在搜索字段里传入 "; DROP INDEX logs,你才意识到你一直在用字符串拼接来构建查询。
有更好的方式。JavaScript 和 TypeScript 的 ES|QL 查询构建器可以让你改成这样写查询:
`
1. import { ESQL, E, f } from '@elastic/elasticsearch-esql-dsl'
3. const query = ESQL.from('logs-*')
4. .where(E('status_code').gte(minStatus))
5. .where(E('host.name').eq(hostname))
6. .where(E('@timestamp').gte(startDate))
7. .stats({ error_count: f.count() })
8. .by('status_code')
9. .sort(E('error_count').desc())
10. .limit(10)
`AI写代码
值会被自动转义(escaped)。你可以在编辑器中获得自动补全(autocomplete),并且无需再“脑内解析”模板字符串,就能清晰理解查询实际在做什么。
ES|QL 查询构建器已经在 Elastic 的多种语言客户端中提供支持,包括 Python、Ruby 等。本篇文章重点介绍 JavaScript 和 TypeScript 版本,并通过一些可以直接上手的实际示例来讲解它的用法。
入门开始
安装该包:
`npm install @elastic/elasticsearch-esql-dsl`AI写代码
这是一个最小查询示例:
`
1. import { ESQL, E } from '@elastic/elasticsearch-esql-dsl'
3. const query = ESQL.from('employees')
4. .where(E('still_hired').eq(true))
5. .sort(E('last_name').asc())
6. .limit(10)
8. console.log(query.render())
`AI写代码
渲染结果如下:
`
1. FROM employees
2. | WHERE still_hired == true
3. | SORT last_name ASC
4. | LIMIT 10
`AI写代码
要在 Elasticsearch 上执行它:
`
1. import { Client } from '@elastic/elasticsearch'
3. const client = new Client({ node: 'http://localhost:9200' })
4. const response = await client.esql.query({ query: query.render() })
`AI写代码
就是这样。没有字符串插值,也不需要手动转义。
一步一步构建真实查询
我们来看一个更贴近实际的场景:你正在构建一个用于分析 Web 服务器错误日志的仪表盘。我们先从简单开始,然后逐步增加功能。
步骤 1:过滤错误日志
`
1. import { ESQL, E } from '@elastic/elasticsearch-esql-dsl'
3. const errors = ESQL.from('logs-*')
4. .where(E('status_code').gte(400))
5. .limit(100)
`AI写代码
`
1. FROM logs-*
2. | WHERE status_code >= 400
3. | LIMIT 100
`AI写代码
步骤 2:添加计算列
你的时间戳是以毫秒为单位,但你希望将响应时间转换为秒:
`
1. const errors = ESQL.from('logs-*')
2. .where(E('status_code').gte(400))
3. .eval({ response_secs: E('response_time_ms').div(1000) })
4. .limit(100)
`AI写代码
`
1. FROM logs-*
2. | WHERE status_code >= 400
3. | EVAL response_secs = response_time_ms / 1000
4. | LIMIT 100
`AI写代码
步骤 3:按状态码聚合错误
`
1. import { f } from '@elastic/elasticsearch-esql-dsl'
3. const errorBreakdown = ESQL.from('logs-*')
4. .where(E('status_code').gte(400))
5. .stats({
6. error_count: f.count(),
7. avg_response: f.avg('response_time_ms'),
8. })
9. .by('status_code')
10. .sort(E('error_count').desc())
`AI写代码
`
1. FROM logs-*
2. | WHERE status_code >= 400
3. | STATS error_count = COUNT(*), avg_response = AVG(response_time_ms) BY status_code
4. | SORT error_count DESC
`AI写代码
这个 f 命名空间提供了 150+ 个 ES|QL 函数封装:包括聚合函数、字符串函数、日期函数、数学函数、地理函数等等。它们都返回可链式调用的表达式,因此可以在任何原本使用 E() 的地方使用。
步骤 4:使用日期函数进行时间维度分析
`
1. const hourlyErrors = ESQL.from('logs-*')
2. .where(E('status_code').gte(400))
3. .eval({ hour: f.dateTrunc('@timestamp', '1 hour') })
4. .stats({ error_count: f.count() })
5. .by('hour')
6. .sort(E('hour'))
`AI写代码
`
1. FROM logs-*
2. | WHERE status_code >= 400
3. | EVAL hour = DATE_TRUNC(@timestamp, "1 hour")
4. | STATS error_count = COUNT(*) BY hour
5. | SORT hour
`AI写代码
步骤 5:安全地分支查询
每个方法都会返回一个新的查询对象,原始查询不会被修改。这意味着你可以先构建一个基础查询,然后为不同的视图进行分支扩展:
`
1. const base = ESQL.from('logs-*')
2. .where(E('status_code').gte(400))
3. .where(E('@timestamp').gte('2026-01-01T00:00:00Z'))
5. const byStatus = base
6. .stats({ count: f.count() })
7. .by('status_code')
8. .sort(E('count').desc())
10. const byHost = base
11. .stats({ count: f.count() })
12. .by('host.name')
13. .sort(E('count').desc())
14. .limit(20)
16. const recent = base
17. .sort(E('@timestamp').desc())
18. .keep('@timestamp', 'status_code', 'url.path', 'message')
19. .limit(50)
`AI写代码
三个不同的查询,共用一个基础查询。修改 base 的过滤条件,三个查询都会同步更新。这在仪表盘场景中特别有用,因为多个面板通常会对同一数据集使用不同的聚合方式。
三种表达式写法
该领域专用语言(DSL)提供了多种编写条件的方式。下面是同一个 WHERE 子句的三种不同写法:
原始字符串(raw strings):适用于快速一次性查询:
`.where('status_code >= 400 AND host.name == "web-01"')`AI写代码
E() 表达式构建器:当你需要类型安全和自动补全时使用:
`
1. import { and_ } from '@elastic/elasticsearch-esql-dsl'
3. .where(and_(
4. E('status_code').gte(400),
5. E('host.name').eq('web-01')
6. ))
`AI写代码
esql 模板标签(template tag):当你需要安全地插入动态值时使用:
``
1. import { esql } from '@elastic/elasticsearch-esql-dsl'
3. const minStatus = 400
4. const host = 'web-01'
5. .where(esql`status_code >= ${minStatus} AND host.name == ${host}`)
``AI写代码
这三种方式最终都会生成相同的 ES|QL 查询。你可以根据具体场景选择合适方式:简单场景用 raw string,程序化构建表达式用 E(),而需要在静态 ES|QL 中安全插入动态值时使用 template tag。
保持查询安全
如果查询的任何部分来自用户输入,就必须考虑注入风险。ES|QL 支持参数绑定,而 DSL 可以让这一点变得更简单直观:
`
1. function searchLogs(userQuery: string) {
2. const query = ESQL.from('logs-*')
3. .where(E('message').eq(E('?')))
4. .limit(100)
6. return client.esql.query({
7. query: query.render(),
8. params: [userQuery],
9. })
10. }
`AI写代码
? 占位符会在 Elasticsearch 服务器端被替换,因此用户输入不会直接进入查询字符串中。这意味着无需手动转义,也能避免注入风险。
进阶内容
当你熟悉了基础用法之后,这个 DSL 支持所有高级 ES|QL 特性,例如:
混合搜索(Hybrid search)中的 FORK 和 FUSE:
`
1. const results = ESQL.from('articles')
2. .fork(
3. ESQL.branch()
4. .where(f.match('title', 'elasticsearch'))
5. .sort(E('_score').desc())
6. .limit(50),
7. ESQL.branch()
8. .where(f.knn('embedding', 10))
9. .sort(E('_score').desc())
10. .limit(50),
11. )
12. .fuse('RRF')
13. .limit(10)
`AI写代码
数据增强(Data enrichment):
`
1. const enriched = ESQL.from('logs-*')
2. .enrich('ip_lookup')
3. .on('client.ip')
4. .with('geo.city', 'geo.country')
`AI写代码
条件聚合(Conditional aggregation):
`
1. const stats = ESQL.from('employees')
2. .stats({
3. eng_avg: f.avg('salary').where(E('dept').eq('Engineering')),
4. sales_avg: f.avg('salary').where(E('dept').eq('Sales')),
5. total: f.count(),
6. })
`AI写代码
AI / 机器学习(ML)集成:
`
1. const summarized = ESQL.from('docs')
2. .completion('Summarize this document')
3. .with({ inferenceId: 'my-llm' })
`AI写代码
完整命令与函数列表请查看 ES|QL query builder 文档。
下一步
这是 @elastic/elasticsearch-esql-dsl 的首个版本发布。你可以在 npm 上找到该包,在 GitHub 上查看源码,并在仓库中阅读完整文档。如果你遇到问题或有功能需求,欢迎提交 issue;我们正在持续迭代,希望真正构建 JavaScript 和 TypeScript 开发者需要的工具。