Elasticsearch Query DSL之Term level queries

Stella981
• 阅读 752

简介

term_level查询操作的是存储在反向索引(倒排索引)中的准确词根,这些查询通常用于结构化数据,如数字、日期和枚举,而不是全文字段,无需进行分析(分词),term level查询类似于关系型数据库的(where条件过滤)。其查询模式如下:

  • term query
    查找包含指定字段中精确匹配查询字符串的文档。

  • terms query、
    查找包含指定字段中包含查询词根集合中任意一个精确匹配的文档。

  • terms_set query
    查找与一个或多个指定词根相匹配的文档。必须匹配的项的数量取决于指定的最小值或脚本。

  • range query
    范围查询

  • exists query
    返回在原始字段中至少有一个非空值的文档

  • prefix query
    前缀查询

  • wildcard query
    通配符查询

  • regexp query
    正则表达式查询

  • fuzzy query
    模糊查询

  • type query
    指定类型查询

  • ids query
    ids数组查询。

term query

term query从索引库中的词根倒排索引中精确匹配文档。

1POST _search2{3  "query": {4    "term" : { "user" : "Kimchy" } 5  }6}

其含义就是,查找user属性为"Kimchy"的文档,执行查询之前,不会对查询字符串"Kimchy"进行分词。term query查询支持boost参数来提高单个词根的相关度。

不同字段类型的分析处理过程不净相同,下面是各数据类型的处理机制:

  • 字符串字段(string)可以是文本类型,也可以是关键字类型。keyword、精确值(如数字、日期和关键字类型)将字段原始值作为一个整体存储在倒排索引中,以使其可搜索。

  • text类型的字段,首先会原始输入值进行分析(使用分词器)然后得出一系列的词根,然后将这些词根一一添加到倒排索引中。分析文本有很多方法:默认的标准分析器删除大多数标点符号,将文本分解为单个单词,并使用小写字母,在创建索引映射(类似于关系型数据库的表结构,当然有区别)时可以指定各个字段的分词器,在查询的时候也可以使用指定的分词器对查询字符串进行分析,其配置参数名称为:analyzer 。

下面举例说明一下全文索引(match_query)与精确查询(term_query)的工作过程:

 1PUT my_index 2{ 3  "mappings": { 4    "_doc": { 5      "properties": { 6        "full_text": { 7          "type":  "text"    // @1  8        }, 9        "exact_value": {10          "type":  "keyword"    // @211        }12      }13    }14  }15}16PUT my_index/_doc/1    // @317{18  "full_text":   "Quick Foxes!",                   19  "exact_value": "Quick Foxes!"              20}

根据上面介绍得知,字段full_text的值会先使用分词器进行分析,然后将分词后的词根存入倒排索引中,而字段exact_value则会直接将值当成词根存入倒排索引中,并不会进行分词。代码@3存入一个文档,full_text的"Quick Foxes!"默认使用标准分词器,则会生产词根[quick、foxes],然后将quick、foxes存入倒排索引中,而字段exact_value不会使用分词器,直接将"Quick Foxes!"存入倒排索引中。基于上面的索引定义与数据,来分析一下这些查询是否能匹配到该文档:
查阅示例1:

1GET my_index/_search                       2{3  "query": {4    "term": {5      "exact_value": "Quick Foxes!" 6    }7  }8}

分析:使用term_query,并且字段为exact_value,表示精确匹配得知,该字段存储的值与查询值完成匹配,故结论为能匹配到文档。

查询示例2:

1GET my_index/_search                        2{3  "query": {4    "term": {5      "full_text": "Quick Foxes!" 6    }7  }8}

分析:使用term_query匹配,使用full_text字段,查询字符串与存入文档之前的值一样,看似能匹配上,但其实不然,根据上面的分析可值,由于full_text字段的类型为text,存入倒排序列的并不是"Quick Foxes!",而是quick、foxes,故结论为无法匹配到文档。

查询示例3:

1GET my_index/_search                        2{3  "query": {4    "term": {5      "full_text": "foxes" 6    }7  }8}

分析:使用term_query,并且采用查询字符串foxes,由于full_text是text字段类型,会先进行分词,故会存入quick、foxes两个词根,与输入字符串foxes匹配,故结论为能匹配到文档。
查询示例4:

1GET my_index/_search                        2{3  "query": {4    "match": {5      "full_text": "Quick Foxes!" 6    }7  }8}

分析:使用match query(权威搜索),会首先对查询字符串进行分词,然后根据词根一一匹配,故该结果能匹配到文档。

terms query

查找包含指定字段中包含查询词根集合中任意一个精确匹配的文档。实例如下:

1GET /_search2{3    "query": {4        "terms" : { "user" : ["kimchy", "elasticsearch"]}5    }6}

其查询的意图:查询"user"属性的值为"kimchy"或"elasticsearch"的文档。

terms 查询机制

试想如下一个场景,在微博应用场景中,需要查看关注你的所有用户发布的微博,那这个查询第一步应该是得到关注你的所有用户列表,然后查询微博的发布者ID在关注你的列表集合中的所有文档。这个关注列表一般不会很小,如果要分两步来实现的话,查询中传入的terms这个集合会很大,相当于关系型数据库中的in (很大的集合),那有没有一种机制,支持类似关系型数据库中  a.id in ( select b.id from B b where …)这种方式呢?答案当然是有的。下面下能给出本次试验的基础数据:

 1PUT /users/_doc/2 2{ 3    "followers" : ["1", "3"] 4} 5PUT /tweets/_doc/1 6{ 7    "user" : "1" 8} 9//其查询写法如下:10GET /tweets/_search11{12    "query" : {13        "terms" : {14            "user" : {15                "index" : "users",16                "type" : "_doc",17                "id" : "2",18                "path" : "followers"19            }20        }21    }22}
  • 查询terms支持如下参数:

  • index 指定查询索引名(相当于关系型数据库database)

  • type 指定类型名(相当于关系型数据库table)

  • id 指定文档的id(相当于关系型数据库的row 主键id)

  • path:值来自哪个字段,支持级联,例如followers.id等嵌套json属性。

  • routing:指定路由字段值。

从上可知,terms过滤器的值将从一个子查询中获取。在"子查询"get请求以从指定路径(_source字段中存储的值)中提取。执行terms查询请求可能相当缓慢,因为每个词根都需要额外的处理和占用内存。为了防止出现这种情况,可以设置最大支持的terms的个数,默认为65536。可以通过设置index.max_terms_count来更改此默认值。

java(Demo示例)

 1public static void testTermsQuery() { 2        RestHighLevelClient client = EsClient.getClient(); 3        try { 4            SearchRequest searchRequest = new SearchRequest(); 5            searchRequest.indices("esdemo"); 6            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 7 8            sourceBuilder.query( 9                    QueryBuilders.termsQuery("context", "fox", "brown")10                    );11//            TermsLookup termsLookup = new TermsLookup("esdemo", "matchquerydemo", "1", "context");12//            termsLookup.routing("1");13//            sourceBuilder.query(14//                QueryBuilders.termsLookupQuery("context", termsLookup);15//            );16            searchRequest.source(sourceBuilder);17            SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);18            System.out.println(result);19        } catch (Throwable e) {20            e.printStackTrace();21        } finally {22            EsClient.close(client);23        }24    }

terms_set query

查找与一个或多个指定词根相匹配的文档。必须匹配的项的数量取决于指定的最小值或脚本。必须匹配的项的数量是在文档中指定。先从示例开始讲起:

 1PUT /my-index 2{ 3    "mappings": { 4        "_doc": { 5            "properties": { 6                "required_matches": { 7                    "type": "long" 8                } 9            }10        }11    }12}

首先为_doc类型新增一个数值型的字段,这里取值为required_matches。

 1PUT /my-index/_doc/1?refresh 2{ 3    "codes": ["ghi", "jkl"], 4    "required_matches": 2 5} 6 7PUT /my-index/_doc/2?refresh 8{ 9    "codes": ["def", "ghi",“test”,"ak"],10    "required_matches": 311}

在存入es中,会自己决定需要匹配的词根个数,低于这个数值,则不会返回该文档。

 1GET /my-index/_search 2{ 3    "query": { 4        "terms_set": { 5            "codes" : { 6                "terms" : ["abc", "def", "ghi"], 7                "minimum_should_match_field": "required_matches" 8            } 9        }10    }11}

通过属性minimum_should_match_field指定需要匹配的个数,但这个数值来源于文档内部的字段,故该属性值就是指定匹配个数的来源属性名称。该查询结构也支持脚本,其脚本指定字段为minimum_should_match_script,关于script脚本将会在专门的章节中讲述。

range query

范围查询。查询的类型取决于字段类型,对于string字段,是TermRangeQuery,而对于number/date字段,查询是NumericRangeQuery。以下示例返回年龄在10到20岁之间的所有文档。

 1GET _search 2{ 3    "query": { 4        "range" : { 5            "age" : { 6                "gte" : 10, 7                "lte" : 20, 8                "boost" : 2.0 9            }10        }11    }12}

range query支持如下参数:

  • gte 大于等于

  • gt 大于

  • lte 小于等于

  • lt 小于

  • boost 权重(重要程度)

data maths(日期函数)

日期表达式以一个日期(基准日期,锚定日期)开始,可以是now,也可以是以||结尾的日期字符串。这个锚定日期可以有选择地跟随一个或多个数学表达式,例如:

  • +1h 增加一小时

  • -1d 减少一天

  • /d - 向日取整 (返回该天的整点)

  • /M -向月取整(返回该月的第一天的整点)

日期支持如下时间单位:

  • y 年

  • M 月

  • w 周

  • d 日

  • h 小时(12表示法)

  • H 小时(24表示法)

  • m 分钟

  • s 小时

假设现在是2001-01-01 12:00:00,以下是一些例子:

  1. now+1h
    当前时间加1小时,最终表示为:2001-01-01 13:00:00

  2. now-1h
    当前时间减1小时,最终表示为:2001-01-01 11:00:00

  3. now-1h/d
    先减去一小时,为2001-01-01 11:00:00,然后舍弃d后面的时间,这里是舍弃11:00:00,故最终表示为2001-01-01 00:00:00

  4. 2001.02.01||+1M/d
    2001-02-01先加一个月,变为2001-03-01,然后舍弃天后的实际,最终表示为2001-03-01 00:00:00

日期范围查询(date maths)

 1GET _search 2{ 3    "query": { 4        "range" : { 5            "date" : { 6                "gte" : "now-1d/d", 7                "lt" :  "now/d" 8            } 9        }10    }11}

例如当前时间为2018-10-24 12:25:35,则代表查询的含义为date字段的值大于等于2018-10-23 00:00:00 小于 2018-10-24 00:00:00。

当使用日期数学将日期四舍五入到最近的日、月、小时等时,四舍五入的日期取决于范围的两端是否包含或排除。舍入移动到舍入范围的最后一毫秒,舍出到舍入范围的第一毫秒。关于各运算符的舍入舍出规则如下:

  • gt 2014-11-18||/M
    代表大于2014-11-30T23:59:59.999, 使用gt(不包含e),是向上,取当月最后一天23:59:59

  • gte 2014-11-18||/M
    代表2014-11-01,如果运算符为大于等于,则向下舍弃,取当月第一天零点

  • lt 2014-11-18||/M
    代表2014-11-01,如果运算算法小于,则向下舍弃

  • lte 2014-11-18||/M
    代表2014-11-30T23:59:59.999, 小于等于,则向上,取当月最后一天的23:59:59

日期类型范围查询(format)

 1GET _search 2{ 3    "query": { 4        "range" : { 5            "born" : { 6                "gte": "01/01/2012", 7                "lte": "2013", 8                "format": "dd/MM/yyyy||yyyy" 9            }10        }11    }12}

format使用 双竖线|| 做分隔符号,上面表示,查询born字段 大于等于2012-01-01  00:00:00 小于等于2013-01-01 00:00:00。

如果需要年月日,消息分钟,请用如下写法:

1QueryBuilders.rangeQuery("post_date")2            .gte("2018-10-25T14:12:10")3            .lte("2018-10-27T14:12:10")4            .format("yyyy-MM-dd'T'HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss")。//  ‘T’用来分隔 年月日  与  时分秒。

字段类型的转换格式,将在后续文章中介绍日期类型时会详细介绍。

exists query

返回指定字段中至少有一个非空值(null)的文档。示例如下:

1GET /_search2{3    "query": {4        "exists" : { "field" : "user" }5    }6}

对于上面的查询,下面的所有文档都将符合要求:

1{ "user": "jane" }2{ "user": "" }                                    // 空字符串不为null3{ "user": "-" } 4{ "user": ["jane"] }5{ "user": ["jane", null ] }                   //因为存在jane值不为null,则匹配。

null_value映射

自定义null值。例如将"null"字符串定义为null值。

 1PUT /example 2{ 3  "mappings": { 4    "_doc": { 5      "properties": { 6        "user": { 7          "type": "keyword", 8          "null_value": "_null_" 9        }10      }11    }12  }13}

其作用是会将user字段的null值索引为_null_字符串,则下面的文档将能被exists匹配:

1{ "user": null }2{ "user": [null] }

查询不存在某个字段的文档

 1GET /_search 2{ 3    "query": { 4        "bool": { 5            "must_not": { 6                "exists": { 7                    "field": "user" 8                } 9            }10        }11    }12}

prefix query

词根前缀查询,对查询词根不会使用分词器进行分词。使用示例如下:

1GET /_search2{ "query": {3    "prefix" : { "user" : "ki" }4  }5}

同样,也可以未字段设置权重(boost)。示例如下:

1GET /_search2{ "query": {3    "prefix" : { "user" :  { "value" : "ki", "boost" : 2.0 } }4  }5}

其代表含义可转换为关系型数据库查询语句:where a.user like 'ki%'。
思考一下,如果存在有文档的user字段的值为"Kimmi",使用如下字符查询条件:

1{ "query": {2    "prefix" : { "user" : "Ki" }3  }4}

该查询能匹配到结果吗?在默认使用标准分词器的环境中,是无法匹配到数据的,其原因如下:首先,在存储文档时,首先会对"kimmi"字段进行分词,返回的词根为kimmi(全小写),将这些词根存入到Elasticsearch(lucene)的倒排索引中,然后进行查询时,并不会使用分词器对 prefix进行分词,故查询字符串为Ki,是无法匹配到上述文档的,要向匹配到文档,请使用小写的查询ki。这也是ES中的term(词根精确查询)与关系型数据库的一个非常重要的区别。

java示例代码

 1public static void testTermQuery_prefix() { 2        RestHighLevelClient client = EsClient.getClient(); 3        try { 4            SearchRequest searchRequest = new SearchRequest(); 5            searchRequest.indices("twitter"); 6            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 7            sourceBuilder.query( 8                    QueryBuilders.prefixQuery("user", "ki") 9                    .boost(2.0f)10            );11            searchRequest.source(sourceBuilder);12            SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);13            System.out.println(result);14        } catch (Throwable e) {15            e.printStackTrace();16        } finally {17            EsClient.close(client);18        }19    }

Wildcard Query

通配符匹配。支持的通配符为_和?。其中_代表任何的字符序列,包含空字符,而?代表任意的单个字符。这种查询需慎重,特别是对于以通配符开头的查询,例如"a"或"?b",因为这种需要遍历整个倒排索引,通常建议使用 "查询字符加通配符",例如"a"或"a*b"这类。
其查询举例如下

1GET /_search2{3    "query": {4        "wildcard" : { "user" : "ki*y" }5    }6}7

public static void testWildcardQuery() {
        RestHighLevelClient client = EsClient.getClient();
        try {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("esdemo");
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(
                    QueryBuilders.wildcardQuery("user", "ki*il")
                    );
            searchRequest.source(sourceBuilder);
            SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(result);
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            EsClient.close(client);
        }
    }

regexp Query

使用正则表达式进行查询匹配,暂不深入学习,需要时再细化。

fuzzy query

模糊匹配,基于词根编辑距离来实现。所谓的词根编辑距离(其术语为: Levenshtein Edit Distance)是指一个词(字符序列)经过多少次单字符的修改能转换为另外一个词的次数。例如 cat  --> cak 其编辑距离为1。
支持模糊匹配的查询API,其参数fuzziness可选值:

  • 0:表示精确匹配。

  • 1:表示允许出现1个编辑距离。

  • 2:表示允许出现2个编辑距离。

  • auto:当词根长度小于3时,则精确匹配;当词根长度大于3并且小于6时,允许1个编辑长度的词根匹配;当词根大于等于6后,允许2个编辑距离的词根匹配。默认为auto。

注意:fuzzy query是词根级别的查询,不会对查询字符串进行分析。

支持的参数如下:

  • fuzziness 允许的编辑距离,默认为AUTO。

  • prefix_length 词根的前prefix_length个字符不允许出现编辑距离,指一个词根前面的部分必须是精确匹配,因为模糊匹配,一般是用来解决书写错误,或语法(因为的负数)等,前面的字符一般不会书写错误。

  • max_expansions 最大去查找匹配词根的个数,默认为50。

  • transpositions 是否支持位置变化,例如ab-ba,如果transpositions为true,则ab-ba的编辑距离为1,如果transpositions为false,则其编辑距离为2。默认为false。

JAVA示例

假设es数据库中缓存有如下文档:{“message”:"ab and hell"}

 1    public static void testFuzzyQuery() { 2        RestHighLevelClient client = EsClient.getClient(); 3        try { 4            SearchRequest searchRequest = new SearchRequest(); 5            searchRequest.indices("twitter"); 6            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 7            sourceBuilder.query( 8                    QueryBuilders.fuzzyQuery("message", "ba") 9                    .transpositions(true)10                    .fuzziness(Fuzziness.ONE)  // 如果transpositions设置为false,则无法匹配到上述文档,如果将fuzziness(Fuzziness.TWO),则11                                           // 可匹配到文档,但如果transpositions=true,并且fuzziness(Fuzziness.ONE)同样可匹配到文档。12            );13            searchRequest.source(sourceBuilder);14            SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);15            System.out.println(result);16        } catch (Throwable e) {17            e.printStackTrace();18        } finally {19            EsClient.close(client);20        }21    }

type query

term level queries 支持使用type来过滤文档(相当于数据库层面的表级别过滤)

1GET /_search2{3    "query": {4        "type" : {5            "value" : "_doc"6        }7    }8}

ids query

可以通过指定ids数组进行查询,其示例如下:

1GET /_search2{3    "query": {4        "ids" : {5            "type" : "_doc",6            "values" : ["1", "4", "100"]7        }8    }9}

其中type为可选字段,如果未指定,则查索引库中所有的类别,6.0版本后建议一个索引库只定义一个类型。

本节详细介绍了es term query查询模式。

本文分享自微信公众号 - 中间件兴趣圈(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
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
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 )
Stella981 Stella981
3年前
Elasticsearch Mapping parameters(主要参数一览)
Elasticsearch在创建类型映射时可以指定映射参数,下面将一一进行介绍。analyzer指定分词器。elasticsearch是一款支持全文检索的分布式存储系统,对于text类型的字段,首先会使用分词器进行分词,然后将分词后的词根一个一个存储在倒排索引中,后续查询主要是针对词根的搜索。analyzer该参数可以在查询、字段、索引级
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
3年前
Es学习第七课, term、terms、match等基本查询语法
term、terms查询termquery会去倒排索引中寻找确切的term,它并不知道分词器的存在,这种查询适合keyword、numeric、date等明确值的term:查询某个字段里含有某个关键词的文档GET/customer/doc/_search/{"query":{
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Stella981 Stella981
3年前
ELK学习笔记之ElasticSearch的索引详解
0x00ElasticSearch的索引和MySQL的索引方式对比Elasticsearch是通过Lucene的倒排索引技术实现比关系型数据库更快的过滤。特别是它对多条件的过滤支持非常好,比如年龄在18和30之间,性别为女性这样的组合查询。倒排索引很多地方都有介绍,但是其比关系型
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这