Elasticsearch 随笔

查询API关键字说明

关键字 说明
match 比如"宝马多少马力"会被分词为"宝马 多少 马力", 所有有关"宝马 多少 马力", 那么所有包含这三个词中的一个或多个的文档就会被搜索出来
match_phrase 一个文档"我的保时捷马力不错"也会被搜索出来,那么想要精确匹配所有同时包含"宝马 多少 马力"的文档就要用match_phrase
term 代表完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇
fuzzy 近似度查询。minimumSimilarity表示是最小相似度,可以通过指定一个相似度来决定模糊匹配的严格程度。默认为0.5,当这个值越小,通过模糊查找出的文档的匹配程度就越低,文档的数量也就越多;当这个值越大,说明要匹配程度更大,匹配的文档数也就越少,当相似度设置为1,那么就退化为TermQuery查询,所以当这个值>=1或<0会抛出IllegalArgumentException异常

1.matchAllQuery()

匹配全部文档

 QueryBuilder queryBuilder = QueryBuilders.matchAllQuery();  

2.matchQuery(String name,Object text)

匹配单个字段,匹配字段名为filedname,值为value的文档

//单个匹配,搜索name为jack的文档  
QueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "jack");  

3.matchPhraseQuery(String name, Object text)

匹配单个字段,会拆字

//搜索name中包含有music的文档
QueryBuilder queryBuilder = QueryBuilders. matchPhraseQuery("name", "music");

4.termQuery(String name, String value)

精确匹配,不分词

//搜索name中包含有music的文档
QueryBuilder queryBuilder = QueryBuilders. termQuery("name", "music");

5.multiMatchQuery(Object text, String... fieldNames)

多个字段匹配某一个值

//搜索name中或interest中包含有music的文档(必须与music一致) 
QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery("music",  
           "name", "interest");

6.termsQuery(String name, String... values)

多词条查询

//Guangzhou、Shaoguan在addr字段中进行查询,只要满足一个就会返回。但是,并不会返回二者的并集。
QueryBuilder query = QueryBuilders.termsQuery("addr", "Guangzhou", "Shaoguan");

7.wildcardQuery(String name, String query)模糊查询

模糊查询,?匹配单个字符,*匹配多个字符

//搜索名字中含有jack文档(name中只要包含jack即可) 
WildcardQueryBuilder queryBuilder = QueryBuilders.wildcardQuery("name",  
            "*jack*");

8.regexpQuery(String name, String regexp)

正则表达式查询

//搜索WC开头的邮政编码
WildcardQueryBuilder queryBuilder = QueryBuilders.regexpQuery("postcode",  
            "W[0-9].+");

9.prefixQuery(String name, String prefix)

//搜索Bei开头的地址
QueryBuilder query = QueryBuilders.prefixQuery("addr", "Bei");

10.fuzzyQuery(String name, String prefix)

//找出含有华南农业大学或相近地址的数据
QueryBuilder query = QueryBuilders.fuzzyQuery("addr", "华南农业大学");

11.rangeQuery(String name)

// 闭区间
QueryBuilder query = QueryBuilders.rangeQuery("age").from(10).to(20);
// 开区间
QueryBuilder query = QueryBuilders.rangeQuery("age").gt(10).lt(20);

操作篇

索引删除

curl -XDELETE {url}/bom?pretty

索引创建

curl -XPOST  {url}/bom?pretty

索引设置settings

curl -XPUT {url}/bom -d '{
    "settings": {
        "analysis": {
            "analyzer": {
                "charSplit": {
                    "type": "custom",
                    "tokenizer": "ngram_tokenizer"
                }
            },
            "tokenizer": {
                "ngram_tokenizer": {
                    "type": "nGram",
                    "min_gram": "1",
                    "max_gram": "1",
                    "token_chars": [
                        "letter",
                        "digit",
                        "punctuation"
                    ]
                }
            }
        }
    }
}'

索引查看settings

curl -XGET {url}/bom/_settings?pretty

索引设置mappings

curl -XPOST {url}/bom/sku/_mappings -d'{
    "properties": {
        "name": {
            "type": "string",
            "analyzer": "charSplit"
        },
        "openid": {
            "type": "string",
            "analyzer": "charSplit"
        },
        "specification": {
            "type": "string",
            "analyzer": "charSplit"
        },
        "unit": {
            "type": "string"
        },
        "subIds": {
            "type": "string"
        },
        "providerId": {
            "type": "string"
        },
        "skuId": {
            "type": "string"
        }
    }
}'

索引查看mappings

curl -XGET {url}/bom/_mappings?pretty

分词器

标准分词器 Standard Tokenizer

一个标准的分词器提供基于语法的分词器,那是一个适合大部分欧洲语言文档的很好的分词器。分词器实现Unicode文本分割算法,该分割算法在Unicode Standard Annex #29中指定。

参数 说明
max_token_length 定义最长的分词长度

连词分词器 NGram Tokenizer(边际连词分词器 Edge NGram Tokenizer)

如果词的长度大于最短词长度则分词,则依次分成最小长度递进到最大长度的词。
例如:中华人民共和国 min_gram=2,max_gram=3。结果:中华、中华人、华人、华人民、人民、人民共、民共、民共和、共和、共和国、和国。

参数 说明 默认值
min_gram 分词后词语的最小长度 1
max_gram 分词后词语的最大长度 2
token_chars 设置分词的形式,例如,是数字还是文字。elasticsearch将根据分词的形式对文本进行分词。 -
参数 说明 默认值
letter 单词,字母 a, b, ï or 京 1
digit 数字3 or 7 2
whitespace 例如 " " or "\n" -
punctuation 例如 ! or " -
symbol 例如 $ or √ -

token_chars 所接受的以下形式:

参数 说明 默认值
letter 单词,字母 a, b, ï or 京 1
digit 数字3 or 7 2
whitespace 例如 " " or "\n" -
punctuation 例如 ! or " -
symbol 例如 $ or √ -

关键字分词器 Keyword Tokenizer

参数 说明 默认值
buffer_size 关键字的长度 256

keyword分词器输出和它接收到的相同的字符串。即不分词

参数 说明 默认值
buffer_size 关键字的长度 256

字符分词器 Letter Tokenizer

字符(letter)类型分词器将文本按非字符(non-lentter)进行分词。这就是说,它定义临近的最大长度的字符为一个词。注意,这个适合大部分欧洲的语言,但是对一些亚洲的语言来说就很糟糕了,它们的词不是以空格来分割的。

小写分词器 Lowercase Tokenizer

lowercase将词全部转换成小写,其他的同letter Tokenizer

空格分词器Whitespace Tokenizer

以空格来分词

模式分词器/正则分词器 Pattern Tokenizer

参数 说明 默认值
pattern 正则表达式的pattern,默认是 \W+.
flags 正则表达式的 flags.
group 哪个group去抽取数据。 默认是 to -1 (split).

根据正则表达式的匹配规则来分词。

参数 说明 默认值
pattern 正则表达式的pattern,默认是 \W+.
flags 正则表达式的 flags.
group 哪个group去抽取数据。 默认是 to -1 (split).

标准Email URL分词器 UAX Email URL Tokenizer

和标准分词器一样,但是把email和url当作一个词。
UAX Email URL Tokenizer有人翻译为'不拆分email、url的分词器',觉得不太恰当,UAX个人认为是Unicode Standard Annex,见标准分词器中。

路径层次分词器 Path Hierarchy Tokenizer

例如:/something/something/else
结果:/something、/something/something、/something/something/else
默认定界符: /

典型的编译器 Classic Tokenizer

classic分词器提供基于语法的分词器,这对英语文档是一个很好的分词器。

Thai Tokenizer

对泰文进行分词,如果用在其他语言上同标准分词器。

IK中文分词器

创建索引

curl -XPOST http://localhost:9200/index/fulltext/_mapping -d'
{
        "properties": {
            "content": {
                "type": "text",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_max_word"
            }
        }
    
}'

配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer 扩展配置</comment>
    <!--用户可以在这里配置自己的扩展字典 -->
    <entry key="ext_dict">custom/mydict.dic;custom/single_word_low_freq.dic</entry>
     <!--用户可以在这里配置自己的扩展停止词字典-->
    <entry key="ext_stopwords">custom/ext_stopword.dic</entry>
    <!--用户可以在这里配置远程扩展字典 -->
    <entry key="remote_ext_dict">location</entry>
    <!--用户可以在这里配置远程扩展停止词字典-->
    <entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry>
</properties>

热更新 IK 分词使用方法

目前该插件支持热更新 IK 分词,通过上文在 IK 配置文件中提到的如下配置

    <!--用户可以在这里配置远程扩展字典 -->
    <entry key="remote_ext_dict">location</entry>
    <!--用户可以在这里配置远程扩展停止词字典-->
    <entry key="remote_ext_stopwords">location</entry>

其中 location 是指一个 url,比如 http://yoursite.com/getCustomDict,该请求只需满足以下两点即可完成分词热更新。

该 http 请求需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,这两者都是字符串类型,只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。

该 http 请求返回的内容格式是一行一个分词,换行符用 \n 即可。

满足上面两点要求就可以实现热更新分词了,不需要重启 ES 实例。

分词过滤器

standard

将字符串分割成单独的字词

lowercase

标记过滤器,将所有标记转换为小写。

stop

标记过滤器,删除所有可能会造成搜索歧义的停用词,如 a,the,and,is。

html_strip

删除所有的 HTML 标签,并且将 HTML 实体转换成对应的 Unicode 字符,比如将 Á 转成 Á。

stemmer

将单词转化为他们的根形态(root form)

ascii_folding

删除变音符号,比如从 très 转为 tres。

自定义分析器

PUT /my_index
{
    "settings": {
        "analysis": {
            "char_filter": { ... custom character filters ... },
            "tokenizer":   { ...    custom tokenizers     ... },
            "filter":      { ...   custom token filters   ... },
            "analyzer":    { ...    custom analyzers      ... }
        }
    }
}

作为例子,我们来配置一个这样的分析器:
用 html_strip 字符过滤器去除所有的 HTML 标签
将 & 替换成 and,使用一个自定义的 mapping 字符过滤器

"char_filter": {
    "&_to_and": {
        "type":       "mapping",
        "mappings": [ "&=> and "]
    }
}
  1. 使用 standard 分词器分割单词
  2. 使用 lowercase 标记过滤器将词转为小写
  3. 用 stop 标记过滤器去除一些自定义停用词。
"filter": {
    "my_stopwords": {
        "type":        "stop",
        "stopwords": [ "the", "a" ]
    }
}

根据以上描述来将预定义好的分词器和过滤器组合成我们的分析器:

"analyzer": {
    "my_analyzer": {
        "type":           "custom",
        "char_filter":  [ "html_strip", "&_to_and" ],
        "tokenizer":      "standard",
        "filter":       [ "lowercase", "my_stopwords" ]
    }
}

用下面的方式可以将以上请求合并成一条:

PUT /my_index
{
    "settings": {
        "analysis": {
            "char_filter": {
                "&_to_and": {
                    "type":       "mapping",
                    "mappings": [ "&=> and "]
            }},
            "filter": {
                "my_stopwords": {
                    "type":       "stop",
                    "stopwords": [ "the", "a" ]
            }},
            "analyzer": {
                "my_analyzer": {
                    "type":         "custom",
                    "char_filter":  [ "html_strip", "&_to_and" ],
                    "tokenizer":    "standard",
                    "filter":       [ "lowercase", "my_stopwords" ]
            }}
}}}

原理

倒排索引(inverted index)

例如,我们有两个文档,每个文档content字段包含:

1. The quick brown fox jumped over the lazy dog
2. Quick brown foxes leap over lazy dogs in summer

为了创建倒排索引,我们首先切分每个文档的content字段为单独的单词

Term Doc_1 Doc_2
Quick X
The X
brown X X
dog X
dogs X
fox X
foxes X
in X
jumped X
lazy X
leap X
over X X
quick X
summer X
the X

现在,如果我们想搜索"quick brown",我们只需要找到每个词在哪个文档中出现即可:

Term Doc_1 Doc_2
brown X X
quick X
----- ------- -----
Total 2 1

两个文档都匹配,但是第一个比第二个有更多的匹配项。 如果我们加入简单的相似度算法(similarity algorithm),计算匹配单词的数目,这样我们就可以说第一个文档比第二个匹配度更高——对于我们的查询具有更多相关性。
但是在我们的倒排索引中还有些问题:

  1. "Quick"和"quick"被认为是不同的单词,但是用户可能认为它们是相同的。
  2. "fox"和"foxes"很相似,就像"dog"和"dogs"——它们都是同根词。
  3. "jumped"和"leap"不是同根词,但意思相似——它们是同义词。

上面的索引中,搜索"+Quick +fox"不会匹配任何文档(记住,前缀+表示单词必须匹配到)。只有"Quick"和"fox"都在同一文档中才可以匹配查询,但是第一个文档包含"quick fox"且第二个文档包含"Quick foxes"。
用户可以合理地希望两个文档都能匹配查询,我们也可以做得更好。
如果我们将词为统一为标准格式,这样就可以找到不是确切匹配查询,但是足以相似从而可以关联的文档。例如:

  1. "Quick"可以转为小写成为"quick"。使用大小写过滤器
  2. "foxes"可以被转为根形式"fox"。同理"dogs"可以被转为"dog"。使用分词器
  3. "jumped"和"leap"同义就可以只索引为单个词"jump" 使用词汇单元过滤器
Term Doc_1 Doc_2
brown X X
dog X X
fox X X
in X
jump X X
lazy X X
over X X
quick X X
summer X
the X X

但我们还未成功。我们的搜索"+Quick +fox"依旧失败,因为"Quick"的确切值已经不在索引里,不过,如果我们使用相同的标准化规则处理查询字符串的content字段,查询将变成"+quick +fox",这样就可以匹配到两个文档。

推荐阅读更多精彩内容