Elasticsearch SearchRequest 构建备忘录

一、SearchRequest 构建基础

1. 最简单的查询 - 查询所有文档

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;

// 方式1:使用 Lambda Builder(推荐)
SearchRequest request = SearchRequest.of(req -> req
        .index("salesdata_idx")  // 索引名
        .size(100)                // 返回条数
);

SearchResponse<Map> response = elasticsearchClient.search(request, Map.class);

2. 精确匹配查询(Term Query)

java 复制代码
import co.elastic.clients.elasticsearch._types.query_dsl.Query;

SearchRequest request = SearchRequest.of(req -> req
        .index("salesdata_idx")
        .query(q -> q
                .term(t -> t
                        .field("status")
                        .value("active")
                )
        )
        .size(50)
);

对应 DSL:

json 复制代码
{
  "query": {
    "term": {
      "status": "active"
    }
  },
  "size": 50
}

3. 多条件组合查询(Bool Query) - 最常用

java 复制代码
SearchRequest request = SearchRequest.of(req -> req
        .index("salesdata_idx")
        .query(q -> q
                .bool(b -> b
                        // MUST: 必须满足(AND)
                        .must(m -> m.term(t -> t.field("geo").value("CN")))
                        .must(m -> m.term(t -> t.field("fiscal_year").value("FY25")))
                        
                        // SHOULD: 满足其一即可(OR)
                        .should(s -> s.term(t -> t.field("status").value("active")))
                        .should(s -> s.term(t -> t.field("status").value("pending")))
                        
                        // MUST_NOT: 必须不满足
                        .mustNot(mn -> mn.term(t -> t.field("deleted").value(true)))
                        
                        // 最少满足几个 SHOULD
                        .minimumShouldMatch("1")
                )
        )
        .size(100)
);

对应 DSL:

json 复制代码
{
  "query": {
    "bool": {
      "must": [
        {"term": {"geo": "CN"}},
        {"term": {"fiscal_year": "FY25"}}
      ],
      "should": [
        {"term": {"status": "active"}},
        {"term": {"status": "pending"}}
      ],
      "must_not": [
        {"term": {"deleted": true}}
      ],
      "minimum_should_match": "1"
    }
  }
}

4. 范围查询(Range Query)

java 复制代码
SearchRequest request = SearchRequest.of(req -> req
        .index("salesdata_idx")
        .query(q -> q
                .range(r -> r
                        .field("amount")
                        .gte(co.elastic.clients.json.JsonData.of(1000))  // >= 1000
                        .lte(co.elastic.clients.json.JsonData.of(50000)) // <= 50000
                )
        )
);

日期范围查询:

java 复制代码
SearchRequest request = SearchRequest.of(req -> req
        .index("salesdata_idx")
        .query(q -> q
                .range(r -> r
                        .field("create_time")
                        .gte(co.elastic.clients.json.JsonData.of("2025-01-01"))
                        .lt(co.elastic.clients.json.JsonData.of("2025-12-31"))
                )
        )
);

5. 模糊查询/全文检索(Match Query)

java 复制代码
SearchRequest request = SearchRequest.of(req -> req
        .index("salesdata_idx")
        .query(q -> q
                .match(m -> m
                        .field("customer_name")
                        .query("联想")
                        .fuzziness("AUTO")  // 模糊匹配
                )
        )
);

6. IN 查询(Terms Query)

java 复制代码
List<String> recordIds = Arrays.asList("id1", "id2", "id3");

SearchRequest request = SearchRequest.of(req -> req
        .index("salesdata_idx")
        .query(q -> q
                .terms(t -> t
                        .field("record_id")
                        .terms(tv -> tv.value(recordIds.stream()
                                .map(id -> co.elastic.clients.elasticsearch._types.FieldValue.of(id))
                                .collect(Collectors.toList())
                        ))
                )
        )
);

更简洁的写法(使用快捷方法):

java 复制代码
SearchRequest request = SearchRequest.of(req -> req
        .index("salesdata_idx")
        .query(q -> q
                .terms(t -> t
                        .field("record_id")
                        .terms(tv -> tv.value(
                                recordIds.stream()
                                        .map(co.elastic.clients.elasticsearch._types.FieldValue::of)
                                        .collect(Collectors.toList())
                        ))
                )
        )
);

7. 指定返回字段(Source Filtering)

java 复制代码
SearchRequest request = SearchRequest.of(req -> req
        .index("salesdata_idx")
        // 只返回指定字段
        .source(s -> s.filter(f -> f
                .includes("record_id", "customer_name", "amount")
                .excludes("internal_notes")  // 排除某些字段
        ))
        .query(q -> q.matchAll(m -> m))  // 查询所有
        .size(100)
);

8. 排序(Sort)

java 复制代码
import co.elastic.clients.elasticsearch._types.SortOrder;

SearchRequest request = SearchRequest.of(req -> req
        .index("salesdata_idx")
        .query(q -> q.matchAll(m -> m))
        .sort(s -> s
                .field(f -> f
                        .field("amount")
                        .order(SortOrder.Desc)  // 降序
                )
        )
        .sort(s -> s
                .field(f -> f
                        .field("create_time")
                        .order(SortOrder.Asc)   // 升序
                )
        )
        .size(50)
);

9. 分页查询

java 复制代码
int pageNum = 2;
int pageSize = 20;

SearchRequest request = SearchRequest.of(req -> req
        .index("salesdata_idx")
        .query(q -> q.matchAll(m -> m))
        .from((pageNum - 1) * pageSize)  // 起始位置
        .size(pageSize)                   // 每页大小
);

⚠️ 注意 :ES 默认限制 from + size <= 10000,深分页建议使用 ScrollSearch After


10. 聚合查询(Aggregation)

java 复制代码
SearchRequest request = SearchRequest.of(req -> req
        .index("salesdata_idx")
        .size(0)  // 不返回文档,只返回聚合结果
        .aggregations("geo_stats", a -> a
                .terms(t -> t
                        .field("geo")
                        .size(10)  // 返回前10个分组
                )
        )
        .aggregations("total_amount", a -> a
                .sum(s -> s
                        .field("amount")
                )
        )
);

// 获取聚合结果
Map<String, Aggregate> aggs = response.aggregations();

二、结合你的项目实际场景

根据你项目中的 SelectEsDataHandler,这里提供一个完整的示例:

java 复制代码
public EsResult handle(EsDataQuery qry) throws Exception {
    // 构建查询条件
    SearchRequest searchRequest = SearchRequest.of(req -> {
        var builder = req.index(qry.getIndexName());
        
        // 构建 Bool 查询
        if (!qry.isKibanaQuery() && qry.getFieldQuery() != null) {
            builder.query(q -> q.bool(b -> {
                // 添加 record_id IN 查询
                if (CollectionUtils.isNotEmpty(qry.getFieldQuery().getRecordIdList())) {
                    List<FieldValue> values = qry.getFieldQuery().getRecordIdList().stream()
                            .map(FieldValue::of)
                            .collect(Collectors.toList());
                    b.must(m -> m.terms(t -> t
                            .field("record_id")
                            .terms(tv -> tv.value(values))
                    ));
                }
                
                // 添加 invoice_number IN 查询
                if (CollectionUtils.isNotEmpty(qry.getFieldQuery().getInvoiceNumberList())) {
                    List<FieldValue> values = qry.getFieldQuery().getInvoiceNumberList().stream()
                            .map(FieldValue::of)
                            .collect(Collectors.toList());
                    b.must(m -> m.terms(t -> t
                            .field("invoice_number")
                            .terms(tv -> tv.value(values))
                    ));
                }
                
                // 添加 material IN 查询
                if (CollectionUtils.isNotEmpty(qry.getFieldQuery().getMaterialList())) {
                    List<FieldValue> values = qry.getFieldQuery().getMaterialList().stream()
                            .map(FieldValue::of)
                            .collect(Collectors.toList());
                    b.must(m -> m.terms(t -> t
                            .field("material")
                            .terms(tv -> tv.value(values))
                    ));
                }
                
                return b;
            }));
        }
        
        // 设置返回字段
        if (qry.get_source() != null && qry.get_source().length > 0) {
            builder.source(s -> s.filter(f -> f.includes(Arrays.asList(qry.get_source()))));
        }
        
        // 设置分页大小
        builder.size(200);
        
        return builder;
    });
    
    // 执行查询
    SearchResponse<Map> response = elasticsearchClient.search(searchRequest, Map.class);
    
    // 处理结果...
}

三、常用查询对照表

SQL Elasticsearch DSL Java Client API
WHERE status = 'active' term q.term(t -> t.field("status").value("active"))
WHERE id IN (1,2,3) terms q.terms(t -> t.field("id").terms(...))
WHERE amount > 1000 range q.range(r -> r.field("amount").gte(...))
WHERE name LIKE '%联想%' match q.match(m -> m.field("name").query("联想"))
AND bool.must b.must(...)
OR bool.should b.should(...)
NOT bool.must_not b.mustNot(...)
ORDER BY amount DESC sort .sort(s -> s.field(f -> f.field("amount").order(Desc)))
LIMIT 20 OFFSET 40 from/size .from(40).size(20)

四、关键注意事项

  1. 字段类型匹配term 查询用于 keyword 类型,match 用于 text 类型
  2. 空值处理:查询前判断集合是否为空,避免构建无效查询
  3. 性能优化
    • 使用 _source 过滤减少网络传输
    • 避免深分页(from + size > 10000
    • 大数据量使用 Scroll 或 Search After
  4. 超时设置.timeout(Time.of(t -> t.time("30s")))

记录一下

相关推荐
eastyuxiao6 小时前
思维导图拆解项目范围 3 个真实落地案例
大数据·运维·人工智能·流程图
Meya112711 小时前
别再人工硬扛机房管理!智能 U 位系统,让机房管理一键数字化
大数据·运维
天辛大师12 小时前
天辛大师谈人工智能时代,如何用AI研究历代放生劝善忏悔文
大数据·人工智能·随机森林·启发式算法
为儿打call12 小时前
SparkSQL 广播超时排查:小表但是多分区 = BroadcastTimeout
大数据·spark
eastyuxiao12 小时前
如何用思维导图拆解项目范围
大数据·人工智能·流程图
渣渣盟12 小时前
Apache Flink物理分区算子全解析
大数据·flink·apache
小王毕业啦13 小时前
(1990-2024年)个股交易活跃度、个股换手率
大数据·人工智能·数据挖掘·数据分析·区块链·社科数据
N串13 小时前
2.7 公司内部的“阶级”是什么
大数据·人工智能
lizhihai_9914 小时前
股市学习心得—商业航天10大核心材料供应商
大数据·人工智能·学习
app软件定制开发1737709107215 小时前
世界杯应用开发的关键要点与注意事项
大数据·区块链