FosElasticaBundle Prefix查询未返回预期结果的技术问询
你的问题核心在于Elasticsearch对text类型字段的分词行为和你预期的Prefix查询逻辑不匹配,导致了不符合预期的结果。
为什么会返回全部3条数据?
你定义的name字段是text类型,Elasticsearch会自动对这个字段的内容进行分词处理。比如示例中的Coctail pomegranate会被拆分成两个独立的term:coctail和pomegranate(默认还会转成小写)。
你当前的Prefix查询name:pom,实际上是在匹配倒排索引中所有以pom开头的term——而pomegranate正好满足这个条件,所以所有包含pomegranate这个term的文档(也就是你所有的3条示例数据)都被匹配到了。但你真正想要的是整个name字段的原始值以pom开头,而不是某个分词后的子term。
解决方法
要实现"匹配name属性以搜索词开头"的需求,你需要针对字段的原始值进行查询,最稳妥的方式是给name字段添加一个keyword类型的子字段(不分词,存储完整原始值):
1. 修改fos_elastica.yml配置
更新name字段的配置,添加keyword子字段:
fos_elastica: clients: default: { host: %elastic_host%, port: %elastic_port% } indexes: app: client: default types: recipe: properties: recipeId : { type : integer } recipeDescription : { type : text } createdAt : { type : date } updatedAt : { type : date } name : type : text fields: keyword: type: keyword ignore_above: 256 # 限制字段长度,按需调整 persistence: identifier: recipeId driver: orm model: AppBundle\Entity\Recipe finder: ~ provider: ~ listener: ~
注意:修改配置后,需要重新生成Elasticsearch索引并同步数据,新的字段才会生效。
2. 调整控制器中的Prefix查询
把Prefix查询的目标字段改成name.keyword,这样就会针对原始字段值进行前缀匹配:
$q = 'pom'; // 搜索字符串 /** @var FinderInterface $finder */ $finder = $this->container->get('fos_elastica.finder.app.recipe'); $match = new Match(); $match->setFieldQuery('name', $q)->setFieldFuzziness('name', 2.5); // 修改这里的字段为name.keyword $prefix = new Query\Prefix(); $prefix->setPrefix('name.keyword', $q); $bool = new Query\BoolQuery(); $bool->addMust($prefix)->addShould($match); $results = $finder->find($bool);
这样修改后,pom作为前缀只会匹配那些原始name字段以pom开头的文档,你的示例数据中没有这样的文档,就会返回0条结果,符合你的预期。
额外提示
如果你同时需要保留基于分词的模糊匹配(也就是你的Match查询),这种多字段的方式是最优解——name字段用于分词后的模糊搜索,name.keyword用于原始值的前缀匹配。
内容的提问来源于stack exchange,提问作者lomboboo

