57 · ES|QL 与 SQL API(用 SQL 查 ES)
阶段:第六阶段 / 进阶专题
ES:ES|QL(
_query)+ SQL API(_sql) | PostgreSQL:直接写 SQL
1. 概念
前面 56 篇都在教你把 SQL「翻译」成 DSL。但 ES 其实提供了两条直接写类 SQL的路:
| 能力 | 端点 | 定位 | 适用 |
|---|---|---|---|
| SQL API | _sql |
ANSI SQL 子集,DSL 的「SQL 外壳」 | 熟悉 SQL、做过滤/聚合报表、BI 工具接入 |
| ES|QL | _query |
ES 8.11+ 全新管道式查询语言 | 数据探索、多步转换、聚合、可读性强 |
对 PostgreSQL 用户,这两个是「学习成本最低」的入口:不用记 bool/must/filter 嵌套,直接写 WHERE ... GROUP BY ...。
定位提醒:ES|QL / SQL 适合分析与报表 ;对全文相关性打分(
_score)、高并发点查、复杂 nested/join,仍以 DSL 为主(见 10/13/25/51 篇)。
2. PostgreSQL 对照
你在 PG 里怎么写,这里几乎照搬:
sql
SELECT region, SUM(amount) AS total
FROM sales
WHERE amount >= 1000
GROUP BY region
ORDER BY total DESC
LIMIT 5;
差异点:
- ES 表名 = 索引名 (可带通配符
sales-*)。 - 没有 JOIN(ES|QL 有实验性
LOOKUP JOIN,SQL API 无 JOIN);关联优先反范式(见 51 篇)。 text字段做精确/分组要用其keyword子字段(见 02 篇)。
3. ES DSL
3.1 SQL API(_sql)
POST _sql?format=txt
{
"query": "SELECT region, SUM(amount) AS total FROM \"sales_idx\" WHERE amount >= 1000 GROUP BY region ORDER BY total DESC LIMIT 5"
}
-
format:txt(表格,人看)/json/csv/tsv。 -
分页:响应里返回
cursor,下一页把 cursor 传回:POST _sql
{ "cursor": "<上一页返回的 cursor>" } -
不会写 SQL 对应的 DSL? 用翻译端点直接看它生成了什么:
POST _sql/translate
{ "query": "SELECT region, SUM(amount) FROM sales_idx WHERE amount >= 1000 GROUP BY region" }
_sql/translate是学 DSL 的神器:写你熟的 SQL,反查它对应的bool/aggs写法。
3.2 ES|QL(_query)
管道式:数据从上一段「流」到下一段,用 | 串联,非常像 Unix 管道。
POST _query?format=txt
{
"query": """
FROM sales_idx
| WHERE amount >= 1000
| STATS total = SUM(amount) BY region
| SORT total DESC
| LIMIT 5
"""
}
常用命令对照:
| ES|QL | SQL / 说明 |
|---|---|
FROM idx |
FROM 表 |
WHERE |
WHERE |
STATS agg BY col |
GROUP BY + 聚合 |
SORT col DESC |
ORDER BY |
LIMIT n |
LIMIT |
KEEP col1, col2 |
SELECT col1, col2(字段裁剪) |
DROP col |
排除字段 |
EVAL new = a + b |
计算列(生成新字段) |
RENAME a AS b |
列改名 |
DISSECT / GROK |
解析文本抽字段 |
EVAL 示例(计算列 + 过滤):
FROM sales_idx
| EVAL net = amount - discount
| WHERE net > 0
| STATS avg_net = AVG(net) BY region
4. Spring Boot 实现
官方 ElasticsearchClient 同时支持 sql() 和 esql()。
java
@Component
public class Doc57SqlEsql {
@Autowired
private ElasticsearchClient elasticsearchClient;
/** SQL API:返回列名 + 行数据 */
public void runSql(String sql) throws IOException {
SqlQueryResponse resp = elasticsearchClient.sql().query(q -> q
.query(sql)
.fetchSize(1000)); // 每批行数
// 列元数据
List<String> cols = resp.columns().stream()
.map(Column::name).collect(Collectors.toList());
// 行数据(每行是 JsonData 列表)
for (List<JsonData> row : resp.rows()) {
// row.get(i) 对应 cols.get(i)
}
// 分页:resp.cursor() 非空则继续取下一批
String cursor = resp.cursor();
if (cursor != null && !cursor.isEmpty()) {
elasticsearchClient.sql().query(q -> q.cursor(cursor));
}
}
/** ES|QL:直接映射成对象 */
public List<SalesRow> runEsql() throws IOException {
// 8.17 客户端提供对象映射入口
BinaryResponse bin = elasticsearchClient.esql().query(q -> q
.query("""
FROM sales_idx
| WHERE amount >= 1000
| STATS total = SUM(amount) BY region
| SORT total DESC
| LIMIT 5
""")
.format("json"));
// 读取 bin.content()(InputStream)自行解析,
// 或使用 ElasticsearchClient 的 ES|QL object-mapping helper 映射到 SalesRow。
return parse(bin);
}
}
import:
co.elastic.clients.elasticsearch.sql.SqlQueryResponse、...sql.Column、
co.elastic.clients.json.JsonData、co.elastic.clients.transport.endpoints.BinaryResponse。ES|QL 结果是「列式」的(
columns+values),映射对象时按列顺序对齐。
5. 坑与最佳实践
text字段别直接GROUP BY/=:用col.keyword子字段,否则报错或结果不符预期(见 02/43 篇)。- SQL API 无 JOIN :需要关联先反范式,或 ES|QL 用实验性
LOOKUP JOIN(见 51 篇)。 - 深分页 :SQL API 用
cursor顺序翻页,用完POST _sql/close释放;别用大LIMIT跳页(见 40 篇)。 - 相关性打分不是强项 :要
_score、高亮、match分词打分,回到 DSL(10/26 篇)。 - 报表/BI 首选:SQL API 可接 JDBC/ODBC 驱动,给不懂 DSL 的分析师用最省事。
- 学 DSL 的捷径 :写 SQL →
_sql/translate反查 DSL,比死记bool嵌套快得多。 - ES|QL 是趋势:8.11+ 持续增强,新分析场景优先考虑,可读性和多步转换远胜手写 DSL。