Elasticsearch干货(九):queryString中检索词加不加引号?

对于刚接触搜索或者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检索词加不加引号,以及如何检索精确检索词库中不存在的词的介绍。

附1

match检索一直有一个通病,就是不支持多个检索词(例如terms),会报错。网上找了很多资料,国内的各大网上均没有相关介绍,最终在国外的网站找到了相似的:跳转链接 和 github相关iss

{
    "query": {
        "match" : {
            "TITLE" : ["铝合金","轮毂"]
        }
    }
}

这种检索式会报错:

{
  "error": {
    "root_cause": [
      {
        "type": "illegal_state_exception",
        "reason": "Can't get text on a START_ARRAY at 1:28"
      }
    ],
    "type": "illegal_state_exception",
    "reason": "Can't get text on a START_ARRAY at 1:28"
  },
  "status": 500
}

所以我们可以这么做:

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "TITLE": "铝合金"
          }
        },
        {
          "match": {
            "TITLE": "轮毂"
          }
        }
      ]
    }
  }
}

附2

前面提到match_phrase可以通过"slop": 0来实现term的效果,脑洞大开的猩猩们肯定会想到,那我是不是可以通过控制slop参数来实现match检索的功能了呢?答案是不建议。虽然PhraseQuery也会进行打分,但是打分效果远没有BooleanQuery效果好,所以不建议这么使用,无论是terms还是match比这都要好得多。

总结

本片文章介绍了Lucene的queryString检索词加不加引号的区别,以及如何使用term、match来进行检索匹配。如何对词库中没有的词进行精确匹配,以及match不支持数组参数等问题进行探究。

作为整个Elasticsearch干货系列的开篇,内容可能不多,但这的确是困扰了我很久的一个问题,在这里分享给大家,主要还是相关资料太少。我们的老集群已经运行很多年了, 还是0.9版本的集群,那个年代的搜索引擎和现在差别太大了。在这里跟一些初学者说,无论你是学习搜索还是Elasticsearch,一定要学习Lucene,不管Elasticsearch还是以前的solr,他们能够强大,是因为Lucene是全世界最好的搜索引擎,千遍万遍底层技术十年不变。

后续会不定期更新干货系列,非常希望大家能够一起沟通,Elasticsearch越学你会越发现,它非常有趣。

你可能感兴趣的:(Elasticsearch干货(九):queryString中检索词加不加引号?)