【问题标题】:Wilcard search or partial matching in Elastic searchElasticsearch 中的通配符搜索或部分匹配
【发布时间】:2017-04-26 21:28:04
【问题描述】:

我正在尝试为最终用户提供搜索时使用的类型,这更像是 sqlserver。 我能够为给定的 sql 场景实现 ES 查询:

select * from table where name like '%pete%' and type != 'xyz and type!='abc'

但是 ES 查询不适用于这个 sql 查询

select * from table where name like '%peter tom%' and type != 'xyz and type!='abc'

在我的弹性搜索以及通配符查询中,我还需要执行一些布尔过滤查询

{
  "query": {
    "filtered": {
      "filter": {
        "bool": {
          "should": [
            {
              "query": {
                "wildcard": {
                  "name": { "value": "*pete*" }
                }
              }
            }
          ],
          "must_not": [
            {
              "match": { "type": "xyz" }
            },
            {
              "match": { "type": "abc" }
            }
          ]
        }
      }
    }
  }
}

上面带有通配符搜索的弹性查询可以正常工作,并为我获取所有与 pete 匹配且不属于 xyz 和 abc 类型的文档。但是当我尝试使用由空格分隔的 2 个单独的单词执行通配符时,相同的查询返回给我如下图为空。例如

{
  "query": {
    "filtered": {
      "filter": {
        "bool": {
          "should": [
            {
              "query": {
                "wildcard": {
                  "name": { "value": "*peter tom*" }
                }
              }
            }
          ],
          "must_not": [
            {
              "match": { "type": "xyz" }
            },
            {
              "match": { "type": "abc" }
            }
          ]
        }
      }
    }
  }
}

我的映射如下:

{
  "properties": {
    "name": {
      "type": "string"
    },
    "type": {
      "type": "string"
    }
  }
}

我应该使用什么查询来使通配符搜索由空格分隔的单词成为可能

【问题讨论】:

  • 你的问题是你不明白ES如何索引数据。看看这个elastic.co/guide/en/elasticsearch/guide/current/…。也请看 ngGram elastic.co/guide/en/elasticsearch/reference/current/…
  • 所以问题是当你在 ES 中索引文本 "hello world" 时,它会变成 ["hello", "world"]。
  • 我了解文档是如何存储在 ES 中的,但是 ES 提供了什么来允许用户执行 sql,例如搜索由空格分隔的单词是我的问题
  • 你需要喜欢还是需要找到类似的?因此,例如,如果用户搜索“piter tom”,如果您还显示“peter tom”就可以了吗?
  • 我希望双方都进行 LIKE 操作。即像我们在 sql 中所做的 '%peter to%'

标签: elasticsearch wildcard


【解决方案1】:

最有效的解决方案是利用ngram tokenizer 来标记您的name 字段的一部分。例如,如果你有一个像 peter tomson 这样的名字,ngram 分词器会像这样对它进行分词和索引:

  • 宠物
  • 皮特
  • 彼得
  • 彼得 t
  • 彼得到
  • 彼得·汤姆
  • 彼得·汤姆斯
  • 彼得·汤姆索
  • 等汤臣
  • 汤臣
  • 呃汤姆森
  • 汤姆森
  • 汤臣
  • 汤臣
  • 奥姆森
  • mson
  • 儿子
  • 开启

因此,当它被编入索引时,搜索其中任何一个标记都会检索到包含 peter thomson 的文档。

让我们创建索引:

PUT likequery
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_ngram_analyzer": {
          "tokenizer": "my_ngram_tokenizer"
        }
      },
      "tokenizer": {
        "my_ngram_tokenizer": {
          "type": "nGram",
          "min_gram": "2",
          "max_gram": "15"
        }
      }
    }
  },
  "mappings": {
    "typename": {
      "properties": {
        "name": {
          "type": "string",
          "fields": {
            "search": {
              "type": "string",
              "analyzer": "my_ngram_analyzer"
            }
          }
        },
        "type": {
          "type": "string",
          "index": "not_analyzed"
        }
      }
    }
  }
}

然后您就可以使用简单且非常高效的term 查询进行这样的搜索:

POST likequery/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "name.search": "peter tom"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "type": "xyz"
          }
        },
        {
          "match": {
            "type": "abc"
          }
        }
      ]
    }
  }
}

【讨论】:

  • 感谢您的反馈,您能否告知这两个属性的具体情况:“min_gram”:“2”,“max_gram”:“15”。是 2 代表二元组,在这种情况下是什么max_gram 是否表示 . ngram 在 2.1 版本中也可用吗?
  • min_gram 是最小字符数(不是单词),max_gram 是最大字符数,这意味着这个分词器将对所有长度为 2 到 15 的字符串进行分词和索引由您的姓名值组成。
  • @Val 我有疑问,如果你有文件“彼得安德森”,例如。它仍然会匹配它吗?
  • @VolodymyrBilyachat 不,除非我们在混合中加入一些模糊性,否则它不会
  • @Val 完美,因为我认为搜索词将通过相同的标记器进行,但事实并非如此。这就是为什么我在等待你的解决方案:)
【解决方案2】:

好吧,我的解决方案并不完美,我不确定性能。所以你应该自担风险尝试:)

这是 es 5 版本

PUT likequery
{
  "mappings": {
    "typename": {
      "properties": {
        "name": {
          "type": "string",
          "fields": {
            "raw": {
              "type": "keyword"
            }
          }
        },
        "type": {
          "type": "string"
        }
      }
    }
  }
}

在 ES 2.1 中将 "type": "keyword" 更改为 "type": "string", "index": "not_analyzed"

PUT likequery/typename/1
{
  "name": "peter tomson"
}

PUT likequery/typename/2
{
  "name": "igor tkachenko"
}

PUT likequery/typename/3
{
  "name": "taras shevchenko"
}

查询区分大小写

POST likequery/_search
{
  "query": {
    "regexp": {
      "name.raw": ".*taras shev.*"
    }
  }
}

回应

{
  "took": 5,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 1,
    "hits": [
      {
        "_index": "likequery",
        "_type": "typename",
        "_id": "3",
        "_score": 1,
        "fields": {
          "raw": [
            "taras shevchenko"
          ]
        }
      }
    ]
  }
}

PS。我再次不确定此查询的性能,因为它将使用扫描而不是索引。

【讨论】:

    猜你喜欢
    • 2013-09-11
    • 1970-01-01
    • 2015-03-15
    • 2018-12-06
    • 1970-01-01
    • 2017-07-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多