1

我目前在 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 的第二次更新)

索引:下载1800+索引

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

奖金问题

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

4

1 回答 1

1

这是我的看法。

基本上,我们需要做的是my_string在索引时使用分edgeNGram器(下面称为)对字段(下面称为)进行切片和切块edge_ngram_tokenizer。这样一个字符串 like123456789将被标记为12, 123, 1234, 12345, 123456, 1234567, 12345678123456789并且所有标记都将被索引和搜索。

因此,让我们创建一个tests索引、一个名为edge_ngram_analyzeranalyzer 的自定义分析器和一个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"
            }
          }
        }
      }
    }
  }
}

然后让我们使用API索引一些test文档:_bulk

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应用在输入字符串上!)将搜索比输入字符串长的“前缀”。包含在 a 中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"
          }
        }
      ]
    }
  }
}'

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

至于你的奖金问题,我只是建议使用_msearchAPI并一次发送所有查询。

更新:最后,确保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
          }
        }
      }
    }
  }
}
于 2015-08-07T05:55:08.150 回答