【问题标题】:Elasticsearch PHP longest prefix matchElasticsearch PHP 最长前缀匹配
【发布时间】:2015-08-06 23:47:56
【问题描述】:

我目前在 Symfony2 中使用 FOSElasticaBundle,我很难尝试构建一个匹配最长前缀的搜索。

我知道 Internet 上有 100 个示例使用它来执行类似自动完成的搜索。但是,我的问题有点不同。

在自动完成类型的搜索中,数据库包含最长的字母数字字符串(字符长度),用户只提供最短的部分,假设用户键入“jho”,Elasticsearch 可以轻松提供“Jhon, Jhonny, Jhonas ”。

我的问题是倒退,我想提供最长的字母数字字符串,我希望 Elasticsearch 为我提供数据库中最大的匹配项。

例如:我可以提供“123456789”,我的数据库可以有 [12,123,14,156,16,7,1234,1,67,8,9,123456,0],在这种情况下,最长前缀匹配用户提供的号码的数据库是“123456”。

我刚开始使用 Elasticsearch,所以我真的没有接近工作设置或任何东西。

如果有任何信息不清楚或遗漏,请告诉我,我会提供更多详细信息。

更新 1(使用 Val 的第二次更新)

索引:Download 1800+ indexes

Settings:

curl -XPUT localhost:9200/tests -d '{
  "settings": {
    "analysis": {
      "analyzer": {
        "edge_ngram_analyzer": {
          "tokenizer": "edge_ngram_tokenizer",
          "filter": [ "lowercase" ]
        }
      },
      "tokenizer": {
        "edge_ngram_tokenizer": {
          "type": "edgeNGram",
          "min_gram": "2",
          "max_gram": "25"
        }
      }
    }
  },
  "mappings": {
    "test": {
      "properties": {
        "my_string": {
          "type": "string",
          "fields": {
            "prefix": {
              "type": "string",
              "analyzer": "edge_ngram_analyzer"
            }
          }
        }
      }
    }
  }
}'


Query:

curl -XPOST localhost:9200/tests/test/_search?pretty=true -d '{
  "size": 1,
  "sort": {
    "_script": {
      "script": "doc.my_string.value.length()",
      "type": "number",
      "order": "desc"
    },
    "_score": "desc" 
  },
  "query": {
    "filtered": {
      "query": {
        "match": {
          "my_string.prefix": "8092232423"
        }
      },
      "filter": {
        "script": {
          "script": "doc.my_string.value.length() <= maxlength",
          "params": {
            "maxlength": 10
          }
        }
      }
    }
  }
}'

With this configuration the query returns the following results:

  {
  "took" : 61,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1754,
    "max_score" : null,
    "hits" : [ {
      "_index" : "tests",
      "_type" : "test",
      "_id" : "AU8LqQo4FbTZPxBtq3-Q",
      "_score" : 0.13441172,
      "_source":{"my_string":"80928870"},
      "sort" : [ 8.0, 0.13441172 ]
    } ]
  }
}

额外问题

我想为该搜索提供一个数字数组,并以一种有效的方式为每个数字获取匹配的前缀,而不必每次都执行查询

【问题讨论】:

  • 我们在 MySQL 中进行了类似的搜索,我们使用的技术是查询所有匹配项,然后在 php.ini 中计算最大的匹配项。所以搜索所有匹配[1,12,123,1234,12345,123456,1234567,12345678,123456789]的项目。然后对于每个结果检查它是否是最长的字符串。我也对更好的解决方案感兴趣。您的 elasticsearch 数据是来自 KeyValue 还是 Object DB?
  • 我目前正在这样做,我搜索所有结果并准备一棵二叉树,然后搜索最长的前缀 bht 我担心内存管理和那些东西......目前有该列表中有 300,000 个条目。另外,您如何仅查询匹配结果?至于你的问题,可以是任何一种方式
  • 快速问题:您是否希望响应仅包含比输入字符串 的匹配项或更长的匹配项也可以?即,如果我输入“123456789”并且数据库包含“123456789abcd”,这是否也符合匹配条件?
  • 是的,这符合匹配条件,任何以该前缀(最长前缀)开头的都是有效匹配

标签: symfony elasticsearch elastica foselasticabundle longest-prefix


【解决方案1】:

这是我的看法。

基本上,我们需要做的是在索引时使用edgeNGram tokenizer(下面称为edge_ngram_tokenizer)对字段(下面称为my_string)进行切片和切块。这样,像123456789 这样的字符串将被标记为12123123412345123456123456712345678123456789,并且所有标记都将被索引和可搜索。

所以让我们创建一个tests 索引、一个名为edge_ngram_analyzer 分析器的自定义分析器和一个包含一个名为my_string 的单个字符串字段的test 映射。您会注意到 my_string 字段是一个多字段,它声明了一个 prefixes 子字段,其中将包含所有标记化的前缀。

curl -XPUT localhost:9200/tests -d '{
  "settings": {
    "analysis": {
      "analyzer": {
        "edge_ngram_analyzer": {
          "tokenizer": "edge_ngram_tokenizer",
          "filter": [ "lowercase" ]
        }
      },
      "tokenizer": {
        "edge_ngram_tokenizer": {
          "type": "edgeNGram",
          "min_gram": "2",
          "max_gram": "25"
        }
      }
    }
  },
  "mappings": {
    "test": {
      "properties": {
        "my_string": {
          "type": "string",
          "fields": {
            "prefixes": {
              "type": "string",
              "index_analyzer": "edge_ngram_analyzer"
            }
          }
        }
      }
    }
  }
}

然后让我们使用_bulk API索引几个test文档:

curl -XPOST localhost:9200/tests/test/_bulk -d '
{"index":{}}
{"my_string":"12"}
{"index":{}}
{"my_string":"1234"}
{"index":{}}
{"my_string":"1234567890"}
{"index":{}}
{"my_string":"abcd"}
{"index":{}}
{"my_string":"abcdefgh"}
{"index":{}}
{"my_string":"123456789abcd"}
{"index":{}}
{"my_string":"abcd123456789"}
'

我发现特别棘手的是匹配结果可能比输入字符串长或短。为了实现这一点,我们必须组合两个查询,一个查找较短的匹配项,另一个查找较长的匹配项。因此match 查询将找到与输入匹配的“前缀”较短​​的文档,而query_string 查询(将edge_ngram_analyzer 应用于输入字符串!)将搜索比输入字符串长的“前缀”。两者都包含在 bool/should 中并按字符串长度递减排序(即最长的在前)都可以解决问题。

让我们做一些查询,看看会发生什么:

此查询将返回与“123456789”匹配最长的一个文档,即“123456789abcd”。在这种情况下,结果比输入长。

curl -XPOST localhost:9200/tests/test/_search -d '{
  "size": 1,
  "sort": {
    "_script": {
      "script": "doc.my_string.value.length()",
      "type": "number",
      "order": "desc"
    }
  },
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "my_string.prefixes": "123456789"
          }
        },
        {
          "query_string": {
            "query": "123456789",
            "default_field": "my_string.prefixes",
            "analyzer": "edge_ngram_analyzer"
          }
        }
      ]
    }
  }
}'

第二个查询将返回与“123456789abcdef”匹配最长的一个文档,即“123456789abcd”。在这种情况下,结果比输入短。

curl -XPOST localhost:9200/tests/test/_search -d '{
  "size": 1,
  "sort": {
    "_script": {
      "script": "doc.my_string.value.length()",
      "type": "number",
      "order": "desc"
    }
  },
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "my_string.prefixes": "123456789abcdef"
          }
        },
        {
          "query_string": {
            "query": "123456789abcdef",
            "default_field": "my_string.prefixes",
            "analyzer": "edge_ngram_analyzer"
          }
        }
      ]
    }
  }
}'

我希望涵盖它。如果没有,请告诉我。

至于你的额外问题,我只是建议使用_msearch API 并一次发送所有查询。

更新:最后,使用以下命令确保在您的elasticsearch.yml 文件中启用脚本:

 # if you have ES <1.6
 script.disable_dynamic: false

 # if you have ES >=1.6
 script.inline: on

更新 2 我将离开上述内容,因为该用例可能适合其他人的需求。现在,由于您只需要“更短”的前缀(有意义!!),我们需要稍微更改映射和查询。

映射是这样的:

{
  "settings": {
    "analysis": {
      "analyzer": {
        "edge_ngram_analyzer": {
          "tokenizer": "edge_ngram_tokenizer",
          "filter": [
            "lowercase"
          ]
        }
      },
      "tokenizer": {
        "edge_ngram_tokenizer": {
          "type": "edgeNGram",
          "min_gram": "2",
          "max_gram": "25"
        }
      }
    }
  },
  "mappings": {
    "test": {
      "properties": {
        "my_string": {
          "type": "string",
          "fields": {
            "prefixes": {
              "type": "string",
              "analyzer": "edge_ngram_analyzer"  <--- only change
            }
          }
        }
      }
    }
  }
}

现在查询会有些不同,但总是只返回最长的前缀,但比输入字符串更短或长度相等。请试一试。我建议重新索引您的数据以确保一切设置正确。

{
  "size": 1,
  "sort": {
    "_script": {
      "script": "doc.my_string.value.length()",
      "type": "number",
      "order": "desc"
    },
    "_score": "desc"           <----- also add this line
  },
  "query": {
    "filtered": {
      "query": {
        "match": {
          "my_string.prefixes": "123"  <--- input string
        }
      },
      "filter": {
        "script": {
          "script": "doc.my_string.value.length() <= maxlength",
          "params": {
            "maxlength": 3      <---- this needs to be set to the length of the input string
          }
        }
      }
    }
  }
}

【讨论】:

  • 知道为什么在查询“123456789”时它会返回所有点击数吗?包括 abcd 例如.. 我正在使用您的示例
  • 你的意思是上面的第一个查询?对我来说,如果我删除 size 参数,它只会返回四个点击,即123456789abcd1234567890123412。您确定查询的方式(例如,不在/head/ 插件中使用 GET)吗?
  • 没关系,我在 PHP 中错误地使用了 cURL,我使用普通 curl 只是为了在继续使用 elastica 之前进行测试,我想确认这项工作..这是我的错误得到link 显然这是解析中的错误,但由于我是新手,所以我真的不知道在字符串中的哪里调试
  • 其实我的错,我忘了提到你需要启用 groovy 脚本。打开 elasticsearch.yml 文件,在末尾添加 script.disable_dynamic: false 并重新启动集群。
  • 很棒的东西,我正在终端上对其进行测试,到目前为止,它可以在一件小事上正常工作,我添加了大约 2,000 个新前缀,还有一些前缀,如 242、2423、242357、242359。如果我搜索“242”,只会出现数据库中的 242,会不会太难补充?就像前缀与输入相同,因为它没有显示在结果中
猜你喜欢
  • 2013-06-03
  • 2011-07-23
  • 1970-01-01
  • 2013-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多