Elasticsearch Query DSL之全文检索(Full text queries)上篇

Stella981
• 阅读 977

全文查询包括如下几种模式:

  • match query

  • match_phrase query

  • match_phrase_prefix query

  • multi_match query

  • common terms query

  • query_string query

  • simple_query_string query

接下来我们详细介绍上述查询模式。

1、match query

标准的全文检索模式,包含模糊匹配、前缀或近似匹配等。

2、match_phrase query

与match query类似,但只是用来精确匹配的短语。

3、match_phrase_prefix query

与match_phrase查询类似,但是在最后一个单词上执行通配符搜索。

4、multi_match query

支持多字段的match query。

5、common terms query

相比match query,消除停用词与高频词对相关度的影响。

6、query_string query

查询字符串方式

7、simple_query_string query

简单查询字符串方式

本篇主要介绍前四种全文检索方式。

1、match query详解

1.1 match query使用示例与基本工作原理

全文索引查询,这意外着首先会对待查字符串(查询条件)进行分词,然后再去匹配,返回结果中会待上本次匹配的关联度分数。

例如存在这样一条数据:

"_source":{
   "post_date":"2009-11-16T14:12:12",
   "message":"trying out Elasticsearch",
    "user":"dingw2"
}

查询字符串:

"query": {         
    "match" : {             
          "message" : "this out Elasticsearch"         
    } 
}

其JAVA代码对应:

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchQuery("message", "this out elasticsearch"));

其大体步骤如下:

首先对this out Elasticsearch分词,最终返回结果为 this、out、Elasticsearch,然后分别去库中进行匹配,默认只要一个匹配,就认为匹配,但会加入一个匹配程度(关联度),用scoce分数表示。

1.2 match query常用参数详解

  • operator(操作类型)

    可选值为:Operator.OR 和 Operator.AND。表示对查询字符串分词后,返回的词根列表,OR只需一个满足及认为匹配,而AND则需要全部词根都能匹配,默认值为:Operator.OR。

  • minimum_should_match

    最少需要匹配个数。在操作类型为Operator.OR时生效,指明分词后的词根,至少minimum_should_match 个词根匹配,则命中。

    "match" : {            "message" : "this out Elasticsearch",    “minimum_should_match ”:“3”         }

此时由于this词根并不在原始数据"trying out Elasticsearch"中,又要求必须匹配的词根个数为3,故本次查询,无法命中。minimum_should_match 可选值如下:

Type

Example

Description

Integer

3

直接数字,不考虑查询字符串分词后的个数。如果分词的个数小于3个,则无法匹配到任何条目。

Negative integer

-2

负数表示最多不允许不匹配的个数。也就是需要匹配的个数为(total-2)。

Percentage

75%

百分比,表示需要匹配的词根占总数的百分比。

Negative percentage

-25%

允许不匹配的个数占总数的百分比。

Combination

3<90%

如果查询字符串分词的个数小于等于3(前面的整数),则只要全部匹配则返回,如果分词的个数大于3个,则只要90%的匹配即可。

Multiple combinations

2<-25% 9<-3

支持多条件表达式,中间用空格分开。该表达式的意义如下:1、如果分词的个数小于等于2,则必须全部匹配;如果大于2小于9,则除了25%(注意负号)之外都需要满足。2、如果大于9个,则只允许其中3个不满足。

  • analyzer

设置分词器,默认使用字段映射中定义的分词器或elasticsearch默认的分词器。

  • lenient

是否忽略由于数据类型不匹配引起的异常,默认为false。例如尝试用文本查询字符串查询数值字段,默认会抛出错误。

  • fuzziness

模糊匹配。

  • zero_terms_query

默认情况下,如果分词器会过滤查询字句中的停用词,可能会造成查询字符串分词后变成空字符串,此时默认的行为是无法匹配到任何文档,如果想改变该默认情况,可以设置zero_terms_query=all,类似于match_all,默认值为none。

  • cutoff_frequency

match查询支持cutoff_frequency,允许指定绝对或相对的文档频率:

  • OR:高频单词被放入“或许有”的类别,仅在至少有一个低频(低于cutoff_frequency)单词满足条件时才积分;

  • AND:高频单词被放入“或许有”的类别,仅在所有低频(低于cutoff_frequency)单词满足条件时才积分。该查询允许在运行时动态处理停用词而不需要使用停用词文件。它阻止了对高频短语(停用词)的评分/迭代,并且只在更重要/更低频率的短语与文档匹配时才会考虑这些文档。然而,如果所有查询条件都高于给定的cutoff_frequency,则查询将自动转换为纯连接(and)查询,以确保快速执行。

cutoff_frequency取值是相对于文档的总数的小数[0..1),也可以是绝对值[1, +∞)。

  • Synonyms(同义词)

可在分词器中定义同义词,具体同义词将在后续章节中会单独介绍。

1.3 match query示例

public static void testMatchQuery() {
        RestHighLevelClient client = EsClient.getClient();
        try {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("twitter");
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(
                    QueryBuilders.matchQuery("message", "is out Elasticsearch")
                        .zeroTermsQuery(ZeroTermsQuery.ALL)
                        .operator(Operator.OR)
                        .minimumShouldMatch("4<90%")
                    ).sort(new FieldSortBuilder("post_date").order(SortOrder.DESC))
                     .docValueField("post_date", "epoch_millis");
            searchRequest.source(sourceBuilder);
            SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(result);
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            EsClient.close(client);
        }
    }

2、match_phrase query

与match query类似,但只是用来精确匹配的短语。

其主要工作流程:

首先,Elasearch(lucene)会使用分词器对全文本进行分词(返回一个一个的词根(顺序排列)),然后同样使用分词器对查询字符串进行分析,返回一个一个的词根(顺序性)。如果能在全字段中能够精确找到与查询字符串通用的词根序列,则认为匹配,否则认为不匹配。

举例如下:

如果原文字段message:"quick brown fox test we will like to you",则使用标准分词器(analyzer=standard)返回的结果如下:

使用命令:

curl -X GET "192.168.1.10:9200/_analyze" -H 'Content-Type: application/json' -d'
{
  "tokenizer" : "standard",
  "text" : "quick brown fox test we will like to you",
  "attributes" : ["keyword"] 
}'

得出如下结果:

{
    "tokens":[
        {
            "token":"quick",
            "start_offset":0,
            "end_offset":5,
            "type":"<ALPHANUM>",
            "position":0
        },
        {
            "token":"brown",
            "start_offset":6,
            "end_offset":11,
            "type":"<ALPHANUM>",
            "position":1
        },
        {
            "token":"fox",
            "start_offset":12,
            "end_offset":15,
            "type":"<ALPHANUM>",
            "position":2
        },
        {
            "token":"test",
            "start_offset":16,
            "end_offset":20,
            "type":"<ALPHANUM>",
            "position":3
        },
        {
            "token":"we",
            "start_offset":21,
            "end_offset":23,
            "type":"<ALPHANUM>",
            "position":4
        },
        {
            "token":"will",
            "start_offset":24,
            "end_offset":28,
            "type":"<ALPHANUM>",
            "position":5
        },
        {
            "token":"like",
            "start_offset":29,
            "end_offset":33,
            "type":"<ALPHANUM>",
            "position":6
        },
        {
            "token":"to",
            "start_offset":34,
            "end_offset":36,
            "type":"<ALPHANUM>",
            "position":7
        },
        {
            "token":"you",
            "start_offset":37,
            "end_offset":40,
            "type":"<ALPHANUM>",
            "position":8
        }
    ]
}

其词根具有顺序性(词根序列)为quick、brown、fox、test 、we 、will、 like、 to 、you

如果查询字符串为 quick brown,分词后的词根序列为 quick brown,则是原词根序列的子集,则匹配。

如果查询字符串为 quick fox,分词后的词根序列为 quick fox,与原词根序列不匹配。如果指定slop属性,设置为1,则匹配,其表示每一个词根直接跳过一个词根形成新的序列,与搜索词根进行比较,是否匹配。

如果查询字符串为quick fox test,其特点是quick与原序列跳过一个词brown,但fox后面不跳过任何次,与test紧挨着,如果指定slop=1,同样能匹配到文档,但查询字符串quick fox test will,却匹配不到文档,说明slop表示整个搜索词根中为了匹配流,能跳过的最大次数。

按照match_phrase的定义,与match query的区别一个在与精确匹配,一个在于词组term(理解为词根序列),故match_phrase与match相比,不会有如下参数:fuzziness、cutoff_frequency、operator、minimum_should_match 这些参数。

3、match phrase prefix query

与match phrase基本相同,只是该查询模式会对最后一个词根进行前缀匹配。

GET /_search
{
    "query": {
        "match_phrase_prefix" : {
            "message" : {
                "query" : "quick brown f",
                "max_expansions" : 10
            }
        }
    }
}

其工作流程如下:首先先对除最后一个词进行分词,得到词根序列 quick brown,然后遍历整个elasticsearch倒排索引,查找以f开头的词根,依次组成多个词根流,例如(quick brown fox) (quick brown foot),默认查找50组,受参数max_expansions控制,在使用时请设置合理的max_expansions,该值越大,查询速度将会变的更慢。该技术主要完成及时搜索,指用户在输入过程中,就根据前缀返回查询结果,随着用户输入的字符越多,查询的结果越接近用户的需求。

4、multi match query

multi_match查询建立在match查询之上,允许多字段查询。

GET /_search
{
  "query": {
    "multi_match" : {
      "query":    "this is a test", 
      "fields": [ "subject", "message" ]   // @1
    }
  }
}

@1执行作用(查询)的字段,有如下几种用法:

1、[ "subject", "message" ] ,表示针对查询自动对subject,message字段进行查询匹配。

2、[ "title", "*_name" ],支持通配符,表示对title,以_name结尾的字段进行查询匹配。

3、[ "subject^3", "message" ],表示subject字段是message的重要性的3倍,类似于字段权重。

4.1 multi_query重要参数详解

4.1.1 type 属性

指定multi_query内部的执行方式,取值如下:best_fields、most_fields、cross_fields、phrase、phrase_prefix。

1、best_fields

type默认值,只要其中一个字段匹配则匹配文档(match query)。但是使用最佳匹配的字段的score来表示文档的分数,会影响文档的排序。

例如有如下两个文档,id,title,context字段值分别如下:

doc1 :  1  "Quick brown rabbits"   "Brown rabbits are commonly seen brown."
doc2:2  "Keeping pets healthy", "My quick brown as fox eats rabbits on a regular basis."

如果查询字段“brown fox”字符串,两个文档的匹配度谁高呢?初步分析如下:查询字符串"brown fox"会被分词为brown、fox两个词根,首先brown在doc1的title、context中都能匹配brown,而且次数为3次,在doc2中,只有在context字段中匹配到brown fox各一次,那哪个相关度(评分score)。

best_fields类型,认为在同一个字段能匹配到更多的查询字符串词根,则认为该字段更佳。由于doc2的context字段能匹配到两个查询词根,故doc2的匹配度更高,doc2会优先返回,对应测试代码:

    public static void testMultiQueue_best_fields() {
        RestHighLevelClient client = EsClient.getClient();
        try {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("esdemo");
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(
                    QueryBuilders.multiMatchQuery("brown fox", "title","context")
                        .type(Type.BEST_FIELDS)
                    );
            searchRequest.source(sourceBuilder);
            SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(result);
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            EsClient.close(client);
        }
    }

执行的查询结果如下:

{
    "took":4,
    "timed_out":false,
    "_shards":{
        "total":5,
        "successful":5,
        "skipped":0,
        "failed":0
    },
    "hits":{
        "total":2,
        "max_score":0.5753642,
        "hits":[
            {
                "_index":"esdemo",
                "_type":"matchquerydemo",
                "_id":"2",
                "_score":0.5753642,
                "_source":{
                    "context":"My quick brown as fox eats rabbits on a regular basis.",
                    "title":"Keeping pets healthy"
                }
            },
            {
                "_index":"esdemo",
                "_type":"matchquerydemo",
                "_id":"1",
                "_score":0.2876821,
                "_source":{
                    "context":"Brown rabbits are commonly seen.",
                    "title":"Quick brown rabbits"
                }
            }
        ]
    }
}

best_fields类型内部会转换为(dis_max):

GET /_search 
{   
    "query": {     
        "dis_max": {       
                "queries": [         
                    { "match": { "subject": "brown fox" }},         
                    { "match": { "message": "brown fox" }}       
                ],       
                "tie_breaker": 0.3     
          }   
       } 
}

通常best_fields类型使用单个最佳匹配字段的分数,但如果指定了tie_breaker,则其计算结果如下:最佳匹配字段的分数加上 tie_breaker * _score(其他匹配字段分数)。该查询模式支持match query相关的参数,例如analyzer, boost, operator, minimum_should_match, fuzziness, lenient, prefix_length, max_expansions, rewrite, zero_terms_query, cutoff_frequency, auto_generate_synonyms_phrase_query 、fuzzy_transpositions等参数。

best_fields和大多数字段类型都是以字段为中心的——它们为每个字段生成匹配查询。这意味着运算符和minimum_should_match参数将分别应用于每个字段。

2、most_fields

查找匹配任何字段并结合每个字段的_score的文档,Elasticsearch会为每个字段生成一个match查询,然后将它们包含在一个bool查询中。其算法的核心是各个字段的评分相加作为文档的最终得分参与排序。其建议场景是不同字段对同一关键字的存储维度不一样,例如字段一可能包含同义词、词干、变音符等;字段二可能包含原始词根,这种情况下综合各个字段的评分就会显的更加具有相关性。

该查询模式支持match query相关的参数,例如analyzer, boost, operator, minimum_should_match, fuzziness, lenient, prefix_length, max_expansions, rewrite, zero_terms_query, cutoff_frequency, auto_generate_synonyms_phrase_query 、fuzzy_transpositions等参数。

3、phrase、phrase_prefix

这两种类型score的计算采用best_fields方法,但是其查询方式分别为match_phrase、match_phrase_prefix。

4、cross_fields

交叉字段,对于需要匹配多个字段的结构化文档,cross_fields类型特别有用。例如,在查询“Will Smith”的first_name和last_name字段时,在一个字段中可能会有“Will”,而在另一个字段中可能会有“Smith”。这听起来很象most_fields,cross_fields与most_fields的两个明显区别如下:

  • 对于opreator、minimum_should_match的作用域不一样,most_fields是针对字段的,(遍历每个字段,然后遍历查询词根列表,进行逐一匹配),而cross_fields是针对词根的,即遍历词根列表,搜索范围是所有字段。

  • 相关性的考量不相同,cross_fields重在这个交叉匹配,对于一组查询词根,一部分出现在其中一个字段,另外一部分出现在另外一个字段中,其相关性计算评分将更高。

举例说明:例如有如下查询语句:

{   
    "query": {     
        "multi_match" : {       
            "query":      "Will Smith",       
            "type":       "cross_fields",       
            "fields":     [ "first_name", "last_name" ],       
            "operator":   "and"     
        }   
    } 
}

其执行操作时,首先对查询字符串分析得出will、smith两个词根,然后遍历这两个词根,一次对first_name,last_name进行匹配,也就是说opreator、minimum_should_match这些参数作用2次,而most_fields方式,是一个嵌套循环,先遍历字段,然后对每一个词根在该字段上进行匹配,在该示例中,opreator、minimum_should_match这些参数作用4次。

4.1.2 tie_breaker属性

默认情况下,每个词汇混合查询将使用组中任何字段返回的最佳分数,然后将这些分数相加,以给出最终分数。tie_breaker参数可以改变每项混合查询的默认行为。tie_breaker可选值如下:

  • 0.0 : 默认行为,使用最佳字段的score。

  • 1.0 :所有匹配字段socre的和。

  • 0.0 ~ 1.0 : 使用最佳匹配字段的score + (其他匹配字段score) * tie_breaker。

4.1.3 multi_query支持其他match query参数

其他诸如analyzer, boost, operator, minimum_should_match, fuzziness, lenient, prefix_length, max_expansions, rewrite, zero_terms_query, cutoff_frequency, auto_generate_synonyms_phrase_query 、fuzzy_transpositions等参数,multi_query同样支持。

由于篇幅的原因,本节就只介绍全文检索方式的前4种方式,全文检索的其他查询方式将在下一节详细介绍。


更多文章请关注公众号:中间件兴趣圈。

Elasticsearch Query DSL之全文检索(Full text queries)上篇

本文分享自微信公众号 - 中间件兴趣圈(dingwpmz_zjj)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
3年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这