Elasticsearch PHP最长前缀匹配

时间:2015-08-06 23:47:57

标签: symfony elasticsearch elastica foselasticabundle longest-prefix

我目前正在使用Symfony2中的FOSElasticaBundle,我正在努力构建匹配最长前缀的搜索。

我知道互联网上有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 ]
    } ]
  }
}

加分问题

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

1 个答案:

答案 0 :(得分:1)

这是我的看法。

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

因此,让我们创建一个tests索引,一个名为edge_ngram_analyzer分析器的自定义分析器和一个test映射,其中包含一个名为my_string的字符串字段。您会注意到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查询将查找具有较短&#34;前缀的文档&#34;匹配输入和query_string查询(在输入字符串上应用edge_ngram_analyzer!)将搜索&#34;前缀&#34;比输入字符串长。两者都包含在一个bool/should中,并按一个递减的字符串长度(即最长的字符串长度)排序,这样做就可以了。

让我们做一些查询,看看展开了什么:

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

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"
          }
        }
      ]
    }
  }
}'

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

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 我离开上述内容,因为用例可能符合其他人的需求。现在,因为你只需要&#34;更短&#34;前缀(有意义!!),我们需要稍微改变映射和查询。

映射将如下:

{
  "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
          }
        }
      }
    }
  }
}