PHP的ES入门(四)—— 数据查询和分词器问题

486 阅读4分钟

大神请看

本文不一定写的全部都对,如果有哪里写得不好或者不对,可以提但请不要喷,不喜勿看绕道走就是了,虽然有可能在一些小知识点上会误导小白,但是还是可以看看基础的ES操作,对于使用还是一点帮助的

插入数据

先插入一批能适用于我们要讲解的数据

$config = [
    'host' => '127.0.0.1',
    'port' => 9200,
];
$indexName = 'test';
$client = new Client($config);
$index = $client->getIndex($indexName);
// 删除上一节我们创建的index
if ($index->exists()) {
    $index->delete();
}
$data = [
    [
        'name' => '张三',
        'py' => 'zhang san',
        'age' => '19'
    ],
    [
        'name' => '张三三',
        'py' => 'zhang san san',
        'age' => '20'
    ],
    [
        'name' => '詹三',
        'py' => 'zhan san',
        'age' => '20'
    ],
    [
        'name' => '李四',
        'py' => 'li si',
        'age' => '21'
    ],
    [
        'name' => '李四四',
        'py' => 'lisisi',
        'age' => '22'
    ]
];
foreach ($data as $key => $item) {
    $documents[] = new Document($key, $item);
}
$index->addDocuments($documents);

插入数据之后,假如现在我们要查名字中姓张的人,按我们的想法的话,是可以用match去查对吧,我们用api演示一下:

参数
{
	"query": {
		"match": {
			"name": "张"
		}
	}
}

返回结果是能匹配到张三和张三三这两条数据的

假如我们现在要查的是姓氏拼音是zhan或者是zhang的人,按上述例子,我们应该也是用match去匹配出来

{
	"query": {
		"match": {
			"py": "zhan"
		}
	}
}

但是返回结果很遗憾,只有詹三,没有得到我们想要的匹配结果

ES虽然是可以拿来做全文搜索,但是并不是所有的模糊匹配都能匹配出来,就是不是说有一个字符串“abcdefg”,然后随便输入其中一个字符就能把这个字符串匹配出来,这跟分词器是有关系,不同的分词器是有不同的分词规则的,然后搜索的时候如果能触发到分词器分词后的某个词语,就能把数据匹配到。

要明白分词器,可以看这篇文章:learnku.com/articles/35…

这些是我直接复制过来的,ES内置的分词器

Standard Analyzer - 默认分词器,按词切分,小写处理 Simple Analyzer - 按照非字母切分(符号被过滤),小写处理 Stop Analyzer - 小写处理,停用词过滤(the ,a,is) Whitespace Analyzer - 按照空格切分,不转小写 Keyword Analyzer - 不分词,直接将输入当做输出 Patter Analyzer - 正则表达式,默认 \W+ Language - 提供了 30 多种常见语言的分词器 Customer Analyzer 自定义分词器

我们可以调ES的API接口使用默认分词器分别把张三,张三三,zhang san,zhang san san,zhan san分别分词成了什么

GET /_analyze

{
  "analyzer" : "standard",
  "text" : "张三"
}

分词结果:["张", "三"]

{
  "analyzer" : "standard",
  "text" : "张三三"
}

分词结果:["张", "三", "三"]

{
  "analyzer" : "standard",
  "text" : "詹三"
}

分词结果:["詹", "三"]

{
  "analyzer" : "standard",
  "text" : "zhang san"
}

分词结果:["zhang", "san"]

{
  "analyzer" : "standard",
  "text" : "zhang san san"
}

分词结果:["zhang", "san", "san"]

{
  "analyzer" : "standard",
  "text" : "zhan san"
}

分词结果:["zhan", "san"]

根据以上的分词结果,直接搜索zhan是命中不了张三的数据的,但是如果一定要实现这种查询的话,是可以更换要查询的字段的分词器的,然后重新索引数据进行查询即可,这里就不去找哪个分词器能完成这个效果了。除了官方的内置分词器,还有许多第三方的,有兴趣的话是可以去找找看的。

假如现在要做的是一个完全匹配,我只要张三的数据,那按我们上面的查询肯定不行,因为会把张三三也给查出来,所以就需要变通一下,这里有四种方式,第一种比较靠谱,第二种第三种是我从其它文章看的,只局限于一些情况靠谱:

  1. 我们先将name字段设置成keyword类型(这个我们下一节讲),然后重新索引数据再用match进行查询
{
	"query": {
		"match": {
			"name": "张三"
		}
	}
}

没设置之前会查到张三,张三三,詹三(因为分词之后有三这个词,会匹配到詹三的三),设置之后就只会查到张三
  1. 用term查询
{
	"query": {
		"bool": {
			"must": [
				{
					"term": {
						"name": "张"
					}
				},
				{
					"term": {
						"name": "三"
					}
				}
			]
		}
	}
}

在这里会查到张三和张三三
  1. 用match加operator查询
{  
    "query": {
    	"match": {  
	        "name": {  
	            "query":    "张三",  
	            "operator": "and"  
	        }  
	    }
    }
}   

也是会查到张三和张三三,不太靠谱
  1. 用不分词的分词器,简单明了,不分词就不会把张三两个字分开。

第二种和第三种原理上就是查询的数据要全部满足分词后的查询条件,我们查询张三,条件分词后是"张"和"三",所以会匹配出来名字里只要有张和三这两个字的数据,但是当没有这么多花里胡哨的数据的时候,一定条件下还是能使用第二种和第三种的。