queryString中检索词加不加引号?

原文地址:Elasticsearch干货(九):queryString中检索词加不加引号?_桃花惜春风的博客-CSDN博客

这篇文章回答我最近在实际运用中遇到的疑问,总结下:

  • 不加引号,match匹配
  • 加引号,等同于match_phrase。配合"slop": 0,等同于term精准匹配

对于刚接触搜索或者Elasticsearch的小白来说对queryString可能接触的不多,但是对于早期从事搜索的人来说queryString并不会陌生,它可以理解成检索表达式,但并不是elasticsearch的queryDSL,他遵从的是Lucene语法。elasticsearch同样有接口应用于queryString。下面上个例子:

{
  "from": 0,
  "size": 100,
  "query": {
    "query_string": {
      "query": "TITLE:无人机"
    }
  }
}

"TITLE:无人机"就是一个queryString,是一个字符串,表示在TITLE字段中查询匹配“无人机”。
它还支持一些参数:

{
  "from": 0,
  "size": 100,
  "query": {
    "query_string": {
      "query": "TITLE:无人机"
      "fields": [],
      "type": "best_fields",
      "default_operator": "or",
      "max_determinized_states": 10000,
      "enable_position_increments": true,
      "fuzzy_transpositions": true,
      "boost": 1
    }
  }
}

具体的关于queryString本篇不再过多介绍,这里不是重点。

参考官网:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html

从上述例子中我们知道了我们的需求是从TITLE中匹配“无人机”这个词。这个检索式没任何问题,因为一般的分词器都会把无人机这个词分出来,但随着我们检索的深入,我们会遇到一个问题,比如我们要搜索“铝合金轮毂”这个词,一般的分词器都不会分出这个词来,也就是我们所说的专有名词词库。那么会出现什么问题呢?我们试试就知道了

{
  "query": {
    "query_string": {
      "query": "TITLE:铝合金轮毂"
    }
  }
}

我们会发现匹配结果包含很多铝合金和轮毂的文档,可想而知是因为在检索的时候,是先把“铝合金轮毂‘这个词先分词,再去检索的,类似match检索。那么这样的话就会带来两个问题:

  1. 如果我不想用match,我想精准匹配只返回带“铝合金轮毂”而不是“铝合金”+“轮毂”怎么办?
  2. 如果词库里没有“铝合金轮毂”这个词,该怎么做精准匹配?

说到这,可能有些人就会说了,你把“铝合金轮毂”这个词加上引号不就行了。我们来具体看看行还是不行:

{
  "query": {
    "query_string": {
      "query": "TITLE:\"铝合金轮毂\""
    }
  }
}

我们还引号的目的是想做精准匹配,的确,早期的Lucene的确是这么做的。从返回结果上看,也的确达到了我们想要的记过——只返回“铝合金轮毂”。但是我想告诉大家的是,这其实是早期Lucene的一个bug,在Lucene4中已经被修复了——将检索词加引号并不是实现精准匹配。

下面我们开始验证。

{
  "query": {
    "term": {
      "TITLE": "铝合金轮毂"
    }
  }
}

我们使用term检索来验证,前面已经说到词库里是没有“铝合金轮毂”这个词的,也就是说分词器不会将“铝合金轮毂”作为倒排的一个term进行索引。这样来说,这个检索必然是没有数据返回,我们找一句话验证一下。

GET _analyze?pretty
{
  "analyzer": "ik_max_word",
  "text": "某汽车铝合金轮毂在使用一年后发现裂纹"
}

我们使用ik_max分词,避免有些猩猩不服,下面是分词结果:

{
  "tokens": [
    {
      "token": "某",
      "start_offset": 0,
      "end_offset": 1,
      "type": "CN_CHAR",
      "position": 0
    },
    {
      "token": "汽车",
      "start_offset": 1,
      "end_offset": 3,
      "type": "CN_WORD",
      "position": 1
    },
    {
      "token": "铝合金",
      "start_offset": 3,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 2
    },
    {
      "token": "合金",
      "start_offset": 4,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 3
    },
    {
      "token": "轮毂",
      "start_offset": 6,
      "end_offset": 8,
      "type": "CN_WORD",
      "position": 4
    },
    {
      "token": "在",
      "start_offset": 8,
      "end_offset": 9,
      "type": "CN_CHAR",
      "position": 5
    },
    {
      "token": "使用",
      "start_offset": 9,
      "end_offset": 11,
      "type": "CN_WORD",
      "position": 6
    },
    {
      "token": "一年",
      "start_offset": 11,
      "end_offset": 13,
      "type": "CN_WORD",
      "position": 7
    },
    {
      "token": "一",
      "start_offset": 11,
      "end_offset": 12,
      "type": "TYPE_CNUM",
      "position": 8
    },
    {
      "token": "年后",
      "start_offset": 12,
      "end_offset": 14,
      "type": "CN_WORD",
      "position": 9
    },
    {
      "token": "年",
      "start_offset": 12,
      "end_offset": 13,
      "type": "COUNT",
      "position": 10
    },
    {
      "token": "后",
      "start_offset": 13,
      "end_offset": 14,
      "type": "CN_CHAR",
      "position": 11
    },
    {
      "token": "发现",
      "start_offset": 14,
      "end_offset": 16,
      "type": "CN_WORD",
      "position": 12
    },
    {
      "token": "裂纹",
      "start_offset": 16,
      "end_offset": 18,
      "type": "CN_WORD",
      "position": 13
    }
  ]
}

从结果中可以发现,并没有“铝合金轮毂”这个词,所以这个时候使用term检索是不会检索到这条数据的。那为什么用querySring就可以检索的到呢?
因为我们主观带入的认为加上引号就是做了精准匹配,实际上早期的Lucene的确是这样,但这是Lucene的一个bug,早已经被修复了,修复的结果是queryString中带引号的检索词并不是使用精准匹配,而是使用短语匹配。

我们在使用Elasticsearch的过程中可能使用term和match的时候是最多的,用到短语匹配的可能并不是很多。但在特定的场景中,短语匹配的效果要远好于term和match。

{
    "query": {
        "match_phrase" : {
            "TITLE" : "铝合金轮毂",
            "slop": 0
        }
    }
}

match_phrase为短语匹配,短语匹配同样是先将检索词分词,支持自定义分词“analyzer”:“my_analyzer”,一般建议使用和索引分词同一个分词器的检索效果更好。与match不同的是在于参数slop,官方给的解释是

A phrase query matches terms up to a configurable slop (which defaults to 0) in any order. Transposed terms have a slop of 2.

翻译大概意思就是允许分词匹配顺序错误的次数, “slop”: 0表示不允许顺序错误,这样即使将“铝合金”和“轮毂”分开,但是结果中必须是“铝合金”和“轮毂”挨着的才会被检索到,同样可以实现term的检索效果。

以上就是使用queryString检索词加不加引号,以及如何检索精确检索词库中不存在的词的介绍。