用于 JavaScript 和 TypeScript 的 ES|QL 查询构建器:流式、类型安全的查询构建

0 阅读3分钟

作者:来自 Elastic Margaret Gu

探索用于 JavaScript 和 TypeScript 的 ES|QL 查询构建器,并通过实际示例讲解如何构建 ES|QL 查询。

更多阅读:Elasticsearch: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写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

值会被自动转义(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写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
`

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写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

三个不同的查询,共用一个基础查询。修改 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写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

? 占位符会在 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写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

数据增强(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 开发者需要的工具。

原文:www.elastic.co/search-labs…