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

作者:来自 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 查询,你可能写过类似这样的代码:

复制代码
const query = `FROM logs-*
| WHERE status_code >= ${minStatus}
  AND host.name == ${hostname}
  AND @timestamp >= "${startDate}"
| STATS error_count = COUNT(*) BY status_code
| SORT error_count DESC
| LIMIT 10`

它看起来没问题,直到 hostname 变成 O'Brien's server,然后整个查询因为解析错误直接崩掉。或者直到某个用户在搜索字段里传入 "; DROP INDEX logs,你才意识到你一直在用字符串拼接来构建查询。

有更好的方式。JavaScript 和 TypeScript 的 ES|QL 查询构建器可以让你改成这样写查询:

复制代码
import { ESQL, E, f } from '@elastic/elasticsearch-esql-dsl'

const query = ESQL.from('logs-*')
  .where(E('status_code').gte(minStatus))
  .where(E('host.name').eq(hostname))
  .where(E('@timestamp').gte(startDate))
  .stats({ error_count: f.count() })
  .by('status_code')
  .sort(E('error_count').desc())
  .limit(10)

值会被自动转义(escaped)。你可以在编辑器中获得自动补全(autocomplete),并且无需再"脑内解析"模板字符串,就能清晰理解查询实际在做什么。

ES|QL 查询构建器已经在 Elastic 的多种语言客户端中提供支持,包括 Python、Ruby 等。本篇文章重点介绍 JavaScript 和 TypeScript 版本,并通过一些可以直接上手的实际示例来讲解它的用法。

入门开始

安装该包:

复制代码
npm install @elastic/elasticsearch-esql-dsl

这是一个最小查询示例:

复制代码
import { ESQL, E } from '@elastic/elasticsearch-esql-dsl'

const query = ESQL.from('employees')
  .where(E('still_hired').eq(true))
  .sort(E('last_name').asc())
  .limit(10)

console.log(query.render())

渲染结果如下:

复制代码
FROM employees
| WHERE still_hired == true
| SORT last_name ASC
| LIMIT 10

要在 Elasticsearch 上执行它:

复制代码
import { Client } from '@elastic/elasticsearch'

const client = new Client({ node: 'http://localhost:9200' })
const response = await client.esql.query({ query: query.render() })

就是这样。没有字符串插值,也不需要手动转义。

一步一步构建真实查询

我们来看一个更贴近实际的场景:你正在构建一个用于分析 Web 服务器错误日志的仪表盘。我们先从简单开始,然后逐步增加功能。

步骤 1:过滤错误日志

复制代码
import { ESQL, E } from '@elastic/elasticsearch-esql-dsl'

const errors = ESQL.from('logs-*')
  .where(E('status_code').gte(400))
  .limit(100)

FROM logs-*
| WHERE status_code >= 400
| LIMIT 100

步骤 2:添加计算列

你的时间戳是以毫秒为单位,但你希望将响应时间转换为秒:

复制代码
const errors = ESQL.from('logs-*')
  .where(E('status_code').gte(400))
  .eval({ response_secs: E('response_time_ms').div(1000) })
  .limit(100)

FROM logs-*
| WHERE status_code >= 400
| EVAL response_secs = response_time_ms / 1000
| LIMIT 100

步骤 3:按状态码聚合错误

复制代码
import { f } from '@elastic/elasticsearch-esql-dsl'

const errorBreakdown = ESQL.from('logs-*')
  .where(E('status_code').gte(400))
  .stats({
    error_count: f.count(),
    avg_response: f.avg('response_time_ms'),
  })
  .by('status_code')
  .sort(E('error_count').desc())

FROM logs-*
| WHERE status_code >= 400
| STATS error_count = COUNT(*), avg_response = AVG(response_time_ms) BY status_code
| SORT error_count DESC

这个 f 命名空间提供了 150+ 个 ES|QL 函数封装:包括聚合函数、字符串函数、日期函数、数学函数、地理函数等等。它们都返回可链式调用的表达式,因此可以在任何原本使用 E() 的地方使用。

步骤 4:使用日期函数进行时间维度分析

复制代码
const hourlyErrors = ESQL.from('logs-*')
  .where(E('status_code').gte(400))
  .eval({ hour: f.dateTrunc('@timestamp', '1 hour') })
  .stats({ error_count: f.count() })
  .by('hour')
  .sort(E('hour'))

FROM logs-*
| WHERE status_code >= 400
| EVAL hour = DATE_TRUNC(@timestamp, "1 hour")
| STATS error_count = COUNT(*) BY hour
| SORT hour

步骤 5:安全地分支查询

每个方法都会返回一个新的查询对象,原始查询不会被修改。这意味着你可以先构建一个基础查询,然后为不同的视图进行分支扩展:

复制代码
const base = ESQL.from('logs-*')
  .where(E('status_code').gte(400))
  .where(E('@timestamp').gte('2026-01-01T00:00:00Z'))

const byStatus = base
  .stats({ count: f.count() })
  .by('status_code')
  .sort(E('count').desc())

const byHost = base
  .stats({ count: f.count() })
  .by('host.name')
  .sort(E('count').desc())
  .limit(20)

const recent = base
  .sort(E('@timestamp').desc())
  .keep('@timestamp', 'status_code', 'url.path', 'message')
  .limit(50)

三个不同的查询,共用一个基础查询。修改 base 的过滤条件,三个查询都会同步更新。这在仪表盘场景中特别有用,因为多个面板通常会对同一数据集使用不同的聚合方式。

三种表达式写法

该领域专用语言(DSL)提供了多种编写条件的方式。下面是同一个 WHERE 子句的三种不同写法:

原始字符串(raw strings):适用于快速一次性查询:

复制代码
.where('status_code >= 400 AND host.name == "web-01"')

E() 表达式构建器:当你需要类型安全和自动补全时使用:

复制代码
import { and_ } from '@elastic/elasticsearch-esql-dsl'

.where(and_(
  E('status_code').gte(400),
  E('host.name').eq('web-01')
))

esql 模板标签(template tag):当你需要安全地插入动态值时使用:

复制代码
import { esql } from '@elastic/elasticsearch-esql-dsl'

const minStatus = 400
const host = 'web-01'
.where(esql`status_code >= ${minStatus} AND host.name == ${host}`)

这三种方式最终都会生成相同的 ES|QL 查询。你可以根据具体场景选择合适方式:简单场景用 raw string,程序化构建表达式用 E(),而需要在静态 ES|QL 中安全插入动态值时使用 template tag。

保持查询安全

如果查询的任何部分来自用户输入,就必须考虑注入风险。ES|QL 支持参数绑定,而 DSL 可以让这一点变得更简单直观:

复制代码
function searchLogs(userQuery: string) {
  const query = ESQL.from('logs-*')
    .where(E('message').eq(E('?')))
    .limit(100)

  return client.esql.query({
    query: query.render(),
    params: [userQuery],
  })
}

**?**占位符会在 Elasticsearch 服务器端被替换,因此用户输入不会直接进入查询字符串中。这意味着无需手动转义,也能避免注入风险。

进阶内容

当你熟悉了基础用法之后,这个 DSL 支持所有高级 ES|QL 特性,例如:

混合搜索(Hybrid search)中的 FORK 和 FUSE:

复制代码
const results = ESQL.from('articles')
  .fork(
    ESQL.branch()
      .where(f.match('title', 'elasticsearch'))
      .sort(E('_score').desc())
      .limit(50),
    ESQL.branch()
      .where(f.knn('embedding', 10))
      .sort(E('_score').desc())
      .limit(50),
  )
  .fuse('RRF')
  .limit(10)

数据增强(Data enrichment)

复制代码
const enriched = ESQL.from('logs-*')
  .enrich('ip_lookup')
  .on('client.ip')
  .with('geo.city', 'geo.country')

条件聚合(Conditional aggregation):

复制代码
const stats = ESQL.from('employees')
  .stats({
    eng_avg: f.avg('salary').where(E('dept').eq('Engineering')),
    sales_avg: f.avg('salary').where(E('dept').eq('Sales')),
    total: f.count(),
  })

AI / 机器学习(ML)集成:

复制代码
const summarized = ESQL.from('docs')
  .completion('Summarize this document')
  .with({ inferenceId: 'my-llm' })

完整命令与函数列表请查看 ES|QL query builder 文档

下一步

这是 @elastic/elasticsearch-esql-dsl 的首个版本发布。你可以在 npm 上找到该包,在 GitHub 上查看源码,并在仓库中阅读完整文档。如果你遇到问题或有功能需求,欢迎提交 issue;我们正在持续迭代,希望真正构建 JavaScript 和 TypeScript 开发者需要的工具。

原文:https://www.elastic.co/search-labs/blog/esql-query-builder-javascript-typescript

相关推荐
xiaoduo AI6 小时前
智能客服机器人能否实现多 LLM 自由切换?Agent 开放平台接入 DeepSeek、通义千问如何对比成本与效果?
大数据·人工智能·机器人
禹凕6 小时前
MYSQL——基础知识(元数据)
数据库·sql·mysql
m0_624578596 小时前
CSS如何优化Bootstrap加载速度_利用CSS压缩技术减少体积
jvm·数据库·python
FreeGo~6 小时前
【MySQL数据库】数据库基础第一篇
数据库·mysql·oracle
龙亘川6 小时前
2025 中国大数据产业全景:技术、场景、生态与企业格局深度解析
大数据·2025 中国大数据产业白皮书
小短腿的代码世界6 小时前
Qt序列化与持久化深度解析:从QDataStream到自定义二进制协议
开发语言·数据库·qt
秋96 小时前
一键安装mysql8.4.9(附脚本)
数据库
zjy277776 小时前
Go语言怎么用GitHub Actions_Go语言GitHub Actions教程【基础】
jvm·数据库·python
2301_782040456 小时前
如何实现SQL用户行为追踪_通过触发器记录操作明细
jvm·数据库·python