Elasticsearch的几种常用查询

相信大家在做大数据量文本检索的时候都会想到使用elasticsearch(https://github.com/elastic/elasticsearch

关于es的一些特性在网上有很多资料,我这里就不重复了,最近因为工作调动的关系,重新使用到es,之前使用的大多是ik分词等,甚至只是拿来当做一个存储层(结合Flink CDC),如果熟读过官网会发现其实有很多特性,特别在当前(2023.05)已经是8.x的版本https://www.elastic.co/guide/en/elastic-stack/current/overview.html

下面我们来使用一下基础的查询,版本7.16

先说es中一个重要的概念,相关性,相似度计算得出检索的文本匹配,其中有两个概念,TF检索词频和IDF反向文档频率

TF:检索的词出现频率越高,相关性越高

IDF:包含检索词的频率越高,这个检索的相关性比重越低,什么意思呢?是不是跟TF有冲突?其实是为了一种情况,如果某个词在所有文档都出现了,那这个词可以说对本次检索意义不大了,所以用这个方式约束词频出现太高的情况。另外字段长度指的是被检索的,长度,相当于在es中存储的数据字段长度,如果越长的情况,相关性就会越低

(还有个关键词是boost,如果有使用过kibana的都会发现在查询语句中会有这个参数,这个参数其实是权重的意思,可以通过这个字段提高查询的权重)

介绍一下分词器的原理:

分词器一般是利用了tokenizer,把数据切割成很多个tokens,比如:我们是21世纪的新人类,那么不同分词器就会把他拆分成若干个tokens,如:我们,我们是,们是,是21世纪,等等,另外分词也有各种算法,比如我们常用的ik、ngram这些插件等,另外还有哑巴算法等(这些直接对词语进行分词操作的),另外这个只作用于text类型上,像keyword都是全值匹配,毕竟它这个字段就命名为关键词嘛

好了正式介绍基础查询:

term/terms:使用term查询如果是字段类型是keyword的话就是等值,如果是text则会进行分词(具体要看分词settings是字段或者查询)

GET INDEX_NAME/_search
{
    "query" : {
       "term" : {
          "FIELD" : {
              "VALUE" : ""
           }
       }
   }
}
GET INDEX_NAME/_search
{
    "query" : {
       "terms" : {
          "FIELD" : [
              "VALUE1",
              "VALUE2"
           ]
       }
   }
}

terms_set:和terms相似,但是可以让查询的数据包含个数的方式,使用minimum_should_match_field可以让查询的数据最少包含个数

GET INDEX_NAME/_search
{
  "query": {
    "terms_set": {
      "programming_languages": {
        "terms": ["c++","java","php"],
        "minimum_should_match_field": "required_match"
      }
    }
  }
}

exists:返回改字段不为空的文档,如null或[]

GET INDEX_NAME/_search
{
  "query": {
    "exists": {
      "field": "FIELD"
    }
  }
}

fuzzy:模糊查询,相当于可以运行查询的字段不太一样也可能查询出来,比如:box→fox,box→ox,box→bxo,这个去取决于,配置的编辑距离,即可容忍不一样的字段个数

GET INDEX_NAME/_search
{
  "query": {
    "fuzzy": {
      "FIELD": "xxx",
      "fuzziness" : 2
    }
  }
}

prefix:很好理解,前缀查询,以下例子,包含xxx前缀的数据

GET INDEX_NAME/_search
{
  "query": {
    "prefix": {
      "FIELD": "xxx"
    }
  }
}

range:范围查询,下面是对age这个字段,大于等于10或者小于等于20的返回查询

GET INDEX_NAME/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 10,
        "lte": 20
      }
    }
  }
}

regexp:正则查询

GET INDEX_NAME/_search
{
  "query": {
    "regexp": {
      "FIELD": "xx.*yy"
    }
  }
}

wildcard:通配符查询,支持两种*和?,*是匹配多个,?是单个,跟mysql的like很像,所以可以举一反三,不建议在开头使用*和?,会影响性能

GET INDEX_NAME/_search
{
  "query": {
    "wildcard": {
      "name": {
        "value": "xxx*"
      }
    }
  }
}

interval:根据特定排序,这个有点像复杂事件,可以查询出来的数据让一些内容在前后左右,有特定顺序(这种查询要看场景,我并没有使用过,这里仅演示)以下查询会出现带有jay chou的字符,如果把ordered去掉,那么chou jay也会被查询出来,其他参数max_gaps影响这两个值最大距离(从字段是字面意思)

GET INDEX_NAME/_search
{
  "query": {
    "intervals": {
      "name": {
        "match": {
          "query": "jay chou",
          "max_gaps": 0,
          "ordered": true
        }
      }
    }
  }
}

match:就是匹配,可以使用到分词

GET INDEX_NAME/_search
{
  "query": {
    "match": {
      "name": "jay chou"
    }
  }
}

match_bool_prefix:等价于多个term

GET INDEX_NAME/_search
{
  "query": {
    "match_bool_prefix": {
      "name": "jay chou"
    }
  }
}
GET INDEX_NAME/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "name": {
              "value": "jay"
            }
          }
        },
        {
          "prefix": {
            "name": {
              "value": "chou"
            }
          }
        }
      ]
    }
  }
}

match_pharse:先解析检测的词,在搜索包含的,以下会不会查出jay chou中间有其他词的数据,当然也可以放宽增加slop参数,则可以让jay chou中间有slop个数据,比如1就可以查出jay xx chou

GET INDEX_NAME/_search
{
  "query": {
    "match_phrase": {
      "name": "jay chou"
    }
  }
}

match_phrase_prefix:相当于match_bool_prefix + match_phrase,先解析分词,再和match_phrase匹配

GET INDEX_NAME/_search
{
  "query": {
    "match_phrase_prefix": {
      "name": "jay chou"
    }
  }
}

multi_match:顾名思义,可就是多个匹配,一个检索词可以匹配多个字段,同时可以结合and、or等操作丰富查询的结果,

GET INDEX_NAME/_search
{
  "query": {
    "multi_match": {
      "query": "jay",
      "fields": [
        "name",
        "info"
      ]
    }
  }
}

commom:查询会把查询语句分成两个部分,较为重要的分为一个部分,不那么重要的为一个部分,分别用low_freq_operatorhigh_freq_operator以及minimum_should_match来控制这些语句的表现,在进行查询之前需要指定一个区分高频和低频词的分界点,也就是cutoff_frequency,它既可以是小数比如0.001代表该字段所有的token的集合里面出现的频率也可以是大于1的整数代表这个词出现的次数。当token的频率高于这一个阈值的时候,他就会被当作高频词

GET INDEX_NAME/_search
{
  "query": {
    "common": {
      "body": {
        "query": "nelly the elephant as a cartoon",
        "cutoff_frequency": 0.001,
        "low_freq_operator": "and"
      }
    }
  }

相当于

GET INDEX_NAME/_search
{
  "query": {
    "bool": {
      "must": [
        {"term": {"body": "nelly"}},
        {"term": {"body": "elephant"}},
        {"term": {"body": "cartoon"}}
      ],
      "should": [
        {"term": {"body": "the"}},
        {"term": {"body": "as"}},
        {"term": {"body": "a"}}
      ]
    }
  }
}

query_string/simple_query_string:输入一个查询语句,返回和这个查询语句匹配的所有的文档,这个我们用的比较少,这个查询语句不是简单的检索词,而是包含特定语法的的搜索语句,里面包含操作符比如ANDOR,在进行查询之前会被一个语法解析器解析,转化成可以执行的搜索语句进行搜索。用户可以生成一个特别复杂的查询语句,里面可能包含通配符、多字段匹配等等。

通过上面的学习之后,我进行的一个实战

需求描述:需要通过用户输入的一些关键词,这个关键词分为三个部分,暂时描述为A、B、C,三个部分在elasticsearch中已经有现成的数据,但是由于架构较为古老,使用的还是springboot1.x的版本,而elasticsearch的版本为7.x,无法使用springboot-data-elasticsearch的封装,目前需要做一个匹配,输入的A和B,需要从头逐字匹配,即:输入jaychou,那么需要匹配jaychou、jaycho、jaych、jayc,业务要求最少四个,最多不超过二十个,分别ABC三个字段

思考:首先思考逐字匹配,那么如果考虑到search_analyzer进行拆词的话,之前只使用过ik,ik的几种分词不满足,考虑是否有其他分词方式,于是通过互联网的查询找到elasticsearch官方有个edge_ngram的分词方式刚好满足需求(可以自行了解一下,分词方式是例如:我们是21世纪新人类,在规定最大和最小边界的情况下,我,我们是,我们是2,我们是21,我们是21世等),另外由于无法使用springboot-data的包,所有创建索引、分词器等都是手写(坑...),@multiField无法使用,如果手写一个,由于嵌套,维护成本可能比较高,于是新增一个字段(即单独多一个edge分词的字段)

按照刚刚的思考,创建了新的索引字段,使用edge分词,输入分词效果还不错,但是随之出现另一个问题,大家都知道_score,这是一个评分系数,是根据es匹配的算法得出的结果,使用上述分词之后有一个情况,由于需要逐匹配,那么包含越多的情况应该分数越高,但是由于不改写评分算法的情况下,如果数据中出现jay的分数可能会比jayc,为什么呢,很好理解,jay的那条数据全等了,但是按照需求,应该jayc分数应该更高

解决方案:

经过查阅和思考,最终方案,把查询的数据用算法截取为jay、jayc、jaych、jaycho、jaychou,然后全部丢进should条件中,使用prefix的方式进行前缀查询,再通过对_score进行sort排序,这样由于业务限制,prefix最多不超过16个条件,同时也不会出现jay全等分数比包含jayc的数据分数高的情况

elasticsearch是当前大热的文本检索框架,很好解决了大数据量检索的问题,我记得在之前的公司做订单中心这个功能时了解过,京东的订单中心也是使用elasticsearch来实现的,并对部署和使用进行了深层次的优化来满足京东庞大的订单量,总之不管存储、检索功能,elasticsearch表现都极好,目前也在飞速迭代,期待未来的一个形态。

参考文章:

https://blog.csdn.net/paditang/article/details/79098830

https://zhuanlan.zhihu.com/p/137575167?utm_source=wechat_session

Leave a Reply

Your email address will not be published. Required fields are marked *