Press "Enter" to skip to content

Elasticsearch叶子查询与模糊查询(五)

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

Elasticsearch检索接口_search可通过URI参数q或请求体参数query接收DSL描述的查询条件,其中参数q接收DSL中定义的查询字符串,而query参数则可以接收所有DSL查询条件。按照官方的说法,DSL可以分为叶子查询(Leaf Query Clauses)和组合查询(Compound Query Clauses)两种类型。叶子查询是在指定的字段中匹配查询条件,例如检索名称为tom的文档、年龄在10~20岁之间的文档等等。叶子查询大致上可分为基于词项的查询和基于全文的查询两大类,除了multi_match和query_string以外,它们大部分都只能针对一个字段设置查询条件。组合查询则不同,它可以包含一个或多个子查询,这些查询以不同的逻辑运算并组装在一起共同执行检索。

 

由于DSL内容非常多,同时又涉及模糊查询、相关性计算等全文检索专业问题。所以出于篇幅上的考虑,本章将只介绍叶子查询和模糊查询等相关问题。

 

5.1基于词项的查询

 

基于词项的查询属于叶子查询,所以这种查询语句一般只能针对一个字段设置条件。基于词项的查询会精确匹配查询条件,不会对查询条件做分词、规范化等预处理。但对于keyword类型字段,如果这个字段通过normalizer参数定义了规整器,词项查询会将查询条件做标准化处理。有关标准化的问题,请参考本书第4章4.4节。

 

需要注意的是,词项查询匹配字段索引中包含的词项值,由于text类型字段会做分词处理,所以不能直接匹配字段的全部内容。比如在text类型字段值为“tom smith”,编入索引词项是tom和smith而没有“tom smith”,所以如果使用“tom smith”做词项查询,将无法检索到这个字段。不仅如此,由于分析器在提取分词后还会通过分词过滤器对分词做处理,所以分词一般都会做一些规范化处理。以默认standard分析器为例,它包含一个lowercase分词过滤器,会将所有分词转换为小写字母。所以如果一个text字段的值为Tom Smith,编入索引的词项将是tom和smith,使用Tom或Smith就无法检索到文档。当然这里还有另外一个因素,那就是基于词项的查询不会对查询条件做分析和规范化处理。

 

因此,基于词项的查询一般不对text类型字段做检索,而用于类似数值、日期、枚举类型等结构化数据的精确匹配。基于词项的查询有多种类型,每一种类型都有一个关键字。在下面各小节的介绍中会直接使用它们的关键字指代这种查询类型。

 

5.1.1 term、terms和terms_set

 

term、terms和terms_set三种查询语句都是对单个字段做词项值的精确匹配,区别在于term查询只能匹配一个词项,terms可以从一组词项中做匹配,而terms_set则可以匹配数组类型的字段。

 

1.term查询

 

term查询可以对字段做单词项的精确匹配,而不能对字段做多词项的匹配。如果以SQL语句来类比,term查询相当于SQL语句where条件中的等于号。所以在使用term查询时需要指定的就是字段与期望值的对应关系,例如:

 

示例5-1中的两个请求都正确,在第二个请求中将message字段的值放在了value参数中。这种方式一般是在需要设置boost参数时使用,boost参数用于提升检索结果的相关性评分,请参考本章第6.1.5节。

 

terms查询

 

terms查询类似于SQL中的in操作符,可以在一组指定的词项范围内匹配字段值,只要字段满足这些词项中的一个就认为满足查询条件。terms查询中由于要设置多个词项,所以字段期望值使用数组来设置:

 

Elasticsearch在terms查询中还支持跨索引查询,这类似于关系型数据库中的一对多或多对多关系。比如,用户与文章之间就是一对多关系,可以在用户索引中存储文章编号的数组以建立这种对应关系,而将文章的实际内容保存在文章索引中(当然也可以在文章中保存用户ID)。如果想将ID为1的用户发表的所有文章都找出来,在文章索引中查询时为

 

在示例5-3中,terms要匹配的字段是_id,但匹配值则来自于另一个索引。这里用到了index、id和path三个参数,它们分别代表要引用的索引、文档ID和字段路径。在上面的例子中,先会到users索引中查找_id为1的文档,然后取出articles字段的值与articles索引里的_id做对比,这样就将用户1的所有文章都取出来了。

 

terms_set查询

 

terms_set查询与terms查询类似,不同的是被匹配的字段类型是数组。terms_set查询接收以数组类型表示的多个词项,被匹配字段只要包含期望词项中的几个即可。具体数量有两种方式设置,一种方式是通过minimum_should_match_field参数指定文档中的一个字段,这个字段必须为数值类型并保存了期望匹配词项的个数;另一种则是通过minimum_should_match_script参数以Painless脚本动态计算。例如:

 

在脚本中可以使用params.num_terms上下文变量获取terms参数中设置的期望匹配词项的实际总数量,Painless脚本在这里可使用的上下文参数见表5-1。

 

5.1.2 range和exists

 

range查询用于匹配一个字段是否在指定范围内,所以一般应用于具有数值、日期等结构化数据类型的字段。例如:

 

示例5-5的range查询会返回所有延误时间在100~200min之间的航班。可以用在范围查询中的参数包括:

 

● gte:大于等于;

 

● gt:大于;

 

● lte:小于等于;

 

● lt:小于;

 

● boost:相关性评分。

 

除了以上参数,日期类型的字段还可以使用format参数指定日期格式,使用time_zone参数指定时区;而范围类型的字段可通过relation参数指定字段与查询条件之间的关系,可选值为WITHIN、CONTAINS和INTERSECTS。范围类型在第2章2.3.1节有介绍,包括integer_range、float_range、long_range、double_range等几种。

 

exists查询用于检索指定字段值不为空的文档,所以exists查询需要通过field字段设置需要检查非空的字段名称。同样的,field参数只能设置一个字段,不支持对多个字段的非空检验。例如:

 

exists查询在验证非空时需要明确什幺样的值是空,什幺的值不是空。默认情况下,字段空值与Java语言的空值相同都是null。空值字段不会被索引,因此也不可检索。

 

5.1.3使用模式匹配

 

前述几种查询类型都是对词项做精确匹配,但Elasticsearch也支持使用通配符、正则表达式等方式对词项做模糊匹配。这些查询类型包括prefix、wildcard、regex和fuzzy四种。

 

1.prefix查询

 

prefix查询可以用于检索字段值中包含指定前缀的文档,这对于只记得词项前缀时做文档检索比较有帮助。例如想要检索包含Mozilla的文档,但只记得前缀为Mo就可以使用prefix查询:

 

尽管单词前缀是Mo,但由于分析器在分词后会将词项做规范化处理,所以查询条件中只能使用mo。

 

2.wildcard查询

 

wildcard查询允许在字段查询条件中使用通配符“ ”和“?”,其中“ ”代表0个或多个字符,而“?”则代表单个字符。例如可使用f * f?x匹配firefox,它将检索所有以f开头并以f?x结尾的词项,其中的问号代表任意字符:

 

由于wildcard查询需要与多个词项做匹配,查询速度会比直接使用完整的词项要慢一些。所以在使用wildcard查询时,尽量不要让通配符出现在查询条件的第一位,因为这需要查询与所有词项做匹配。例如在示例5-8中使用的f * f?x只需要与f开头的词项做匹配,而如果使用*iref?x则需要与所有词项做匹配。

 

3.regexp查询

 

regexp查询允许在查询条件中使用正则表达式与字段词项做匹配,正则表达式的语法与Lucene使用的正则表达式一致。例如同样是匹配firefox,示例5-8中使用的的f * f?x可以用正则表达式写为f.* f.x:

 

由于regexp查询使用的是Lucence正则表达式,所以Java正则表达式中预定义的字符类型如\w、\d并不支持。

 

5.1.4 type和ids

 

除了使用词项对业务字段做匹配以外,还可以根据索引的元字段做匹配,这包括type查询和ids查询,它们分别可以根据映射类型和文档_id字段做检索。

 

1.type查询

 

type查询根据映射类型做查询,将所有属于指定映射类型的文档都查询出来。换句话说,就是根据文档的_type字段做匹配。例如示例5-10的请求,将会返回所有映射类型为_doc的文档:

 

在请求路径中也可以添加索引名称,这将把查询范围限定在指定的索引中。由于在Elasticsearch版本6以后索引只能定义一个映射类型,所以这种查询已经没有太多意义。

 

2.ids查询

 

ids查询允许根据一组ID值查询多个文档,需要注意的ids查询所查询的元字段是_uid而不是_id。由于_uid字段由映射类型和文文档ID共同决定,而在Elasticsearch版本6中已经将多映射类型废止,_uid是为了保证版本间兼容才被保留下来。所以在版本6以后_uid的值与_id字段完全相同,因此可以认为ids查询的就是_id字段。例如在示例5-11中的请求就是将kibana_sample_data_logs索引中_id值为FO1Gd2cBbo-eBn7dSgWe的文档检索出来:

 

在请求路径中也可以不指定索引名称,这样将会查询所有索引。由于_id仅在索引内惟一,所以在这种情况下有可能通过一个ID检索到多个文档。

 

5.1.5 停止词和common查询

 

有些词项在所有文档中出现的频率都很高,比如英文中的“the”“of”“to”等,中文中的“的”“得”“虽然”等。它们出现的范围和频率虽然很高,但是往往与文档要表达的核心意思关联并不大。这类词项在检索时就像是噪音一样,只有将它们剔除才可能得到更接近用户期望的结果。

 

1.停止词

 

在处理这类问题时,最显而易见的办法就是在文档编入索引时将它们剔除,这类出现在文档中但并不会编入索引中的词项就是停止词(Stopword)。停止词一般是在定制分析器时预先定义好,文档在编入索引时分析器就会将这些停止词从文档中剔除。例如:

 

但是以停止词的方式去除无意义词项在某些场景下会导致问题,比如在“你知道‘虽然’这个词是什幺含义吗?”这句话中,“虽然”是一般意义上的停止词,但在整个句子中却居于重要地位。如果将它从上述句子中去除,就失去了整句话的核心意义。类似的情况在英文中也不少,比如在莎士比亚经典台词“To be or not to be”中,所有的单词都是一般意义上的停止词,但去除了它们中任何一个整个句子都会失去意义。所以针对这种情况,Elasticsearch提供了另外一种解决方案,这就是common查询。

 

2.common查询

 

common查询将词项分为重要词项和非重要词项两大类,重要词项是那些出现频率相对较低的词项,所以也称为低频词项;而非重要词项则是那些出现频率相对较高的词项,所以也称为高频词项。这里所说的出现频率不是指词项在单个文档某字段中的出现次数,而是指在某字段中出现了该词项的文档数量。这种重要与非重要划分的标准显然是基于逆向文档频率(Inrert Document Frequency,具体请参考第6章6.1.2节)思想,即如果词项在大多数文档中都出现了,那幺它与结果的相关性就低,反之相关性就高。在common查询中使用cutoff_frequency设置词项频率,可以设置为一个绝对数量,代表出现了词项的文档个数;也可以设置为百分比,代表出现了词项的文档数量占总文档数量的百分比。cutoff_frequency区分绝对数量和百分比是看设置的值是否小于1,小于1时为百分比否则为绝对数量。需要特别注意的是,词项频率是分片运算的,在文档数据比较少的情况下,有可能出现各分片严重不平衡的现象。为了体验common查询,可以按示例5-13创建只有一个分片的索引articles,并添加4个content字段分别为“this is elasticsearch”“this is logstash”“this is kibana”和“logstash kibana”的文档:

 

按照划分重要词项和非重要词项的思想,“this”和“is”出现在3/4的文档中,应该归类为非重要词项,而“elasticsearch”“logstash”和“kibana”则只出现在1~2份文档中应为重要词项。所以将cutoff_frequency设置为2次(小于3)就可以按重要性将它们区分开来,而将cutoff_frequency设置为3或更大值时,所有查询词项都会被归类为非重要词项,如示例5-14所示:

 

在示例5-14的查询中,查询条件“this is elasticsearch,logstash”会被拆分为“this”“is”“elasticsearch”和“logstash”四个词项。按cutoff_frequency设置的标准,前两者会被识别为非重要词项,而后两者则会识别为重要词项。接下来common查询会执行两次检索,第一次检索是根据重要词项“elasticsearch”和“logstash”匹配文档,而第二次检索则是在第一次检索的基础上再次使用非重要词项“this”和“is”匹配文档,重要词项对相关性数值_score的影响大于非重要词项。读者可自行将cutoff_frequency设置为3,看一看返回的文档及其_score分值有什幺不同。

 

当查询条件中存在多个词项时,无论是是重要词项还是非重要词项,它们与字段匹配结果之间的关系都是或者的关系。也就是说,只要满足任意一个词项匹配条件即可以被筛选出来,匹配多个只会使相关性评分升高。这种默认的关系可以通过三个参数修改,它们分别是minimum_should_match、low_freq_operator和high_freq_operator。后两者比较直观,分别是设置低频词项和高频词项的操作符。minimum_should_match略有些复杂,直接设置值时设置的是低频词项需要匹配的数量。如果要设置高频词项则需要使用low_freq和high_freq区分,例如:

 

common查询通过词项频率做重要性区别,将那些与结果相关性不大的词项筛选出来,使它们仅对结果相关性产生一定影响,同时还可以降低检索运算和相关性运算的复杂度。common查询是对停止词的一种替代方案,但比停止词又灵活了很多。它使用词项频率作为控制阈值,将词项分为高频词和低频词,而高频词就相当于停止词。这使得在一些专业性文章中,某些特定的词语能够自动成为停止词。比如在elastic官网中,包含elasticsearch、logstash等词项的网页肯定非常多,这些词项出现的频率就会自然升高。如果使用cutoff_frequency设置了合适的百分比,它们也就自然而然地成为高频词项,从而产生了类似停止词的效果。

 

5.2 基于全文的查询

 

基于全文的查询与基于词项的查询最显着的区别是前者会对查询条件做分析,使用的分析器可以在索引创建时通过analyzer参数或search_analyzer参数设置,也可以在检索时通过_search接口的analyzer参数动态修改。尽管基于全文的查询也是叶子查询,但其中的multi_match和query_string查询可以针对多个字段做查询。

 

5.2.1词项匹配

 

match查询和multi_match查询都是使用查询条件中提取出来的词项与字段做匹配,不同的是前者只对一个字段做匹配,而后者则可以同时对多个字段做匹配。

 

1.match查询

 

match查询接收文本、数值和日期类型值,在检索时将查询条件做分词处理再以提取出来的词项与字段做匹配。如果提取出来的词项为多个,词项与词项之间的匹配结果按布尔或运算,也就是说只要有一个匹配成功即认为是满足查询条件。例如在kibana_sample_data_logs中检索与Firefox和Chrome浏览器相关的文档:

 

词项匹配的运算逻辑和匹配个数通过operator和minimum_should_match这两个参数来改变,operator参数的作用是定义分词匹配结果的逻辑组合关系,可选值为or或and(默认值为or);minimum_should_match参数的作用则是定义分词匹配的最小数量。以示例5-17中的查询为例,如果将operator设置为and则不会有任何文档返回:

 

类似地,如果将minimum_should_match参数为2,即使设置operator为or也不会有文档返回,因为minimum_should_match参数要求提取出来的两个词项都要满足。minimum_should_match参数可选值有很多种,不仅可以使用正值,还可以使用负值。正值代表需要匹配的数量,负值代表不需要匹配的数量。但无论设置什幺样的值,实际匹配的数量不能小于1,也不能大于子句总数。具体见表5-2。

 

2.multi_match查询

 

multi_match查询与match查询类似,但可以实现对多字段的同时匹配。例如:

 

在示例5-18中,请求将同时检索DestCountry和OriginCountry这两个字段,只要有一个字段包含AT词项即满足查询条件。当然两个字段如果都包含AT词项,它的_score分值会更高。如果在查询条件中没有指定要匹配的字段,将由索引的配置参数“index.query.default_field”决定,默认为“ . ”,即在索引定义的所有字段中检索。multi_match查询的相关性评分涉及分值的组合,具体请参考第6.2.6节。

 

5.2.2 短语匹配

 

顾名思义,短语查询在检索时匹配的不是单个词项,而是由多个词项组成的短语。但不要被短语匹配的表面所迷惑,短语匹配并不是用整个查询条件与字段做匹配。如果是这样,Elasticsearch就必须要给所有的短语做索引,这个数量将十分惊人;而如果不对所有短语做索引,那幺文档就不能够通过短语检索到。所以短语匹配跟普通的match查询并没有本质区别,它在执行检索前也会分析并提取查询条件中的词项。只是在检索过程中,match查询只要包含一个或多个词项即视为满足条件,而短语匹配不仅要求全部词项都要包含,还要保证它们在原始文档中出现的先后次序与查询条件中的次序一致。Elasticsearch提供了两种基于全文的短语查询,它们是match_phrase查询和match_phrase_prefix查询。

 

1.match_phrase查询

 

match_phrase查询是基于全文的查询,所以会将查询条件按顺序分词,然后再查看它们在字段中的位置之差,只有差值都为1才满足查询条件。换句话说,这些词项要在字段中依次出现,并且是紧挨着的。例如:

 

在示例5-19的请求中,使用firefox和6.0a1两个词项匹配message字段,并且它们在所有词项中的位置之差应该为1。match_phrase查询提供了一个用于控制词项之间位置差的参数slop,默认情况下它的值是1。可以通过加大slop的值,扩大短语检索的匹配范围。

 

通过词项位置来匹配短语的方式大大降低了索引数量,也增强了短语匹配的灵活性。但这要求在文档编入索引时,将词项在字段中的位置也编入索引。默认情况下,只有text类型的字段才会自动将词项位置编入索引,其他类型字段只存储文档ID。这在本书第2章有过介绍,详细请参考第2.2.1节。

 

2.match_phrase_prefix查询

 

除了match_phrase查询以外,Elasticsearch还提供了基于前缀的短语匹配。在这种匹配中,最后一个词项可以设置为前缀。例如示例5-19中的请求使用前缀短语匹配可以写成:

 

在示例5-20的查询条件中,最后一个词项只给出了前缀6.0,这样会把Firefox版本6.0的文档检索出来。显然,前缀短语匹配可以用于智能提示。即在用户输入6.0时,将所有以6.0开头的短语全部找出来。为了控制提示数量,Elasticsearch还提供了一个参数max_expansions用于控制匹配结果的上限,默认情况下这个参数的值是50。

 

5.2.3 查询字符串

 

在本书第4章4.1节介绍基于URI的_search接口调用时曾经讲解过查询字符串,这里的查询字符串与第4.1节介绍的是同一概念。查询字符串是具有一定逻辑含义的字符串,因此它不会直接使用分析器做分词提取,而是先通过某种类型的解析器解析为逻辑操作符和更小的字符串。例如,查询字符串“(firefox 6.0a1)OR(chrome 11.0.696.50)”中,OR为逻辑操作符,它会先被解析为“firefox 6.0a1”和“chrome 11.0.696.50”两部分,然后这两部分再使用字段的分析器提取词项。逻辑操作符除了OR以外还有AND,可以使用“field_name:query_term”的形式指定匹配的字段,所以查询字符串可以支持多字段检索。需要特别注意的是,操作符OR和AND必须大写,如果使用小写将会被解析为词项参与到查询条件中。

 

查询字符串还可以使用双引号做短语查询,例如“\"firefox 6.0 a1\" OR \"chrome 11.0.696.50\"”将被解析为两个短语查询“firefox 6.0 a1”和“chrome 11.0.696.50”。

 

1.query_string查询

 

query_string查询是基于请求体执行查询字符串的形式,它与基于URI时使用的请求参数q没有本质区别。例如可以使用示例5-21中的形式执行查询字符串:

 

query_string查询使用query参数接收查询字符串,而使用default_field参数指定匹配查询字符串的默认字段名称。default_field参数并不是必须要指定的,默认字段名称也是由index.query.default_field参数指定。所以如果没有指定default_field,也没有在查询字符串使用“field_name:query_term”的形式指定字段,检索将在所有字段上进行。query_string查询可用参数还有很多,比如可以使用fields参数指定多字段匹配,使用default_operator指定词项之间的逻辑运算关系等。表5-3列出了query_string支持的参数。

 

由于query_string查询也支持多字段,所以它的相关性评分与multi_match一样比较复杂,详细请参见第6.2.6节。

 

2.simple_query_string查询

 

顾名思义,simple_query_string查询是对query_string查询的简化,它的简化体现在它解析查询字符串时会忽略异常,并且引入了一些更为便捷的简化操作符。在使用上simple_query_string没有default_field参数,而是使用fields参数指定检索的字段名称。例如示例5-21中的请求使用simple_query_string查询时可以写成:

 

在示例5-22中,查询字符串中的“ |”就是simple_query_string引入的简化操作符,代表逻辑或操作即OR操作符。simple_query_string查询支持的简化操作符见表5-4。

 

在默认情况下,simple_query_string查询支持表5-4中所有简化操作符,但可以通过flags参数来开启或关闭这些简化操作符。在flags可接收的参数中,ALL代表开启所有简化操作符,是默认值;NONE代表关闭所有简化操作符,WHITESPASE代表使用空格分词。其余flags可接收值在表5-4中已经给出,可以使用“| ”组合多个选项。

 

5.2.4间隔查询

 

间隔查询(Intervals)是在Elasticsearch版本7中才引入的一种查询方法,这种方法与短语查询类似,但比短语查询更为强大。它可以定义一组词项或短语组成的匹配规则,然后按顺序在文本中检查这些规则。这种查询之所以被称为间隔查询,就是因为规则与规则之间可以通过max_gaps参数定义间隔。间隔查询的关键字为intervals,主要包括all_of、any_of和match三个参数,这三个参数又有各自的子参数。先来看一个示例:示例5-23

 

在示例5-23中,intervals查询首先定义了要匹配的字段为message字段,然后使用all_of指明所有规则都需要满足。在示例中,all_of参数通过intervals子参数定义了一组匹配词项或短语的规则,而ordered参数设置为true则表明这些规则需要按顺序匹配。all_of可用子参数见表5-5。

 

在示例5-23中,intervals参数中定义了两个匹配规则,而且它们之间必须按照定义的顺序匹配。但由于没有定义max_gaps,所以它们之间的间隔并没有限制。在第一个匹配规则中,使用match.query定义了一段文本,由于同时还使用match.max_gaps定义了间隔为0,所以文本中的词项就必须是紧挨着的短语。match可用子参数见表5-6。

 

示例5-23中定义的第二个匹配规则使用了any_of,它的含义是只要匹配intervals中的任意一个规则即可。所以示例5-23整个查询条件的含义就是找到包含“get beats metricbeat”短语,并且在其后有404或503的文档。所以这相当于把请求“GET /beats/metricbeat”,并且返回响应状态码为404或503的请求日志全部检索出来了。

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。