通配符(wildcard)和正则表达式查询(regexp),相关的还有prefix前缀查询(前缀查询我们这里用不到,不做过多说明),他们都是底层基于词的查询,注意事基于词的,其工作方式就是扫描倒排索引中的词列表才能找到所有匹配的词,然后依次获得每个词的文档ID,这三种查询都需要考虑性能问题,为了节省资源,要避免使用左通配这样的模式匹配(如: foo 或 .foo 这样的正则式)。
prefix 、 wildcard 和 regexp 查询是基于词操作的,如果用它们来查询 analyzed 字段,它们会检查字段里面的每个词,而不是将字段作为整体来处理。
比方说包含 “Quick brown fox” (快速的棕色狐狸)的 title 字段会生成词: quick、brown和fox:
{ "regexp": { "title": "br.*" }}
//会匹配到
{ "regexp": { "title": "Qu.*" }}
//不会匹配到,因为索引里是quick而不是Quick
{ "regexp": { "title": "quick br*" }}
//不会匹配到,因为quick和brown在词表(倒排索引)中是分开的
基于以上分析,因为我们项目的字段处理都是分词的,所以需要查询字段对应的keyword字段,这一点很重要,之前没有这么做,是导致正则查询不准确的原因之一。
-
通配符查询
通配符查询后台会直接查询分词字段
常用的通配符有:?匹配任意字符,*匹配0或多个字符,.匹配单个字符等等,这些规则与正则里边的使用大多是类似的。比如查询李又亭,可以直接这样通配“李又.” -
正则表达式查询
正则表达式查询后台会查询keyword字段
这里我们使用的查询语法应该是Lucene regular expression engine支持的语法,正则表达式语法在这里只支持一些常用的,所以这里也仅介绍Lucense支持的语法。es查询语法中只有regexp和query_string查询支持正则,我们所使用的就是query_string查询,query_string支持很多语法,以/开始和结束时es会认为是正则表达式查询,正常我们在写正则表达式的时候,需要以^标识开始,以$标识结束,称之为Anchor,Lucene总是会帮我们做这一步,所以不需要标识开始和结束。
首先某些特定字符被做为了保留字,使用的时候必须要转义,转义的意思是如果你真正要查的内容是这些字符的时候需要在字符前边加"",比如“*”、"+" 、"\",这些字符包括:
. ? + * | { } [ ] ( ) " \ # @ & < > ~
还有就是,以""包起来的内容需要全部匹配( interpreted literally),举个栗子:有字符串“李又亭”,正则表达式"/"李2亭"/"是匹配不上的- ''."代表任意字符,比如有字符串"abcde"
ab... # match
a.c.e # match - "+"代表加号之前的最小单元匹配一次或多次,比如有字符串“aaabbb”
a+b+ # match
aa+bb+ # match
a+.+ # match
aa+bbb+ # match - ""代表星号之前的最小单元匹配零次次或多次,比如有字符串“aaabbb”
ab* # match
abc* # match
.bbb. # match
aaabbb # match - "?"代表星号之前的最小单元匹配零次次或一次,比如有字符串“aaabbb”
aaa?bbb? # match
aaaa?bbbb? # match
.....?.? # match
aa?bb? # no match - "{}"可以被用来声明之前的最小单元出现一个最小次数和最大次数,比如:
{5} # repeat exactly 5 times
{2,5} # repeat at least twice and at most 5 times
{2,} # repeat at least twice
比如有一个字符串"aaabbb"
a{3}b{3} # match
a{2,4}b{2,4} # match
a{2,}b{2,} # match
.{3}.{3} # match
a{4}b{4} # no match
a{4,6}b{4,6} # no match
a{4,}b{4,} # no match - "()"被用来分组构成一个最小单元或者说是子模式,比如有一个字符串"ababab":
(ab)+ # match
ab(ab)+ # match
(..)+ # match
(...)+ # no match
(ab)* # match
abab(ab)? # match
ab(ab)? # no match
(ab){3} # match
(ab){1,2} # no match - "|"可以作为一个或的操作符,符号左右侧的内容有一个匹配上就认为是匹配成功,这里注意这个匹配是最长匹配,而不是最短匹配(这一点可以看下边例子好好体会一下),比如有''aabb"
aabb|bbaa # match
aacc|bb # no match
aa(cc|bb) # match
a+|b+ # no match
a+b+|b+a+ # match
a+(b|c)+ # match - "[]"作为一个选择符,意思是中括号内的任意字符出现都认为是匹配成功,加上代表相反的意思,也就是说后的任意字符不出现都认为是匹配成功。
[abc] # 'a' or 'b' or 'c'
[a-c] # 'a' or 'b' or 'c'
[-abc] # '-' or 'a' or 'b' or 'c'
[abc-] # '-' or 'a' or 'b' or 'c'
[^abc] # any character except 'a' or 'b' or 'c'
[^a-c] # any character except 'a' or 'b' or 'c'
[^-abc] # any character except '-' or 'a' or 'b' or 'c'
[^abc-] # any character except '-' or 'a' or 'b' or 'c'
上边出现的破折号(-)意思是一个连续区间,比如[a-z]表示小写字母a到小写字母z的共26个字符中的任意字符,[0-9]表示0到9的共10个字符中的任意字符,如果想表示字符本身就需要转义,比如[^abc-] 表示匹配除了a、b、c、-之外的任意字符。关于破折号,比如有字符串:"abcd":
ab[cd]+ # match
[a-d]+ # match
[^a-d]+ # no match
可选操作符(在正则表达式中也支持一些特殊的操作符,可以使用flags字段控制是否开启,下边都是可选操作符) -
“~”
,Complement(波浪线),比如有正则表达式"ab~cd"
的意思是1>必须要以a开头;2>a后边必须跟着b;3>b后边跟着任意长度的字符比如aa,b,d等等但不能是单个字符c;4>必须以d结尾,再比如有字符串"abcdef":
ab~df # match
ab~cf # match
ab~cdef # no match
a~(cb)def # match
a~(bc)def # no match
10."<>",Interval,支持一个数值的范围,比如有"foo80":
foo<1-100> # match
foo<01-100> # match
foo<001-100> # no match11."&",Intersection,可以实现多个匹配的连接,比如有字符串"aaabbb":
aaa.+&.+bbb # match
aaa&bbb # no match
12."@",Any,可以匹配任意的字符串
@&~(foo.+) # anything except string beginning with "foo"附录:
上边介绍了一些Lucene支持的常用正则语法,相信你已经知道该怎么写自己的正则表达式了,如果还不是很清楚具体怎么写,这里再举个详细的栗子。
比如现在es里有这样两个文档:
{ "1_t": "1", "dataType": null, "dataSource": 2, "2_t": "蒋仁杰", "3_t": "China", "4_t": "CT1439201", "5_t": "蒋仁杰-China" } { "1_t": "8", "dataType": null, "dataSource": 2, "2_t": "李又亭", "3_t": "China", "4_t": "WB2661715", "5_t": "8-æ��å��äº", "6_t": " 000000008--李又亭 --China --WB2661715 --8-?????????" }
- 根据姓名查询姓蒋的,正则应该是这样:
/蒋.+/ 或者/蒋.{1,}/
- 根据姓名查询姓蒋的,然后名字是两个字的文档,正则应该是这样:
/蒋.{2}/
- 查询姓名是蒋仁杰或者李又亭的,正则应该这样写:
/(蒋仁杰)|(李又亭)/
- 查询姓名是李又亭或者张又亭或者王又亭的,正则应该这样写:
/[李张王]又亭/
- 查询姓张或者姓李而且名字里带亭字的,正则应该这样写:
/[张李].*亭{1,}.*/ 或者/[张李].*亭+.*/
- ''."代表任意字符,比如有字符串"abcde"