ElasticSearch 高级搜索

100 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情


⭐️前面的话⭐️

✉️坚持和努力一定能换来诗与远方!
💭推荐书籍:📚《王道408》,📚《深入理解 Java 虚拟机-周志明》,📚《Java 核心技术卷》
💬算法刷题:✅力扣🌐牛客网
🎈Github
🎈码云Gitee


1 DSL搜索

• Domain Specific Language
• 特定领域语言
• 基于JSON格式的数据查询
• 查询更灵活,有利于复杂查询

1.0 创建索引、同时创建映射;创建文档

PUT /index_user
{
    "settings":{
        "index":{
            "number_of_shards":5,
            "number_of_replicas":0
        }
    },
    "mappings" : {
        "properties":{
            "user_id":{
                "type":"long"
            },
            "name":{
                "type":"text",
                "analyzer":"ik_max_word"
            },
            "login_name":{
                "type":"keyword"
            },
            "age":{
                "type":"integer"
            },
            "birthday":{
                "type":"date"
            },
            "desc":{
                "type":"text",
                "analyzer":"ik_max_word"
            },
            "head_url":{
                "type":"text",
                "index":false
            }
        }
    }
}

添加 3 个文档

POST /index_user/_doc/1
{
    "user_id":"1",
    "name":"僵尸猎手",
    "login_name":"jsls",
    "age":25,
    "birthday":"1990-03-01",
    "desc":"我是一名房产经纪人,现在转行了,目前是一名运输工人",
    "head_url":"https://www.zhuifengren.cn/img/jsls.jpg"
}

POST /index_user/_doc/2
{
    "user_id":"2",
    "name":"夏维尔",
    "login_name":"xwe",
    "age":28,
    "birthday":"1992-06-06",
    "desc":"我是一名高级开发经理,每天坐地铁上班,在北京住,从不堵车",
    "head_url":"https://www.zhuifengren.cn/img/xwe.jpg"
}

POST /index_user/_doc/3
{
    "user_id":"3",
    "name":"迪士尼在逃仙柔",
    "login_name":"dsnzxr",
    "age":10,
    "birthday":"2011-06-22",
    "desc":"我是一名五年级的小学生,每天专车接专车送,中午在学校入伙,食堂菜可好了,上学期期末考试我拿了三好学生奖",
    "head_url":"https://www.zhuifengren.cn/img/dsnzxr.jpg"
}

1.1 查询所有 match_all

POST /index_user/_search
{
    "query":{
        "match_all":{}
    }
}

1.2 match查询

会将关键字先分词,然后用每一个分词去查询,最后将结果取并集

POST /index_user/_search
{
    "query":{
        "match":{
            "desc":"高级地铁运输"
        }
    }
}

match查询扩展1(match查询后,结果取交集)

POST /index_user/_search
{
    "query":{
        "match":{
            "desc": {
                "query": "一名小学生",
                "operator": "and"
            }
        }
    }
}

match查询扩展2(指定匹配率)

POST /index_user/_search
{
    "query":{
        "match":{
            "desc": {
                "query": "一名小学生坐地铁",
                "minimum_should_match":"6"  // 或 "minimum_should_match":"60%"
            }
        }
    }
}

1.3 term查询(不分词)

不会将关键字分词,直接拿来查询。

POST /index_user/_search
{
    "query":{
        "term":{
            "desc":"一名地铁"
        }
    }
}

1.4 terms查询

与term查询类似,可以写多个关键字,会用每个关键字去查询,最后将结果取并集。

POST /index_user/_search
{
    "query":{
        "terms":{
            "desc":[
                "一名",
                "小学生"
            ]
        }
    }
}

1.5 match的多字段查询

POST /index_user/_search
{
    "query":{
        "multi_match":{
            "query":"一名小学生 僵尸25",
            "fields":[
                    "desc",
                    "name"    // "name^10"
                ]
        }
    }
}

1.6 match_phrase查询

与match类似,会先将关键字分词,然后用每个分词去查询,但会对文档中分词间的间隔有一定限制,使用slop属性去限制,默认是0,需要小于设置的间隔,才能匹配文档。

POST /index_user/_search
{
    "query":{
        "match_phrase":{
            "desc":{
                "query": "一名 学生",
                "slop":8
            }
        }
    }
}

1.7 文档中是否存在某字段 exists

文档中存在该字段,就会被查出来。

POST /index_user/_search
{
    "query":{
        "exists":{
            "field":"name"
        }
    }
}

1.8 查询部分字段 _source

POST /index_user/_search
{
    "query":{
        "match_all":{}
    },
    "_source": [
        "user_id",
        "name"
    ]
}

1.9 分页 from size

POST /index_user/_search
{
    "query":{
        "match_all":{}
    },
    "from":0,   // 从哪条文档开始,文档下标从 0 开始
    "size":2   // 每页多少条文档
}

1.10 使用id集合查询

POST /index_user/_search
{
    "query": {
        "ids":{
            "type":"_doc",
            "values":["1","3"]
        }
    }
}

1.11 布尔查询

  • 参数中可以包含多种条件的组合。
    • 其中 must 块下的条件,文档必须都符合才会被查出来。
    • must_not 块下的条件,文档必须都不符合才会被查出来。
    • should 块下的条件,文档只要符合一个就能被查出来。
  • 最终结果集是 must、must_not、should 块查询结果的交集
POST /index_user/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "desc": "一名"
                    }
                },
                {
                    "match": {
                        "desc": "小学生"
                    }
                }
            ],
            "must_not": [],
            "should": [
                {
                    "match": {
                        "desc": "一名"
                    }
                },
                {
                    "match": {
                        "desc": "小学生"
                    }
                }
                
            ]
        }
    }
}

1.12 为某个查询加权

"match": {
    "desc": {
        "query": "一名",
        "boost":10
    }
}

1.13 结果集过滤 post_filter

从ES查询后,再将结果集过滤一次。

  • 支持范围查询:
    •   gte:大于等于
    •   lte:小于等于
    •   gt:大于
    •   lt:小于
  • 也支持 term、match 操作。
POST /index_user/_search
{
    "query":{
        "match":{
            "desc":"一名"
        }
    },
    "post_filter": {
        "range" : {
            "age": {
                "gt":10,
                "lt":26
            }
        }
    }
}

1.14 排序 sort

POST /index_user/_search
{
    "query":{
        "match":{
            "desc":"一名"
        }
    },
    "sort":[
        {
            "age":"desc"
        },
        {
            "user_id":"asc"
        },
        {
            "login_name":"asc"
        }
    ]
}

1.15 高亮

高亮效果就是在匹配的关键字上增加标签,便于前端去高亮显示。

POST /index_user/_search
{
    "query":{
        "match":{
            "desc":"一名小学生"
        }
    },
    "highlight":{
        "pre_tags":"<span>",
        "post_tags":"</span>",
        "fields":{
            "desc":{}
        }
    }
}

2 批量操作

mget

3 分词器

下载地址:github.com/medcl/elast…

下载后解压到 es 的 plugins 文件夹,重启es即可。

分词器是Elasticsearch中很重要的一个组件,用来将一段文本分析成一个一个的词,Elasticsearch再根据这些词去做倒排索引。

3.1 内置分词器

Elasticsearch 中内置了一些分词器,这些分词器只能对英文进行分词处理,无法将中文的词识别出来。

  • standard:标准分词器,是Elasticsearch中默认的分词器,可以拆分英文单词,大写字母统一转换成小写。
  • simple:按非字母的字符分词,例如:数字、标点符号、特殊字符等,会去掉非字母的词,大写字母统一转换成小写。
  • whitespace:简单按照空格进行分词,相当于按照空格split了一下,大写字母不会转换成小写。
  • stop:会去掉无意义的词,例如:the、a、an 等,大写字母统一转换成小写。
  • keyword:不拆分,整个文本当作一个词。

查看分词效果通用接口

GET /_analyze
{
    "analyzer": "standard",  
    "text": "I am a man."
}
{
  "tokens" : [
    {
      "token" : "i",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "am",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "a",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "man",
      "start_offset" : 7,
      "end_offset" : 10,
      "type" : "<ALPHANUM>",
      "position" : 3
    }
  ]
}

3.2 LK 分词器

Elasticsearch中内置的分词器不能对中文进行分词,因此我们需要再安装一个能够支持中文的分词器,IK分词器就是个不错的选择。

ik_max_word: 会将文本做最细粒度的拆分,适合 Term Query

ik_smart: 会做最粗粒度的拆分,适合 Phrase 查询。

分词效果如下:

GET /_analyze
{
    "analyzer": "ik_max_word", 
    "text": "我是一名Java高级程序员"
}
{
  "tokens" : [
    {
      "token" : "我",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "CN_CHAR",
      "position" : 0
    },
    {
      "token" : "是",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "CN_CHAR",
      "position" : 1
    },
    {
      "token" : "一名",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "一",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "TYPE_CNUM",
      "position" : 3
    },
    {
      "token" : "名",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "COUNT",
      "position" : 4
    },
    {
      "token" : "java",
      "start_offset" : 4,
      "end_offset" : 8,
      "type" : "ENGLISH",
      "position" : 5
    },
    {
      "token" : "高级",
      "start_offset" : 8,
      "end_offset" : 10,
      "type" : "CN_WORD",
      "position" : 6
    },
    {
      "token" : "程序员",
      "start_offset" : 10,
      "end_offset" : 13,
      "type" : "CN_WORD",
      "position" : 7
    },
    {
      "token" : "程序",
      "start_offset" : 10,
      "end_offset" : 12,
      "type" : "CN_WORD",
      "position" : 8
    },
    {
      "token" : "员",
      "start_offset" : 12,
      "end_offset" : 13,
      "type" : "CN_CHAR",
      "position" : 9
    }
  ]
}

3.3 自定义词库

在进行中文分词时,经常出现分析出的词不是我们想要的,这时我们就需要在IK分词器中自定义我们自己词库。

例如:追风人,分词后,只有 追风 和 人,而没有 追风人,导致倒排索引后查询时,用户搜 追风 或 人 可以搜到 追风人,搜 追风人 反而搜不到 追风人。

# cd /usr/local/elasticsearch-7.14.1/plugins/ik/config

# vi IKAnalyzer.cfg.xml

在配置文件中增加自己的字典

# vi my.dic

在文本中加入 追风人,保存。

4 SpringBoot 整合

ElasticsearchTemplate 替换为 ElasticsearchRestTemplateTransport Client 替换为 RestHighLevelClient

在 Elasticsearch 7.0 中不建议使用 TransportClient,并且在8.0中会完全删除TransportClient。

因此,官方更建议我们用 Java High Level REST Client,它执行 HTTP 请求,而不是序列化的 Java 请求。

我们的使用的 ElasticsearchRestTemplate 就是基于RestHighLevelClient的再一层封装。

4.1 整合

1个依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

yml 配置

server:
  port: 8080

spring:
  elasticsearch:
    rest:
      uris: http://127.0.0.1:9200  #多个地址用逗号分隔
#      username:     #用户名
#      password:     #密码
      connection-timeout: 6000
      read-timeout: 6000

4.2 操作

新建一个ES的entity 类:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "user", shards = 3, replicas = 1, refreshInterval = "30s")
public class UserEsEntity {
    @Id
    private String id;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_max_word", analyzer = "ik_smart")
    private String name;
    @Field(type = FieldType.Keyword)
    private String sex;
    @Field(type = FieldType.Integer)
    private Integer age;
    @Field(type = FieldType.Keyword, index = false)
    private String grade;
}

新建一个ElasticsearchRepositoryRepository中很多方法都被标了过时,如下

@Repository
public interface UserEsDao extends ElasticsearchRepository<UserEsEntity, Long> {
}

对于查询已经不建议使用ElasticsearchRepository了,直接学习ElasticsearchRestTemplate 的Api。

参考