likes
comments
collection
share

对es订单的账号和邮箱进行模糊查询

作者站长头像
站长
· 阅读数 33

产品提了个小需求,后台订单列表要能用账号和邮箱进行模糊查询,如下图所示。

对es订单的账号和邮箱进行模糊查询

本来以为很简单,几分钟就能搞定,最后没想到折腾了1,2小时。


分析

目前我们订单是会从MySQL同步到es的,后台查询就是走的es里的订单。也就是说我们es订单索引是没有这2个字段的,需要加一下。

同步的代码那边加2个字段简单,然后就是查询条件那边代码也要加下,其实就是dsl语句加2个查询呗,尽管我对es不是很熟没用过几次,但这个crud我还是会的(自信)。

于是我"胯"地一下就写完了然后果然拉胯了。 调试时发现自己插入个订单,账号和邮箱这2个字段存进去了,但是根据账号能查到这条数据,根据邮箱查却查不到。

被领导问进度时候我说还没搞定,这个问题困惑了我,且我这才发现我实现的只是精确查询,我压根没注意需求要求的是模糊查询,于是我也被吊了一下,那么我必须记录下这篇文章了。


问题

现在是2个问题:

  • 为什么我用term查询查不到这条订单,精确匹配都做不到了吗! 我看原先别人java代码写的dsl语句里对订单的手机号、订单编号都是用的term查询啊! 代码截图如下 对es订单的账号和邮箱进行模糊查询
  • 如何实现账号和邮箱字段的模糊查询!

带着这2个问题,我需要先分析下为什么我写的dsl语句无法对邮箱精确查询,是不是该用其他语句。 然后再研究下怎么模糊查询,毕竟需求要求是模糊查询。


排查-解决

1.为什么无法对邮箱精确查询

先分析邮箱查不到的原因,我代码是这么写的,直接复制别人的,改动成email了。

if (StringUtils.isNotBlank(param.getEmail())) {
    queryBuilder.filter(QueryBuilders.termQuery("email", param.getEmail()));
}

输出dsl语句发现是term查询。然后我猛然记得了,邮箱是带有特殊字符"@"的,可能是词条分掉了。 但是具体为什么查不到,我说不出原因,因为我对es并不熟悉,原理也不了解。 于是去官方文档Terms Query看看,结果发现人家说的很清楚啊!

如图所示,大大的警告: "避免对text类型的字段用词条查询!" 对es订单的账号和邮箱进行模糊查询

为什么不能用term对text类型的字段用词条查询?

原因在文档下方示例也说的很清楚。 我这里针对我的邮箱来说明下, 比如邮箱"123@qq.com"则会被标准分词器分成"123","qq.com",结果如下。 感兴趣的也可以用这个语句来用1个分词器验证下。 这个语句文档也有说明:Test an analyzer

对es订单的账号和邮箱进行模糊查询

因为es就是会将text字段进行分词,而默认采用的则是标准分词器

那知道了这个,文档也说了词条查询则不会去分析搜索的词条而依旧是查询整个输入的短语,那"123@qq.com"自然是查不到了,而有意思的是输入"123"则能查到,不知道的还以为是模糊查询呢!

The term query does not analyze the search term. The term query only searches for the exact term you provide. This means the term query may return poor or no results when searching text fields.

所以term查不到是既定事实了。

2.如何对邮箱进行精确查询?

所以需要连忙"补救",改动邮箱那块的dsl语句。那该用哪个query呢,继续看文档。

根据上面的分析觉得词条查询这名字很让人误解,因为它其实不是查询词条,,即不会去查分词好的词条。 原理是不会用分词器分析输入。

这么说那么肯定得有语句是会用到分词器的。看文档也发现了与词条级别的查询对应有个全文查询,如文档所示:

对es订单的账号和邮箱进行模糊查询 可以看到有matchmatch_phrase2个语句。

该用match还是match_phrase?

Match query文档说明的也比较复杂,当然也证明了它的功能应该很强。

Match phrase query则没多少内容,说明了下有个slot,可以设置输入文本每个单词的间隔。

2个查询的区别我这边测试出来的。

小测试

插入2条记录("testf"是我创建的只有1个字段"name"的索引):

POST /testf/user
{
  "name":"this is a"
}
POST /testf/user
{
  "name":"this is a test"
}

然后分别用2种语句去查这2条记录

GET testf/_search
{
  "query":{
    "match": {
      "name": {
        "query": "this is a test"
      }
    }
  }
}
GET testf/_search
{
  "query":{
    "match": {
      "name": {
        "query": "this is a "
      }
    }
  }
}
GET testf/_search
{
  "query":{
    "match_phrase": {
      "name": {
        "query": "this is a test"
      }
    }
  }
}
GET testf/_search
{
  "query":{
    "match_phrase": {
      "name": {
        "query": "this is a "
      }
    }
  }
}

结果是:

  • match query 都能查出来
  • match_phrase query 只能查出来 “this is a test”这一条。

当然match query 可以带很多参数,可以有"and"、"or"操作符等。 但我这里测试就是简单的查询,那么结论用 match_phrase。因为它类似精确查询了,而match query是查处所有包含这些词条的文档出来,在有特殊字符的时候效果类似模糊查询了,给客户用的话肯定会具有迷惑性。

那么将邮箱的代码改动如下:

if (StringUtils.isNotBlank(param.getEmail())) { queryBuilder.filter(QueryBuilders.matchPhraseQuery("email", param.getEmail())); }

精确查询不用term而用match_phrase那解决了精确查询的问题。

那现在问题是如何对字段模糊查询了。

3.如何实现对账号和邮箱的模糊查询

前面词条查询和match都在对带有特殊字符即能会被分词的文本时有模糊查询的"效果"。

但是需求要求的模糊查询肯定是要求对所有文本,比如我们系统的账号,基本就是纯字母串了,那不直接没有模糊查询的效果了。

分析下,应该还是要从分词器这块下手。

领导和我说他之前弄个1个onechar的自定义分词器,让我这次直接用即可。 那我这边先记录下如何指定自定义分词器的语句,而如何自定义1个分词器下次再记录了。

// 关闭索引
POST /order/_close

// 配置自定义分词器
PUT /order/_settings
{
    "analysis": {
      "tokenizer": {
        "onechar": {
          "type": "ngram",
          "min_gram": 1,
          "max_gram": 1
        }
      },
      "analyzer": {
        "autocomplete": {
          "tokenizer": "onechar",
          "filter": [
            "lowercase"
          ]
        }
      }
    }
}

// 打开索引
POST /order/_open

// 修改字段的类型和分词器(已存在的字段的类型无法变更)
PUT /order/_mapping/_doc
{
      "properties": {
        "accountNo": {
          "type": "text",
          "analyzer": "autocomplete",
          "search_analyzer": "autocomplete",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        }
      },
        "email": {
          "type": "text",
          "analyzer": "autocomplete",
          "search_analyzer": "autocomplete",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        }
      }

}

小结

  • 避免使用term词条查询text字段。如果用了:
    • 在对手机号这种纯数字的字段上其实是精确匹配了
    • 但无法支持邮箱这种带有特殊字符的字段,表现为: 精确匹配不到。有时却能模糊匹配。
  • 对带有特殊字符的text字段用全文查询,可以是match_phrase query
    • mMatch query` 看具体场景也可以使用。
  • 自定义1个按1个字符分词的分词器来进行模糊查询

这次整理的还漏掉了"keyword"和大小写的问题,下次补充下。