elasticsearch搜索中文分词理解、类似SQL语句中的"LIKE”条件的模糊搜索以及忽略大小写搜索

本文介绍Elasticsearch在高并发场景下的应用,涵盖索引建立、分词查询、数据检索及JAVA示例等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

elasticsearch作为一款搜索引擎,应用于数据库无法承受前端的搜索压力时,采用其进行数据的搜索。可以大并发架构设计中一种选择,以下是elasticsearch搜索引擎的部分规则,在实际应用中可以让我们快速熟悉和帮助解决一些问题。

01》不进行分词的索引建立

URL: es_index_test

{
  "settings": {
    "index": {
      "number_of_shards": "4",
      "number_of_replicas": "1"
    }
  },
  "mappings": {
    "es_index_type_test": {
      "properties": {
        "productId": {
          "type": "text"
        },
        "productName": {
          "type": "keyword",
          "index": "true"
        }
      }
    }
  }

}

说明:“productName”属性建立索引时,将其设置为不进行分词设置。利用wildcard搜索方式,可以实现MYSQL中的LIKE效果。例如:文档{"productId":10001,"productName":"山鸡图"},可以用{"query":{"wildcard":{"productName":"*鸡*"}}},搜索出来。

02》需要进行分词的索引建立

URL: es_index_test

{
  "settings": {
    "index": {
      "number_of_shards": "4",
      "number_of_replicas": "1"
    }
  },
  "mappings": {
    "es_index_type_test": {
      "properties": {
        "productId": {
        "type": "text"
        },
        "productName": {
          "type": "text",
          "analyzer": "ik_max_word",
          "search_analyzer": "ik_max_word"
        }
      }
    }
  }

}

说明:“productName”属性建立索引时,将其设置为进行分词设置。elasticsearch默认针对中文的分词是按照一个中文字符,就是一个分词。例如:文档{"productId":10001,"productName":"山鸡图"}中,会拆分为“山”、“鸡”和“图”三个分词。中文分词的拆分,可以安装ik分词器进行分词拆分。例如:文档{"productId":10001,"productName":"山鸡图"}中,会拆分为“山鸡”和“图”两个分词。中文短句具体拆分成哪些分词是ik分词器的字典来识别的,此字典可以根据实际情况进行调整。

03》忽略大小写的索引建立

URL: es_index_test
{
  "settings": {
    "index": {
      "number_of_shards": "10",
      "number_of_replicas": "3"
    },
    "analysis": {
      "normalizer": {
        "es_normalizer": {
          "filter": [
            "lowercase",
            "asciifolding"
          ],
          "type": "custom"
        }
      }
    }
  },
  "mappings": {
    "es_index_test": {
      "properties": {
        "productId": {
          "type": "text"
        },
        "productName": {
          "type": "keyword",
          "normalizer": "es_normalizer",
          "index": "true"
        }
      }
    }
  }

}

说明:“productName”属性建立索引时,将其设置为忽略大小写。

04》分词查询

URL:es_index_test/es_index_type_test/_analyze

  • ik分词器以“ik_max_word”方式拆分
{
  "analyzer":"ik_max_word",
  "text":"中华人民共和国"

}

  • 结果

{
  "tokens": [
    {
      "end_offset": 7,
      "start_offset": 0,
      "position": 0,
      "type": "CN_WORD",
      "token": "中华人民共和国"
    },
    {
      "end_offset": 4,
      "start_offset": 0,
      "position": 1,
      "type": "CN_WORD",
      "token": "中华人民"
    },
    {
      "end_offset": 2,
      "start_offset": 0,
      "position": 2,
      "type": "CN_WORD",
      "token": "中华"
    },
    {
      "end_offset": 3,
      "start_offset": 1,
      "position": 3,
      "type": "CN_WORD",
      "token": "华人"
    },
    {
      "end_offset": 7,
      "start_offset": 2,
      "position": 4,
      "type": "CN_WORD",
      "token": "人民共和国"
    },
    {
      "end_offset": 4,
      "start_offset": 2,
      "position": 5,
      "type": "CN_WORD",
      "token": "人民"
    },
    {
      "end_offset": 7,
      "start_offset": 4,
      "position": 6,
      "type": "CN_WORD",
      "token": "共和国"
    },
    {
      "end_offset": 6,
      "start_offset": 4,
      "position": 7,
      "type": "CN_WORD",
      "token": "共和"
    },
    {
      "end_offset": 7,
      "start_offset": 6,
      "position": 8,
      "type": "CN_CHAR",
      "token": "国"
    }
  ]

}

  • ik分词器以“ik_smart”方式拆分

{
  "analyzer":"ik_smart",
  "text":"中华人民共和国"

}

  • 结果

{

"tokens": [

    {
      "end_offset": 7,
      "start_offset": 0,
      "position": 0,
      "type": "CN_WORD",
      "token": "中华人民共和国"
    }
  ]

}

  • ES默认

{
  "text":"中华人民共和国"

}

  • 结果

{

  "tokens": [
    {
      "end_offset": 1,
      "start_offset": 0,
      "position": 0,
      "type": "<IDEOGRAPHIC>",
      "token": "中"
    },
    {
      "end_offset": 2,
      "start_offset": 1,
      "position": 1,
      "type": "<IDEOGRAPHIC>",
      "token": "华"
    },
    {
      "end_offset": 3,
      "start_offset": 2,
      "position": 2,
      "type": "<IDEOGRAPHIC>",
      "token": "人"
    },
    {
      "end_offset": 4,
      "start_offset": 3,
      "position": 3,
      "type": "<IDEOGRAPHIC>",
      "token": "民"
    },
    {
      "end_offset": 5,
      "start_offset": 4,
      "position": 4,
      "type": "<IDEOGRAPHIC>",
      "token": "共"
    },
    {
      "end_offset": 6,
      "start_offset": 5,
      "position": 5,
      "type": "<IDEOGRAPHIC>",
      "token": "和"
    },
    {
      "end_offset": 7,
      "start_offset": 6,
      "position": 6,
      "type": "<IDEOGRAPHIC>",
      "token": "国"
    }
  ]

}

说明:以上三种分词拆分的方式不一样,最终产生分词的结果不相同。

05》数据查询-wildcard

URL: es_index_test/es_index_type_test/_search
{
  "query":{"wildcard":{"productName": "山鸡图" }}

}

说明:wildcard种查询方式需要结合方法支持匹配符合,例如:*鸡*,ES会去匹配,在JAVA程序中构建采用。JAVA程序中采用QueryBuilders类的wildcardQuery(String name, Object text)方法。

06》数据查询-match

URL:es_index_test/es_index_type_test/_search
{
  "query":{"match":{"productName": "山鸡图" }}

}

说明:查询时会根据分词进行匹配,例如:“山鸡图”ES拆分为“山鸡”和“图”两个分词到ES搜索引擎内筛选出记录,最后将符合记录的数据返回。返回的记录可能包含,山鸡汤(包含“山鸡”)和山虎图(包含“图”分词)。JAVA程序中采用QueryBuilders类的matchQuery(String name, Object text)方法。

07》数据查询-term

URL: es_index_test/es_index_type_test/_search
{
  "query":{
    "term":{
      "productName":"山鸡图"
    }
  }
}
说明:只有分词完全匹配“山鸡图”这三个字后,才可以返回数据。JAVA程序中采用QueryBuilders类的termQuery(String name, Object value)方法。

08》数据查询-terms

URL: es_index_test/es_index_type_test/_search

{
  "query":{
    "terms":{
      "productName":["山鸡图","山虎图"]
    }
  }

}

说明:分词匹配“山鸡图”和“山虎图”返回记录。JAVA程序中采用QueryBuilders类的termsQuery(String name, String... values)方法。

09》删除查询出来的结果集

URL:es_index_test/es_index_type_test/_delete_by_query

{
  "query":{"wildcard":{"productName": "*鸡*" }}

}

说明:删除产品名称包含“鸡”字文档。

10》elasticsearch中JAVA实例

1、ElasticSearchProperties
package com.jd.ccc.sys.biz.yb.op.notice.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import lombok.Data;

/**
 * ElasticSearch搜索引擎配置参数 
 * 具体参数的配置信息在yml文件内
 * 
 * @create 2018-5-10
 * @author zhangqiang200<https://blog.youkuaiyun.com/zhangqiang_accp>
 *
 */
@Data
@Component
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticSearchProperties {
   /**
    * 集群名
    */
   private String clusterName;
   /**
    * 索引名称
    */
   private String indexName;

   /**
    * 类型名称
    */
   private String typeName;

   /**
    * 主节点
    */
   private String masterNode;

   /**
    * 从节点
    */
   private String slaveNodes;

}
2、ElasticSearchConfig
package com.jd.ccc.sys.biz.yb.op.notice.config;

import java.net.InetAddress;
import java.net.UnknownHostException;

import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 
 * 初始化一个ES搜索引擎配置
 * 
 * @create 2018-5-10
 * @author zhangqiang200<https://blog.youkuaiyun.com/zhangqiang_accp>
 *
 */
@Configuration
@EnableConfigurationProperties(ElasticSearchProperties.class)
public class ElasticSearchConfig {
   
    private static final Logger LOGGER = LoggerFactory.getLogger(ElasticSearchConfig.class);
   
   @Autowired
   private ElasticSearchProperties elasticSearchProperties;
   
   private static final String SYS_PROPERTY="es.set.netty.runtime.available.processors";
   
   private static final String CLUSTER_NAME="cluster.name";
   
   private static final String CLIENT_SNIFF="client.transport.sniff";

   @Bean(name="elasticSearchCluster")
   public Client getClient() {

      System.setProperty(SYS_PROPERTY, "false");
      Settings settings = Settings.builder().put(CLUSTER_NAME, elasticSearchProperties.getClusterName())
            .put(CLIENT_SNIFF, false).build();
      
      TransportClient transportClient = null;
      try {

         String[] masters = elasticSearchProperties.getMasterNode().split(":");
         transportClient = new PreBuiltTransportClient(settings).addTransportAddress(
               new InetSocketTransportAddress(InetAddress.getByName(masters[0]), Integer.parseInt(masters[1])));

         String[] slaveNodes = elasticSearchProperties.getSlaveNodes().split(",");// 逗号分隔

         //遍历从库信息
         for (String node : slaveNodes) {
            String[] ipPort = node.split(":");// 冒号分隔
            if (ipPort.length == 2) {
               transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(ipPort[0]),
                     Integer.parseInt(ipPort[1])));
            }
         }
         return transportClient;
      } catch (UnknownHostException e) {
         LOGGER.error("ES 客户端连接失败.{}",e);
         return null;
      }
   }

}
3、服务层操作
/**
 * 查询模糊搜索产品列表的总记录数
 *
 * @param likeProductName
 *            模糊搜索产品名称的关键字
 * @param type
 *            产品类型
 * @return 总记录数
 *
 * @create 2018-5-9
 * @author zhangqiang200<https://blog.youkuaiyun.com/zhangqiang_accp>
 */
private Integer queryCount(String likeProductName, String type) {
    BoolQueryBuilder builder=this.builderQueryData(likeProductName, type);
    try {
        SearchResponse searchResponse = elasticSearchCluster.prepareSearch(elasticSearchProperties.getIndexName())
                .setTypes(elasticSearchProperties.getTypeName()).setQuery(builder)
                .setSearchType(SearchType.DEFAULT).get();
        SearchHits hits = searchResponse.getHits();
        return (int)hits.getTotalHits();
    }catch(Exception e) {
        LOGGER.error("Server access failure,{}",e);
        return 0;
    }
}

/**
 * 拼接模糊查询筛选条件
 *
 * @param likeProductName
 *            模糊搜索产品名称的关键字
 * @param type
 *            产品类型
 * @return 筛选条件字符串
 *
 * @create 2018-5-9
 * @author zhangqiang200<https://blog.youkuaiyun.com/zhangqiang_accp>
 */
private BoolQueryBuilder builderQueryData(String likeProductName, String type) {
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    boolQueryBuilder.must(QueryBuilders.matchQuery(PRODUCT_STATUS, "03"));
    if(StringUtils.isNotBlank(likeProductName)) {
        boolQueryBuilder.must(QueryBuilders.wildcardQuery(PRODUCT_NAME,"*"+likeProductName+"*"));
    }
    // 类型不为空
    if (StringUtils.isNotBlank(type)) {
        String[] types = type.split(",");
        if (types.length == 1) {
            boolQueryBuilder.must(QueryBuilders.matchQuery(INST_TYPE,type));
        } else {
            boolQueryBuilder.must(QueryBuilders.termsQuery(INST_TYPE, types));
        }
    }
    LOGGER.debug("wild card query-->{}",boolQueryBuilder.toString());
    return boolQueryBuilder;
}

/**
 * 模糊查询商品列表数据
 * @param likeProductName 模糊搜索产品名称的关键字
 * @param type 产品类型
 * @param startIndex 开始索引
 * @param pageSize 每页大小
 * @returnW
 *
 * @create 2018-5-9
 * @author zhangqiang200<https://blog.youkuaiyun.com/zhangqiang_accp>
 */
private List<String> queryData(String likeProductName, String type, int startIndex, int pageSize) {
    List<String> resultList = new ArrayList<>();
    BoolQueryBuilder builder=this.builderQueryData(likeProductName, type);
    try {

        SearchResponse searchResponse = elasticSearchCluster.prepareSearch(elasticSearchProperties.getIndexName())
                .setTypes(elasticSearchProperties.getTypeName()).setQuery(builder)
                .setSearchType(SearchType.DEFAULT).setFrom(startIndex).setSize(pageSize).get();
        SearchHit[] hits = searchResponse.getHits().getHits();


        for (SearchHit hit : hits) {
            resultList.add(hit.getSourceAsString());
        }
    }catch(Exception e) {
        LOGGER.error("Server access failure,{}",e);
    }

    return resultList;
}

<think>好的,用户想在Elasticsearch中实现类似SQLLIKE '91%'的功能,也就是匹配以'91'开头的数据。我需要先回忆一下Elasticsearch中用于模糊查询的方法。 首先,Elasticsearch不像SQL那样直接支持LIKE操作符,但有几种方式可以实现类似效果。常见的模糊查询方法有wildcard查询、prefix查询,还有使用ngram分词器配合match_phrase_prefix等方法。 用户提到的是以'91%'开头,也就是前缀匹配。这时候,prefix查询可能是最直接的选择,因为它专门用来匹配以特定前缀开头的词项。不过要注意的是,prefix查询对大小写敏感,并且要求字段是keyword类型,因为它是基于词项的精确匹配。如果字段是text类型,可能需要使用子字段.keyword来进行查询。 另外,wildcard查询也支持通配符,比如使用'91*'来匹配以91开头的任何字符串。但wildcard查询在性能上可能不如prefix高效,尤其是在处理大量数据时,因为它需要扫描更多的词项。同时,通配符查询的开销较大,特别是如果模式以通配符开头的话,比如'*91',但这里的情况是前缀,所以性能影响可能较小。 还有一种方法是使用ngram分词器,将文本分解成更小的片段,然后进行匹配。例如,设置min_gram为2,这样每个两个字符的组合都会被索引。然后可以使用match_phrase_prefix来查找以'91'开头的短语。这种方法可能在索引时增加存储开销,但查询时会更高效,尤其是对于较长的前缀。 接下来需要考虑用户的实际应用场景。如果用户的数据量不大,或者查询频率不高,使用prefix或wildcard查询可能已经足够。但如果有高性能需求或大数据量,可能需要优化索引结构,比如使用ngram分词器。 同时,还要提醒用户注意字段的类型。如果字段是text类型,默认情况下会被分词,而prefix或wildcard查询需要的是未分词的keyword类型。因此,正确的字段映射非常重要。例如,确保字段是keyword类型,或者在text类型字段中有一个keyword子字段。 例如,用户的映射可能需要类似这样的结构: ```json { "mappings": { "properties": { "field_name": { "type": "text", "fields": { "keyword": { "type": "keyword" } } } } } } ``` 这样,在查询时可以使用field_name.keyword来进行前缀匹配。 此外,还需要考虑查询的性能问题。prefix查询在Lucene中是优化的,因为它可以利用倒排索引的结构来快速定位前缀匹配的文档。而wildcard查询可能需要遍历更多的词项,特别是当通配符模式复杂时。因此,在可能的情况下,优先使用prefix查询。 最后,用户可能需要查看实际返回的结果是否符合预期。如果存在大小写不一致的情况,可能需要将数据规范化(如全部转为小写)或在查询时进行处理,比如使用通配符时忽略大小写,但Elasticsearch的wildcard查询默认是区分大小写的,所以需要注意这一点。 总结下来,可能的解决方案有: 1. 使用prefix查询,适用于keyword类型的字段,匹配以'91'开头的精确前缀。 2. 使用wildcard查询,支持更复杂的通配符模式,但性能可能较低。 3. 使用ngram分词器结合match_phrase_prefix,适合更灵活的匹配,但需要调整索引设置。 需要根据用户的具体需求和数据情况推荐最合适的方案,并提醒相关的注意事项,如字段类型、性能影响和大小写敏感问题。</think>在Elasticsearch中实现类似SQL的`LIKE '91%'`功能,可以通过以下方法实现: --- ### 方法1:使用`prefix`查询(推荐) 适用于精确前缀匹配,要求字段类型为`keyword`: ```python from elasticsearch import Elasticsearch es = Elasticsearch(hosts=["http://localhost:9200"], timeout=30) query = { "query": { "prefix": { "field_name.keyword": { # 替换为实际字段名 "value": "91" } } } } result = es.search(index="your_index", body=query) ``` - **原理**:直接匹配以`91`开头的词项,性能高效[^3] - **注意**:字段需为`keyword`类型(或使用`.keyword`子字段) --- ### 方法2:使用`wildcard`查询 支持更复杂的通配符模式: ```python query = { "query": { "wildcard": { "field_name.keyword": { "value": "91*" # *表示任意字符 } } } } ``` - **特点**:支持`*`(多字符)和`?`(单字符)通配符 - **缺点**:性能略低于`prefix`查询,避免开头使用通配符(如`*91`) --- ### 方法3:使用`ngram`分词器(大数据量优化) 1. **定义索引映射**: ```json { "settings": { "analysis": { "analyzer": { "ngram_analyzer": { "tokenizer": "ngram_tokenizer" } }, "tokenizer": { "ngram_tokenizer": { "type": "ngram", "min_gram": 2, "max_gram": 3 } } } }, "mappings": { "properties": { "field_name": { "type": "text", "analyzer": "ngram_analyzer" } } } } ``` 2. **查询语句**: ```python query = { "query": { "match_phrase_prefix": { "field_name": "91" } } } ``` --- ### 关键差异对比 | 方法 | 适用场景 | 性能 | 字段类型要求 | |-----------|--------------------|-----|--------------| | `prefix` | 简单前缀匹配 | 高 | `keyword`类型 | | `wildcard` | 复杂模式匹配 | 中 | `keyword`类型 | | `ngram` | 高频前缀查询的大数据场景 | 最高 | 需自定义分词器 | --- ### 注意事项 1. 若字段为`text`类型,需通过`字段名.keyword`访问原始值 2. 避免在`wildcard`中使用`*`开头,会导致全索引扫描 3. 使用`ngram`分词器会显著增加索引存储空间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值