3

我有一些带有名称字段的文档。我正在使用名称字段的分析版本进行搜索和not_analyzed排序。排序发生在一个级别,即名称首先按字母顺序排序。但是在字母表中,名称是按字典顺序排列的,而不是按字母顺序排列的。这是我使用的映射:

{
  "mappings": {
    "seing": {
      "properties": {
        "name": {
          "type": "string",
          "fields": {
            "raw": {
              "type": "string",
              "index": "not_analyzed"
            }
          }
        }
      }
    }
  }

任何人都可以提供相同的解决方案吗?

4

3 回答 3

18

深入研究 Elasticsearch 文档,我偶然发现了这一点:

不区分大小写的排序

假设我们有三个用户文档,其名称字段分别包含 Boffey、BROWN 和 bailey。首先,我们将应用字符串排序和多字段中描述的使用 not_analyzed 字段进行排序的技术:

PUT /my_index
{
  "mappings": {
    "user": {
      "properties": {
        "name": {                    //1
          "type": "string",
          "fields": {
            "raw": {                 //2
              "type":  "string",
              "index": "not_analyzed"
            }
          }
        }
      }
    }
  }
}
  1. analyzed name字段用于搜索。
  2. not_analyzed name.raw字段用于排序。

前面的搜索请求将按以下顺序返回文档:BROWN、Boffey、bailey。这被称为字典顺序而不是字母顺序。从本质上讲,用于表示大写字母的字节的值低于用于表示小写字母的字节,因此名称首先以最低字节排序。

这对计算机可能有意义,但对于那些合理地期望这些名称按字母顺序排序的人来说没有多大意义,而不管大小写。为了实现这一点,我们需要以字节顺序对应于我们想要的排序顺序的方式来索引每个名称。

换句话说,我们需要一个能够发出单个小写标记的分析器:

按照这个逻辑,您需要使用自定义关键字分析器将其小写,而不是存储原始文档:

PUT /my_index
{
  "settings" : {
    "analysis" : {
      "analyzer" : {
        "case_insensitive_sort" : {
          "tokenizer" : "keyword",
          "filter" : ["lowercase"]
        }
      }
    }
  },
  "mappings" : {
    "seing" : {
      "properties" : {
        "name" : {
          "type" : "string",
          "fields" : {
            "raw" : {
              "type" : "string",
              "analyzer" : "case_insensitive_sort"
            }
          }
        }
      }
    }
  }
}

现在 ordering byname.raw应该按字母顺序排序,而不是按字典顺序排序。

使用 Marvel 在我的本地机器上完成的快速测试:

索引结构:

PUT /my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "case_insensitive_sort": {
          "tokenizer": "keyword",
          "filter": [
            "lowercase"
          ]
        }
      }
    }
  },
  "mappings": {
    "user": {
      "properties": {
        "name": {
          "type": "string",
          "fields": {
            "raw": {
              "type": "string",
              "index": "not_analyzed"
            },
            "keyword": {
              "type": "string",
              "analyzer": "case_insensitive_sort"
            }
          }
        }
      }
    }
  }
}

测试数据:

PUT /my_index/user/1
{
  "name": "Tim"
}

PUT /my_index/user/2
{
  "name": "TOM"
}

使用原始字段查询:

POST /my_index/user/_search
{
  "sort": "name.raw"
}

结果:

{
  "_index" : "my_index",
  "_type" : "user",
  "_id" : "2",
  "_score" : null,
  "_source" : {
    "name" : "TOM"
  },
  "sort" : [
    "TOM"
  ]
},
{
  "_index" : "my_index",
  "_type" : "user",
  "_id" : "1",
  "_score" : null,
  "_source" : {
    "name" : "Tim"
  },
  "sort" : [
    "Tim"
  ]
}

使用小写字符串查询:

POST /my_index/user/_search
{
  "sort": "name.keyword"
}

结果:

{
  "_index" : "my_index",
  "_type" : "user",
  "_id" : "1",
  "_score" : null,
  "_source" : {
    "name" : "Tim"
  },
  "sort" : [
    "tim"
  ]
},
{
  "_index" : "my_index",
  "_type" : "user",
  "_id" : "2",
  "_score" : null,
  "_source" : {
    "name" : "TOM"
  },
  "sort" : [
    "tom"
  ]
}

我怀疑第二个结果在你的情况下是正确的。

于 2015-10-28T09:37:11.903 回答
4

从 Elastic 5.2 开始,您可以使用规范化器来设置不区分大小写的排序。

字段的normalizer属性与keyword类似, analyzer只是它保证分析链产生单个令牌。

normalizer为关键字编制索引之前,以及在keyword通过查询解析器(例如查询)搜索字段时应用match

PUT index
{
  "settings": {
    "analysis": {
      "normalizer": {
        "my_normalizer": {
          "type": "custom",
          "char_filter": [],
          "filter": ["lowercase", "asciifolding"]
        }
      }
    }
  },
  "mappings": {
    "type": {
      "properties": {
        "foo": {
          "type": "keyword",
          "normalizer": "my_normalizer"
        }
      }
    }
  }
}

PUT index/type/1
{
  "foo": "BÀR"
}

PUT index/type/2
{
  "foo": "bar"
}

PUT index/type/3
{
  "foo": "baz"
}

POST index/_refresh

GET index/_search
{
  "query": {
    "match": {
      "foo": "BAR"
    }
  }
}

上面的查询匹配文档 1 和 2,因为在索引和查询时都BÀR转换为。bar

{
  "took": $body.took,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 0.2876821,
    "hits": [
      {
        "_index": "index",
        "_type": "type",
        "_id": "2",
        "_score": 0.2876821,
        "_source": {
          "foo": "bar"
        }
      },
      {
        "_index": "index",
        "_type": "type",
        "_id": "1",
        "_score": 0.2876821,
        "_source": {
          "foo": "BÀR"
        }
      }
    ]
  }
}

此外,关键字在索引之前转换的事实也意味着聚合返回标准化值:

GET index/_search
{
  "size": 0,
  "aggs": {
    "foo_terms": {
      "terms": {
        "field": "foo"
      }
    }
  }
}

返回

{
  "took": 43,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 0.0,
    "hits": []
  },
  "aggregations": {
    "foo_terms": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "bar",
          "doc_count": 2
        },
        {
          "key": "baz",
          "doc_count": 1
        }
      ]
    }
  }
}

来源:规范化器

于 2019-07-24T02:36:25.860 回答
0

只是一些基于@PiwEL 答案的附加组件。

如果您使用的是nodejs 版本的弹性搜索,如果您的索引已经存在,则无法真正更新设置,如下所示

await esClient.indices.putSettings({
      index,
      body: {
        analysis: {
          normalizer: {
            my_normalizer: {
              type: "custom",
              char_filter: [],
              filter: ["lowercase", "asciifolding"],
            },
          },
        },
      },
    });

这个 API 总是抛出异常。所以我最终在创建索引时创建了这个设置,因此必须删除并重新创建索引。更多细节可以在这里追踪

因此,在使用 NodeJs 客户端时,以下内容对我有用。

await esClient.indices.create({
  index,
  body: {
    settings: {
      analysis: {
        normalizer: {
          my_normalizer: {
            type: "custom",
            char_filter: [],
            filter: ["lowercase", "asciifolding"],
          },
        },
      },
    },
    mappings: {
      properties: {
        id: {
          type: "keyword",
        },
        name: {
          type: "text",
          fields: {
            keyword: {
              type: "keyword",
              normalizer: "my_normalizer",
            },
          },
        },
        seedless: {
          type: "boolean",
        },
        origin: { type: "text" },
      },
    },
  },
});
于 2021-07-15T01:40:57.483 回答