ElasticSearch7.X学习四--数据类型--keyword datatype

本文介绍了ElasticSearch7.X中keyword数据类型的概念和用途,适用于索引ID、邮箱地址等结构化内容。keyword类型适合用于过滤、排序和聚合操作,提供精确查询。对比text类型,keyword在范围查询时效率更高。文中还讨论了如何根据需求选择numberic或keyword,以及多类型字段的使用,并通过查询示例说明了keyword在查询中的重要性。

官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.html

概念

keyword类型是索引ID,邮箱地址,hostnames,zip codes,tags,状态码(status codes)等结构化内容。通常是在查询中用来“过滤”,“排序”,“聚合”等操作,并且当keyword类型字段作为查询条件时,是需要精确查找的。如果是需要对多文本查询的话还是使用text类型。
对于numberic类型文档中提到如果是明确需要用到范围查询(range query)的话那还是使用numberic,如果不需要使用范围查询的话就使用keyword,因为term和term-level查询对于keywrod类型来说查询是很高效的。如果不确定是否使用range查询文档中还提到可以使用多类型,如下定义:

  "state" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }

多类型,不限于numberic,为了查询效率高,text类型也可以,只是我认为多类型肯定会占用更多的内存和存储。上面 "ignore_above"的含义是“state”字段长度不超过256,单位应该是字节,最大值是2147483647。

查询

查询过程中如果是keyword类型的字段,需要将这个keyword加上,不加和加前后对比:

curl -X GET "localhost:9200/bank/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": { "bool": {"must":{"term":{"state":"KS"}}} },
  "sort": [
    { "account_number": "asc" }
  ],
  "from": 0,
  "size": 10
}
'

结果是没有数据hit到。加上后:

curl -X GET "localhost:9200/bank/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "query": { "bool": {"must":{"term":{"state.keyword":"KS"}}} },
  "sort": [
    { "account_number": "asc" }
  ],
  "from": 0,
  "size": 10
}
'

结果是:

  {
        "_index" : "bank",
        "_type" : "_doc",
        "_id" : "534",
        "_score" : null,
        "_source" : {
          "account_number" : 534,
          "balance" : 20470,
          "firstname" : "Cristina",
          "lastname" : "Russo",
          "age" : 25,
          "gender" : "F",
          "address" : "500 Highlawn Avenue",
          "employer" : "Cyclonica",
          "email" : "cristinarusso@cyclonica.com",
          "city" : "Gorst",
          "state" : "KS"
        },
        "sort" : [
          534
        ]
      },
      {
        "_index" : "bank",
        "_type" : "_doc",
        "_id" : "561",
        "_score" : null,
        "_source" : {
          "account_number" : 561,
          "balance" : 12370,
          "firstname" : "Sellers",
          "lastname" : "Davis",
          "age" : 30,
          "gender" : "M",
          "address" : "860 Madoc Avenue",
          "employer" : "Isodrive",
          "email" : "sellersdavis@isodrive.com",
          "city" : "Trail",
          "state" : "KS"
        },
        "sort" : [
          561
        ]
      }
    ]
  }

开始学习的时候就是因为拉下了keyword这个关键字导致查询无结果集。

@Verify @Override public ResponseVo<List<GoodsSpuPageEsVo>> pageEs(EsPageParamPo pageParam, int page, int size) { if (page < 1) { throw new OSPException(OSPError.PARAM_ERROR, "page必须大于1"); } if (size < 1 || size > 500) { throw new OSPException(OSPError.PARAM_ERROR, "size范围1~100"); } //Integer cusOrgId1 = sysConfigService.getName("SET_DEFAULT_USER_ENTERPRISE_INFO", Integer.class); Pageable pageable = Pageable.newInstance(page, size, pageParam); CustomerEnterprise customerEnterprise = null; String cusEsPathPrefix = null; if (null != pageParam.getCusOrgId()) { customerEnterprise = customerEnterpriseMapper.selectById(pageParam.getCusOrgId()); if (customerEnterprise == null) { log.error("商品使用客群属性es搜索,客群id不存在 {}", pageParam.getCusOrgId()); return ResponseVo.empty(pageable); } cusEsPathPrefix = "cusInfo." + customerEnterprise.getId() + "."; } List<String> cusEsPathPrefixs = new ArrayList<>(); if (CollectionUtils.isNotEmpty(pageParam.getCusOrgIds())) { List<CustomerEnterprise> customerEnterprises = customerEnterpriseMapper.selectList(MCustomerEnterprise.in(MCustomerEnterprise.id, pageParam.getCusOrgIds())); if (CollectionUtils.isEmpty(customerEnterprises)) { log.error("商品使用客群属性es搜索,客群id不存在 {}", pageParam.getCusOrgId()); return ResponseVo.empty(pageable); } customerEnterprises.forEach(enterprise -> { cusEsPathPrefixs.add("cusInfo." + enterprise.getId() + "."); }); } String indexName = sysConfigService.getName(Constants.ES_GOODS_TO_ELASTICSEARCH_INDEX_KEY, String.class, Constants.ES_GOODS_TO_ELASTICSEARCH_INDEX_DEFUALT_VALUE); String minimumShouldMatch = sysConfigService.getName(Constants.ES_MINIMUM_SHOULD_MATCH_KEY, String.class, Constants.ES_MINIMUM_SHOULD_MATCH_DEFUALT_VALUE); HighlightBuilder highlightBuilder = new HighlightBuilder(); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); if (StringUtils.isNotBlank(pageParam.getGoodsName())) { boolQuery.must(QueryBuilders.multiMatchQuery(pageParam.getGoodsName(), "author", "masterName", "publishYear", "searchTags", "name", "typeName", "publishingHouse") .field("name", 2.0f).operator(Operator.OR).minimumShouldMatch(minimumShouldMatch)); // 配置高亮显示 highlightBuilder.field("name"); highlightBuilder.field("author"); // 设置高亮标签 highlightBuilder.preTags("<span style=\"color:#FF0000;\">"); highlightBuilder.postTags("</span>"); /** 相似度搜索,提高全文搜索的_score评分 */ boolQuery.should(QueryBuilders.matchPhraseQuery("name", pageParam.getGoodsName()).slop(6)); boolQuery.should(QueryBuilders.matchPhraseQuery("isbn", pageParam.getGoodsName()).slop(4)); boolQuery.should(QueryBuilders.matchPhraseQuery("author", pageParam.getGoodsName()).slop(2)); boolQuery.should(QueryBuilders.matchPhraseQuery("searchTags", pageParam.getGoodsName()).slop(2)); boolQuery.should(QueryBuilders.matchPhraseQuery("typeName", pageParam.getGoodsName()).slop(2)); boolQuery.should(QueryBuilders.matchPhraseQuery("masterName", pageParam.getGoodsName()).slop(3)); boolQuery.should(QueryBuilders.matchPhraseQuery("publishYear", pageParam.getGoodsName()).slop(3)); boolQuery.should(QueryBuilders.matchPhraseQuery("publishingHouse", pageParam.getGoodsName()).slop(3)); // 设置minimum_should_match参数 boolQuery.minimumShouldMatch(1); } if (StringUtils.isNotBlank(pageParam.getSearchInfo())) { boolQuery.must(QueryBuilders.multiMatchQuery(pageParam.getSearchInfo(), "name", "isbn", "spuId") //TODO 这里重设了 字段的得分权重 .field("name", (float) 2.0).operator(Operator.OR).minimumShouldMatch(minimumShouldMatch)); highlightBuilder.field("name"); /**相似度搜索,提高全文搜索的_score的评分*/ boolQuery.should(QueryBuilders.matchPhraseQuery("name", pageParam.getSearchInfo()).slop(6)); boolQuery.should(QueryBuilders.matchPhraseQuery("isbn", pageParam.getSearchInfo()).slop(2)); boolQuery.should(QueryBuilders.matchPhraseQuery("spuId", pageParam.getSearchInfo()).slop(2)); } if (StringUtils.isNotBlank(pageParam.getLeftName())) { // 配置高亮显示 highlightBuilder.field("name"); // 设置高亮标签 highlightBuilder.preTags("<span style=\"color:#FF0000;\">"); highlightBuilder.postTags("</span>"); boolQuery.must(QueryBuilders.matchQuery("name", pageParam.getLeftName())); // 可选:添加模糊匹配提高召回率 //boolQuery.should(QueryBuilders.matchQuery("name", pageParam.getLeftName())); // 设置minimum_should_match //boolQuery.minimumShouldMatch(0); } String productClassCode = pageParam.getProductClassCode(); if (customerEnterprise != null) { boolQuery.must(QueryBuilders.termQuery(cusEsPathPrefix + "canGoods", "true")); boolQuery.must(QueryBuilders.termQuery(cusEsPathPrefix + "canSupplierCode", "true")); if (!productClassCode.equals(ProductClassCodeEnum.VIRTUAL.code)) { boolQuery.must(QueryBuilders.termQuery(cusEsPathPrefix + "canType", "true")); } } if (CollectionUtils.isNotEmpty(cusEsPathPrefixs)) { BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery(); cusEsPathPrefixs.forEach(prefix -> { // 为每个前缀创建一个必须满足所有条件的布尔查询 BoolQueryBuilder prefixQuery = QueryBuilders.boolQuery(); prefixQuery.must(QueryBuilders.termQuery(prefix + "canGoods", "true")); prefixQuery.must(QueryBuilders.termQuery(prefix + "canSupplierCode", "true")); if (!productClassCode.equals(ProductClassCodeEnum.VIRTUAL.code)) { prefixQuery.must(QueryBuilders.termQuery(prefix + "canType", "true")); } // 将每个前缀的查询添加到主查询中作为 should 条件 subBoolQuery.should(prefixQuery); }); // 设置 minimum_should_match 为 1,表示只需满足任意一个前缀的所有条件即可 subBoolQuery.minimumShouldMatch(1); boolQuery.must(subBoolQuery); } if (null != pageParam.getLowPrice() || null != pageParam.getHighPrice()) { RangeQueryBuilder rangeQueryBuilder = null; if (customerEnterprise != null) { rangeQueryBuilder = QueryBuilders.rangeQuery(cusEsPathPrefix + "price"); } else { rangeQueryBuilder = QueryBuilders.rangeQuery("sellPrice"); } if (null != pageParam.getLowPrice()) { rangeQueryBuilder.gte(pageParam.getLowPrice()); } if (null != pageParam.getHighPrice()) { rangeQueryBuilder.lte(pageParam.getHighPrice()); } boolQuery.must(rangeQueryBuilder); } if (null != pageParam.getCreateTimeStart() || null != pageParam.getCreateTimeEnd()) { RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("createTimeMillis"); if (null != pageParam.getCreateTimeStart()) { rangeQueryBuilder.gte(pageParam.getCreateTimeStart()); } if (null != pageParam.getCreateTimeEnd()) { rangeQueryBuilder.lte(pageParam.getCreateTimeEnd()); } boolQuery.must(rangeQueryBuilder); } if (null != pageParam.getUpdateTimeStart() || null != pageParam.getUpdateTimeEnd()) { RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("updateTimeMillis"); if (null != pageParam.getUpdateTimeStart()) { rangeQueryBuilder.gte(pageParam.getUpdateTimeStart()); } if (null != pageParam.getUpdateTimeEnd()) { rangeQueryBuilder.lte(pageParam.getUpdateTimeEnd()); } boolQuery.must(rangeQueryBuilder); } if (null != pageParam.getUpTimeStart() || null != pageParam.getUpTimeEnd()) { RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("upTimeMillis"); if (null != pageParam.getUpTimeStart()) { rangeQueryBuilder.gte(pageParam.getUpTimeStart()); } if (null != pageParam.getUpTimeEnd()) { rangeQueryBuilder.lte(pageParam.getUpTimeEnd()); } boolQuery.must(rangeQueryBuilder); } if (null != pageParam.getStockStart() || null != pageParam.getStockEnd()) { RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("stock"); if (null != pageParam.getStockStart()) { rangeQueryBuilder.gte(pageParam.getStockStart()); } if (null != pageParam.getStockEnd()) { rangeQueryBuilder.lte(pageParam.getStockEnd()); } boolQuery.must(rangeQueryBuilder); } if (null != pageParam.getOnTop()) { boolQuery.must(QueryBuilders.termQuery("onTop", pageParam.getOnTop())); } if (null != pageParam.getSupplierCodeList() && !pageParam.getSupplierCodeList().isEmpty()) { BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery(); pageParam.getSupplierCodeList().forEach(one -> { subBoolQuery.should(QueryBuilders.termQuery("supplierCode", one)); }); boolQuery.must(subBoolQuery); } if (null != pageParam.getProductClassCodeList() && !pageParam.getProductClassCodeList().isEmpty()) { BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery(); pageParam.getProductClassCodeList().forEach(one -> { subBoolQuery.should(QueryBuilders.termQuery("productClassCode.keyword", one)); }); boolQuery.must(subBoolQuery); } if (StringUtils.isNotBlank(pageParam.getProductClassCode())) { boolQuery.must(QueryBuilders.termQuery("productClassCode.keyword", pageParam.getProductClassCode())); // boolQuery.must(QueryBuilders.termQuery("label.count", pageParam.getProductClassCode())); }/* else { boolQuery.must(QueryBuilders.termQuery("productClassCode.keyword", ProductClassCodeEnum.BOOK.code)); }*/ if (null != pageParam.getGoodsTypeIdList() && !pageParam.getGoodsTypeIdList().isEmpty()) { MGoodsType.in(MGoodsType.code, pageParam.getGoodsTypeIdList()).eq(MGoodsType.onDel, DeleteEnum.NO_DELETE.code).eq(MGoodsType.channelId, "3106").eq(MGoodsType.status, YESOrNOEnum.STATUS_1.code); if (StringUtils.isNotBlank(pageParam.getProductClassCode())) { MGoodsType.eq(MGoodsType.productClassCode, pageParam.getProductClassCode()); } else { MGoodsType.eq(MGoodsType.productClassCode, ProductClassCodeEnum.BOOK.code); } List<GoodsType> typeList = goodsTypeMapper.selectList(MGoodsType.build()); Stream<String> typePathList = typeList.stream().map(GoodsType::getPath); BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery(); typePathList.forEach(one -> { subBoolQuery.should(QueryBuilders.prefixQuery("typePath", one)); }); boolQuery.must(subBoolQuery); } if (null != pageParam.getSpuIds() && !pageParam.getSpuIds().isEmpty()) { BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery(); pageParam.getSpuIds().forEach(one -> { subBoolQuery.should(QueryBuilders.termQuery("spuId", one)); }); boolQuery.must(subBoolQuery); } if (CollectionUtils.isNotEmpty(pageParam.getBanSpuIds())) { BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery(); pageParam.getBanSpuIds().forEach(one -> { subBoolQuery.should(QueryBuilders.termQuery("spuId", one)); }); boolQuery.mustNot(subBoolQuery); } if (null != pageParam.getTags() && !pageParam.getTags().isEmpty()) { // boolQuery.must(QueryBuilders.matchQuery(channelKey + "tag", pageParam.getTags().stream().collect(Collectors.joining(" "))).minimumShouldMatch("60%")); Map<String, Object> params = Maps.newHashMap(); String scriptStr = ""; for (int i = 0; i < pageParam.getTags().size(); i++) { params.put("to_tag" + i, pageParam.getTags().get(i)); scriptStr += "doc['tags.keyword'].values.contains(params.to_tag" + i + ") "; if (i != (pageParam.getTags().size() - 1)) { scriptStr += "|| "; } } Script script = new Script(Script.DEFAULT_SCRIPT_TYPE, Script.DEFAULT_SCRIPT_LANG, scriptStr, params); boolQuery.must(QueryBuilders.scriptQuery(script)); } if (null != pageParam.getStatus()) { boolQuery.must(QueryBuilders.termQuery("status", pageParam.getStatus())); } if (null != pageParam.getMasterStatus()) { boolQuery.must(QueryBuilders.termQuery("masterStatus", pageParam.getMasterStatus())); } boolQuery.must(QueryBuilders.termQuery("onDel", DeleteEnum.NO_DELETE.code)); boolQuery.must(QueryBuilders.termQuery("masterOnDel", DeleteEnum.NO_DELETE.code)); searchSourceBuilder.query(boolQuery); if (!highlightBuilder.fields().isEmpty()) { searchSourceBuilder.highlighter(highlightBuilder); } searchSourceBuilder.from((page - 1) * size); searchSourceBuilder.size(size); //随机分页查询 放在排序前,否则无效 if (null != pageParam.getIsOpenRandomPage() && pageParam.getIsOpenRandomPage().equals(1)) { Script script = new Script("Math.random()"); ScriptSortBuilder sortBuilder = new ScriptSortBuilder(script, ScriptSortBuilder.ScriptSortType.NUMBER); searchSourceBuilder.sort(sortBuilder); } /**是否默认排序*/ boolean defaultSort = true; if (null != pageParam.getFixedPriceSort()) { defaultSort = false; searchSourceBuilder.sort("fixedPrice", SortTypeEnum.TYPE_1.code.equals(pageParam.getFixedPriceSort()) ? SortOrder.ASC : SortOrder.DESC); } if (null != pageParam.getUpTimeSort()) { defaultSort = false; searchSourceBuilder.sort("upTimeMillis", SortTypeEnum.TYPE_1.code.equals(pageParam.getUpTimeSort()) ? SortOrder.ASC : SortOrder.DESC); } if (null != pageParam.getCreateSort()) { defaultSort = false; searchSourceBuilder.sort("createTimeMillis", SortTypeEnum.TYPE_1.code.equals(pageParam.getCreateSort()) ? SortOrder.ASC : SortOrder.DESC); } if (null != pageParam.getUpdateSort()) { defaultSort = false; searchSourceBuilder.sort("updateTimeMillis", SortTypeEnum.TYPE_1.code.equals(pageParam.getUpdateSort()) ? SortOrder.ASC : SortOrder.DESC); } if (null != pageParam.getRecommendSort()) { defaultSort = false; Map<String, Object> params = Maps.newHashMap(); params.put("to_tag", "推荐"); // Script script=new Script(Script.DEFAULT_SCRIPT_TYPE,Script.DEFAULT_SCRIPT_LANG,"doc['"+channelKey+"tag.keyword'].value==params.to_tag?1:0",params); /**同时存在多个值时候,原来的== 判断就不起作用*/ Script script = new Script(Script.DEFAULT_SCRIPT_TYPE, Script.DEFAULT_SCRIPT_LANG, "doc['tags.keyword'].values.contains(params.to_tag)?1:0", params); ScriptSortBuilder scriptSortBuilder = SortBuilders.scriptSort(script, ScriptSortBuilder.ScriptSortType.NUMBER).order(SortTypeEnum.TYPE_1.code.equals(pageParam.getRecommendSort()) ? SortOrder.ASC : SortOrder.DESC); searchSourceBuilder.sort(scriptSortBuilder); searchSourceBuilder.sort(SortBuilders.fieldSort("onTop").order(SortOrder.DESC).unmappedType("integer")); searchSourceBuilder.sort(SortBuilders.fieldSort("sort").order(SortOrder.ASC).unmappedType("integer")); } if (null != pageParam.getPriceSort()) { defaultSort = false; if (customerEnterprise != null) { searchSourceBuilder.sort(cusEsPathPrefix + "price", SortTypeEnum.TYPE_1.code.equals(pageParam.getPriceSort()) ? SortOrder.ASC : SortOrder.DESC); } else { searchSourceBuilder.sort("sellPrice", SortTypeEnum.TYPE_1.code.equals(pageParam.getPriceSort()) ? SortOrder.ASC : SortOrder.DESC); } } if (null != pageParam.getSaleCntSort()) { defaultSort = false; searchSourceBuilder.sort("spuSaleCnt", SortTypeEnum.TYPE_1.code.equals(pageParam.getSaleCntSort()) ? SortOrder.ASC : SortOrder.DESC); } if (null != pageParam.getPublishTimeSort()) { // 根据出版时间排序 defaultSort = false; FieldSortBuilder fieldSortBuilder = SortBuilders.fieldSort("publishTimeMillis").order(SortTypeEnum.TYPE_1.code.equals(pageParam.getPublishTimeSort()) ? SortOrder.ASC : SortOrder.DESC).unmappedType("number"); searchSourceBuilder.sort(fieldSortBuilder); } if (StringUtils.isNotBlank(pageParam.getLeftName())) { searchSourceBuilder.sort("_score", SortOrder.DESC); } else { if (defaultSort) { if (StringUtils.isNotBlank(pageParam.getGoodsName())) { searchSourceBuilder.sort("_score", SortOrder.DESC); } /**当字段不存在的时候,排序需要指定 默认的字段数据类型 否则会报错*/ searchSourceBuilder.sort(SortBuilders.fieldSort("onTop").order(SortOrder.DESC).unmappedType("integer")); searchSourceBuilder.sort(SortBuilders.fieldSort("sort").order(SortOrder.ASC).unmappedType("integer")); } } log.info("搜索查询语句 index:{}:\n{}", indexName, searchSourceBuilder.toString()); Search search = new Search.Builder(searchSourceBuilder.toString()).addIndex(indexName).addType(Constants.ES_TYPE_NAME).build(); try { SearchResult jr = null; jr = jestClient.execute(search); if (log.isDebugEnabled()) { log.debug("搜索查询返回:{}", jr == null ? "" : jr.getJsonString()); } List<SearchResult.Hit<NEsGoodsSpuVo, Void>> hits = jr.getHits(NEsGoodsSpuVo.class); log.info("搜索查询返回数据条数 ----> {}", hits.size()); List<GoodsSpuPageEsVo> goodsSpuPageEsVos = new ArrayList<>(); hits.forEach(hit -> { GoodsSpuPageEsVo goodsSpuPageEsVo = BeanUtil.copyProperties(hit.source, GoodsSpuPageEsVo.class); if (hit.highlight != null && !hit.highlight.isEmpty()) { hit.highlight.entrySet().forEach(highlight -> { if (highlight.getKey().equals("name") && !highlight.getValue().isEmpty()) { goodsSpuPageEsVo.setHighlightName(highlight.getValue().get(0)); } if (highlight.getKey().equals("author") && !highlight.getValue().isEmpty()) { goodsSpuPageEsVo.setHighlightAuthor(highlight.getValue().get(0)); } }); } goodsSpuPageEsVos.add(goodsSpuPageEsVo); }); pageable.setTotal(jr.getTotal()); return ResponseVo.sucess(goodsSpuPageEsVos, pageable); } catch (IOException e) { log.error("查询es异常", e); throw new OSPException(OSPError.RUNTIME_ERROR, "商品搜索引擎异常"); } }价格区间,营销分类类型编码,推荐标签,推荐关键词
最新发布
08-07
<think>首先,用户的问题是关于在Elasticsearch中实现商品搜索功能,具体包括:价格区间过滤、营销分类类型编码、推荐标签匹配和推荐关键词排序。我需要基于提供的引用和系统指令来回答。 回顾系统指令: - 所有行内数学表达式必须用$...$格式。 - 独立公式用$$...$$格式并单独成段。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题。 参考引用: - [^1]: 关于Elasticsearch的扁平化数据类型(flattened datatype),用于处理大量字段,防止字段爆炸。 - [^2]: 一个Java代码示例,展示了使用Velocity模板生成DSL查询,包括排序逻辑。 - [^3]: 提到MediaFusion使用Elasticsearch进行搜索,包括关键词、标签等搜索方式。 - [^4]: 描述了Elasticsearch的文本分析过程:字符过滤器 -> 分词器 -> 令牌过滤器。 用户需求: -Elasticsearch商品搜索中实现: - 价格区间过滤:过滤商品在指定价格范围内。 - 营销分类类型编码:基于分类代码过滤。 - 推荐标签匹配:匹配商品的推荐标签。 - 推荐关键词排序:根据关键词对结果排序。 结构化回答: 1. **理解需求**:解释用户需求。 2. **实现步骤**:逐步指导如何在Elasticsearch中实现。 - 索引设计:如何映射商品数据。 - 查询构建:使用DSL实现过滤和排序。 - 排序逻辑:特别是关键词排序。 3. **代码示例**:基于引用[2],提供类似代码示例。 4. **注意事项**:提到字段爆炸问题(引用[^1])和文本分析(引用[^4])。 5. **生成相关问题**:在最后添加相关问题。 确保使用中文,并自然添加引用标识。 **索引设计建议**: - 商品索引应包含字段:price(数值类型)、category_code(keyword类型)、tags(数组,keyword类型)、keywords(用于排序)。 - 为了防止字段爆炸,考虑使用flattened类型(引用[^1]),但具体到用户需求,可能不需要,除非有大量动态字段。 **查询构建**: - **价格区间过滤**:使用range查询。 - **营销分类类型编码**:使用term查询。 - **推荐标签匹配**:使用match或term查询,如果tags是数组。 - **推荐关键词排序**:使用function_score或script_score来提升包含关键词的商品排名。 对于排序,用户提到“推荐关键词排序”,可能意味着根据关键词的相关性排序。可以使用Elasticsearch的function_score查询来调整得分。 基于引用[^2],它使用了Velocity模板生成DSL,我可以提供一个类似的DSL示例。 **完整回答大纲**: 1. **介绍**:简述Elasticsearch在商品搜索中的应用。 2. **索引映射**:建议商品索引的映射。 3. **查询DSL**:详细解释和示例查询。 4. **排序实现**:专门解释关键词排序。 5. **代码示例**:参考引用[^2],提供伪代码或Java示例。 6. **注意事项**:性能考虑(引用[^1])。 7. **相关问题**:生成相关问题。 **生成相关问题**: - 如何优化Elasticsearch查询性能? - 如何处理多条件过滤和排序? - Elasticsearch中如何实现复杂排序逻辑? - 如何防止字段爆炸问题? 确保回答真实可靠,基于Elasticsearch知识。</think>### Elasticsearch 商品搜索实现:价格区间过滤、营销分类类型编码、推荐标签匹配和推荐关键词排序 在Elasticsearch中实现商品搜索功能,需要结合索引设计、查询DSL(Domain Specific Language)和排序逻辑。以下我将逐步解释如何满足您的需求:价格区间过滤(过滤指定价格范围内的商品)、营销分类类型编码(基于分类代码过滤)、推荐标签匹配(匹配商品的推荐标签)和推荐关键词排序(根据关键词对结果排序)。我会结合Elasticsearch的最佳实践和参考引用,提供具体实现方案。 #### 1. **索引设计:定义商品映射(Mapping)** 首先,确保商品索引的映射正确,以支持高效查询。基于您的需求,建议使用以下字段: - `price`:数值类型(如`float`或`integer`),用于价格区间过滤。 - `category_code`:keyword类型,用于精确匹配营销分类类型编码。 - `tags`:数组类型(如`keyword`),存储推荐标签,支持多标签匹配。 - `keywords`:text类型(结合keyword子字段),用于关键词排序和匹配。为防止字段爆炸问题(如引用[^1]所述,大量字段会导致性能下降),避免使用动态映射;如果标签或关键词数量未知,可考虑`flattened`类型[^1]。 示例映射JSON: ```json PUT /goods_index { "mappings": { "properties": { "price": { "type": "float" }, "category_code": { "type": "keyword" }, "tags": { "type": "keyword" }, "keywords": { "type": "text", "fields": { "keyword": { "type": "keyword" } // 用于精确排序 } }, // 其他字段如商品名称、描述等 } } } ``` #### 2. **查询构建:实现过滤和匹配** 使用Elasticsearch的布尔查询(`bool` query)组合多个条件。查询DSL包括: - **价格区间过滤**:使用`range`查询过滤价格。 - **营销分类类型编码过滤**:使用`term`查询精确匹配分类代码。 - **推荐标签匹配**:使用`terms`查询匹配标签数组(允许多标签OR逻辑)。 - **推荐关键词排序**:在查询中嵌入排序逻辑,优先使用`function_score`提升包含关键词的商品得分。 完整查询DSL示例: ```json GET /goods_index/_search { "query": { "bool": { "filter": [ // 过滤不影响得分,适合价格和分类 { "range": { "price": { "gte": 100, // 最低价格 "lte": 500 // 最高价格 } } }, { "term": { "category_code": "electronics" // 营销分类类型编码 } }, { "terms": { "tags": ["recommended", "hot"] // 推荐标签匹配,支持多个标签 } } ], "must": [ // 影响得分的查询部分 { "match": { "keywords": "discount" // 推荐关键词匹配,用于基础得分 } } ] } }, "sort": [ // 排序逻辑 { "_score": "desc" // 先按关键词匹配得分排序 }, { "price": "asc" // 次要排序,如价格升序 } ] } ``` - **解释**: - `filter` 用于价格、分类和标签的精确匹配,高效且不计算得分。 - `must` 用于关键词匹配,影响相关性得分。 - `sort` 优先按关键词匹配得分(`_score`)降序排序,确保推荐关键词靠前;您可添加其他字段如价格作为次要排序。 - 如果推荐关键词需要更智能的排序(如关键词权重),可使用`function_score`查询提升特定关键词的得分(参考引用[^2]中的排序实现)。 #### 3. **关键词排序优化** 对于“推荐关键词排序”,如果需基于多个关键词或自定义权重,使用`function_score`查询增强控制: - 示例:提升包含“recommended”关键词的商品。 ```json "query": { "function_score": { "query": { ... }, // 上述bool查询 "functions": [ { "filter": { "match": { "keywords": "recommended" } }, "weight": 2 // 权重提升 } ], "score_mode": "sum" // 得分累加方式 } } ``` 这能确保匹配推荐关键词的商品在结果中排名更高,类似于引用[^2]中使用的排序技巧[^2]。 #### 4. **完整代码示例(基于Java)** 参考引用[^2]的Velocity模板方法,您可以用Java生成动态DSL。以下是简化示例: ```java import co.elastic.clients.elasticsearch._types.query_dsl.*; import co.elastic.clients.elasticsearch.core.SearchRequest; public PageResult<GoodsEs> searchGoods(GoodsSearchDto dto, Page page) { // 构建bool查询 BoolQuery.Builder boolBuilder = new BoolQuery.Builder(); // 添加过滤条件 boolBuilder.filter(Query.of(q -> q .range(r -> r.field("price").gte(JsonData.of(dto.getMinPrice())).lte(JsonData.of(dto.getMaxPrice()))) )); boolBuilder.filter(Query.of(q -> q .term(t -> t.field("category_code").value(dto.getCategoryCode())) )); boolBuilder.filter(Query.of(q -> q .terms(t -> t.field("tags").terms(t2 -> t2.value(dto.getTags()))) // dto.getTags()为List<String> )); // 添加关键词匹配(影响得分) boolBuilder.must(Query.of(q -> q .match(m -> m.field("keywords").query(dto.getKeyword())) )); // 构建查询 Query query = Query.of(q -> q.bool(boolBuilder.build())); // 设置排序 SortOptions sort = SortOptions.of(s -> s .score(s1 -> s1.order(SortOrder.Desc)) // 按关键词得分降序 ); // 执行搜索 SearchRequest request = SearchRequest.of(s -> s .index("goods_index") .query(query) .sort(sort) .from(page.getOffset()) .size(page.getSize()) ); return goodsMapper.search(request); // 假设goodsMapper处理ES客户端 } ``` - **说明**:此代码基于Elasticsearch Java客户端,动态生成查询。上线后可参考引用[^2]的效果优化性能[^2]。 #### 5. **注意事项** - **性能优化**:为防止字段爆炸(如引用[^1]所述),避免动态字段;使用`flattened`类型处理未知数量标签[^1]。索引时确保文本分析正确(如引用[^4]的流程:字符过滤器 -> 分词器 -> 令牌过滤器)[^4]。 - **测试建议**:在开发环境验证查询响应时间;使用Kibana Dev Tools测试DSL。 - **扩展性**:如果推荐关键词需要语义匹配(如近义词),结合Elasticsearch的全文搜索特性调整分析器。 通过以上步骤,您能高效实现商品搜索的过滤和排序。如果索引数据量大,考虑分片和副本设置提升查询速度。
### 图片文件的 Content-Type 和数据类型的关系 图片文件的 `Content-Type` 是指用于描述资源类型的 MIME 类型。对于图像文件而言,常见的 `Content-Type` 值包括但不限于: - `image/jpeg`: 表示 JPEG 格式的图像文件[^1]。 - `image/png`: 表示 PNG 格式的图像文件。 - `image/gif`: 表示 GIF 格式的动画或静态图像文件。 这些 `Content-Type` 设置不仅帮助服务器理解客户端发送的数据性质,同时也指导浏览器或其他接收端如何处理接收到的内容[^2]。 #### 如何确定图片文件的 Content-Type 为了确保图片能够被正确解析和显示,必须为其指定合适的 `Content-Type` 头部信息。可以通过以下方式来确定特定图片文件对应的 `Content-Type`: 1. **基于扩展名**: 文件系统的默认关联通常是可靠的依据之一。例如 `.jpg`, `.jpeg` 应该对应 `image/jpeg`; 而 `.png` 则应为 `image/png`. 2. **通过工具检测**: 使用命令行工具如 `file` (Linux/Unix), 或者编程库(Python 中有 PIL/Pillow 等),可以直接读取并识别二进制流中的签名位模式以判断其实际格式. 3. **HTTP 请求响应头部检查**: 当从网络获取图片时,查看 HTTP Response Headers 中的 `Content-Type` 字段即可得知服务端声明的媒体类型. #### Elasticsearch 中关键字与数值类型的对比应用到图片元数据存储上 考虑到 Elasticsearch 文档中关于 keyword 及 numeric 类型的选择建议[^5], 如果要保存有关图片的信息,则可以根据需求选择合适的数据类型- 对于那些适合做精确匹配、排序以及聚合运算的关键属性——比如版权归属者的姓名、拍摄地点名称等字符串形式的数据项,应该采用 `keyword` 类型; - 若涉及到尺寸大小(宽度高度像素数)这样的连续量度指标,并且预期会执行区间筛选操作的话,则更适合选用 `numeric` 类型来进行建模。 ```json { "mappings": { "properties": { "filename": {"type": "text", "fields": {"keyword": {"type": "keyword"}}}, "width": {"type": "integer"}, "height": {"type": "integer"} } } } ``` 上述 JSON 片段展示了如何在一个映射定义里同时支持全文检索(`text`)及其精准查找变体(`keyword`), 同时也为整数维度提供了必要的索引配置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值