ES学习之数据类型和映射分词

本文深入探讨Elasticsearch的核心概念,包括精确值与全文搜索的区别、倒排索引的创建方式、内置分析器及分词测试等内容。同时,介绍了Elasticsearch支持的基本数据类型、自定义映射的方法以及复杂数据类型的处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文主要内容:
1、精确值和全文搜索
2、创建倒排索引
3、内置分析器、分词测试
4、基本数据类型
5、自定义映射
6、更新映射(只能增加)
7、复杂数据类型

精确值和全文

Elasticsearch 中的数据可以概括的分为两类:精确值和全文。

精确值 如它们听起来那样精确。例如日期或者用户 ID,但字符串也可以表示精确值,例如用户名或邮箱地址。对于精确值来讲,Foo 和 foo 是不同的,2014 和 2014-09-15 也是不同的。

另一方面,全文 是指文本数据(通常以人类容易识别的语言书写),例如一个推文的内容或一封邮件的内容。

我们很少对全文类型的域做精确匹配。相反,我们希望在文本类型的域中搜索。不仅如此,我们还希望搜索能够理解我们的意图 ,可能在搜索全文类型时,我们希望可以得到:

  • 搜索 UK ,会返回包含 United Kindom 的文档。
  • 搜索 jump ,会匹配 jumped , jumps , jumping ,甚至是 leap
  • 搜索 johnny walker 会匹配 Johnnie Walkerjohnnie depp 应该匹配 Johnny Depp
  • fox news hunting 应该返回福克斯新闻( Foxs News )中关于狩猎的故事,同时, fox hunting news
    应该返回关于猎狐的故事。

为了促进这类在全文域中的查询,Elasticsearch 首先分析文档,之后根据结果创建倒排索引,然后再对输入进行分析

创建倒排索引

Elasticsearch 使用一种称为 倒排索引 的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。

例如,假设我们有两个文档,每个文档的 content 域包含如下内容:

The quick brown fox jumped over the lazy dog
Quick brown foxes leap over lazy dogs in summer

为了创建倒排索引,我们首先将每个文档的 content 域拆分成单独的 词(我们称它为 词条tokens ),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。结果如下所示:

Term      Doc_1  Doc_2
-------------------------
Quick   |       |  X
The     |   X   |
brown   |   X   |  X
dog     |   X   |
dogs    |       |  X
fox     |   X   |
foxes   |       |  X
in      |       |  X
jumped  |   X   |
lazy    |   X   |  X
leap    |       |  X
over    |   X   |  X
quick   |   X   |
summer  |       |  X
the     |   X   |
------------------------

现在,如果我们想搜索 quick brown ,我们只需要查找包含每个词条的文档:

Term      Doc_1  Doc_2
-------------------------
brown   |   X   |  X
quick   |   X   |
------------------------
Total   |   2   |  1

两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用仅计算匹配词条数量的简单 相似性算法 ,那么,我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。

但是,我们目前的倒排索引有一些问题:

  • Quickquick 以独立的词条出现,然而用户可能认为它们是相同的词。
  • foxfoxes 非常相似, 就像 dogdogs ;他们有相同的词根。
  • jumpedleap, 尽管没有相同的词根,但他们的意思很相近。他们是同义词。

使用前面的索引搜索 +Quick +fox 不会得到任何匹配文档。(记住,+ 前缀表明这个词必须存在。)只有同时出现 Quickfox 的文档才满足这个查询条件,但是第一个文档包含 quick fox ,第二个文档包含 Quick foxes

我们的用户可以合理的期望两个文档与查询匹配。我们可以做的更好。

如果我们将词条规范为标准模式(analyzer:'standard'),那么我们可以找到与用户搜索的词条不完全一致,但具有足够相关性的文档。例如:

  • Quick 可以小写化为 quick
  • foxes 可以 词干提取 –变为词根的格式– 为 fox 。类似的, dogs 可以为提取为 dog
  • jumpedleap 是同义词,可以索引为相同的单词 jump

现在索引看上去像这样:

Term      Doc_1  Doc_2
-------------------------
brown   |   X   |  X
dog     |   X   |  X
fox     |   X   |  X
in      |       |  X
jump    |   X   |  X
lazy    |   X   |  X
over    |   X   |  X
quick   |   X   |  X
summer  |       |  X
the     |   X   |  X
------------------------

这还远远不够。我们搜索 +Quick +fox 仍然 会失败,因为在我们的索引中,已经没有 Quick 了。但是,如果我们对搜索的字符串使用与 content 域相同的标准化规则,会变成查询 +quick +fox ,这样两个文档都会匹配!

分析与分析器

分析 包含下面的过程:

  • 首先,将一块文本分成适合于倒排索引的独立的词条
  • 之后,将这些词条统一化为标准格式以提高它们的“可搜索性”,或者 recall

分析器执行上面的工作。 分析器实际上是将三个功能封装到了一个包里:

  • 字符过滤器
    首先,字符串按顺序通过每个 字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉HTML,或者将 &
    转化成 and
  • 分词器
    其次,字符串被 分词器 分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。
  • Token 过滤器
    最后,词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化 Quick
    ),删除词条(例如, 像 a, and,the 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。

Elasticsearch提供了开箱即用的字符过滤器、分词器和token 过滤器。 这些可以组合起来形成自定义的分析器以用于不同的目的。我们会在 自定义分析器 章节详细讨论。

内置分析器

Elasticsearch还附带了可以直接使用的预包装的分析器。 接下来我们会列出最重要的分析器。为了证明它们的差异,我们看看每个分析器会从下面的字符串得到哪些词条:

"Set the shape to semi-transparent by calling set_trans(5)"

标准分析器
标准分析器是Elasticsearch默认使用的分析器。它是分析各种语言文本最常用的选择。它根据 Unicode 联盟 定义的 单词边界 划分文本。删除绝大部分标点。最后,将词条小写。它会产生

set, the, shape, to, semi, transparent, by, calling, set_trans, 5

简单分析器
简单分析器在任何不是字母的地方分隔文本,将词条小写。它会产生

set, the, shape, to, semi, transparent, by, calling, set, trans

空格分析器
空格分析器在空格的地方划分文本。它会产生

Set, the, shape, to, semi-transparent, by, calling, set_trans(5)

语言分析器
特定语言分析器可用于 很多语言。它们可以考虑指定语言的特点。例如, 英语 分析器附带了一组英语无用词(常用单词,例如 and 或者 the ,它们对相关性没有多少影响),它们会被删除。 由于理解英语语法的规则,这个分词器可以提取英语单词的 词干 。
english分词器会产生下面的词条:

set, shape, semi, transpar, call, set_tran, 5

分词测试:

GET /gb/_analyze
{
  "field": "tweet",
  "text": "Black-cats" 
}
{
  "tokens": [
    {
      "token": "black",
      "start_offset": 0,
      "end_offset": 5,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "cats",
      "start_offset": 6,
      "end_offset": 10,
      "type": "<ALPHANUM>",
      "position": 1
    }
  ]
}
GET /gb/_analyze
{
  "field": "name",
  "text": "Black-cats" 
}
{
  "tokens": [
    {
      "token": "Black-cats",
      "start_offset": 0,
      "end_offset": 10,
      "type": "word",
      "position": 0
    }
  ]
}

基本域数据类型

Elasticsearch 支持如下基本域数据类型:

字符串: string
整数 : byte, short, integer, long
浮点数: float, double
布尔型: boolean
日期: date
当你索引一个包含新域的文档–之前未曾出现– Elasticsearch 会使用 动态映射(即自动推测类型) 。

注意:
ElasticSearch 5.X以上的版本已经不支持string类型,而是改为text和keyword,前者支持分词,后者则是精确匹配不支持分词

自定义域映射

尽管在很多情况下基本域数据类型已经够用,但你经常需要为单独域自定义映射 ,特别是字符串域。自定义映射允许你执行下面的操作:
- 全文字符串域和精确值字符串域的区别
- 使用特定语言分析器
- 优化域以适应部分匹配
- 指定自定义数据格式
- 还有更多
域最重要的属性是 type 。对于不是 string 的域,你一般只需要设置 type

{
    "number_of_clicks": {
        "type": "integer"
    }
}

默认, string 类型域会被认为包含全文。就是说,它们的值在索引前,会通过 一个分析器,针对于这个域的查询在搜索前也会经过一个分析器。

string 域映射的两个最重要 属性是 indexanalyzer

index
index 属性控制怎样索引字符串。它可以是下面三个值:

  • analyzed 首先分析字符串,然后索引它。换句话说,以全文索引这个域。
  • not_analyzed 索引这个域,所以它能够被搜索,但索引的是精确值。不会对它进行分析。
  • no 不索引这个域。这个域不会被搜索到。

stringindex 属性默认是 analyzed 。如果我们想映射这个字段为一个精确值,我们需要设置它为 not_analyzed

{
    "tag": {
        "type":     "string",
        "index":    "not_analyzed"
    }
}

对于 analyzed 字符串域,用 analyzer 属性指定在搜索和索引时使用的分析器。默认, Elasticsearch 使用 standard分析器, 但你可以指定一个内置的分析器替代它:

{
    "tweet": {
        "type":     "string",
        "analyzer": "english"
    }
}

更新映射:

当你首次创建一个索引的时候,可以指定类型的映射。你也可以使用 /_mapping 为新类型(或者为存在的类型更新映射)增加映射

尽管你可以 增加_ 一个存在的映射,你不能 _修改 存在的域映射。如果一个域的映射已经存在,那么该域的数据可能已经被索引。如果你意图修改这个域的映射,索引的数据可能会出错,不能被正常的搜索。

我们可以更新一个映射来添加一个新域,但不能将一个存在的域从 analyzed 改为 not_analyzed

创建一个索引,消息体里指定mapping映射:

PUT /gb 
{
  "mappings": {
    "tweet" : {
      "properties" : {
        "tweet" : {
          "type" :    "string",
          "analyzer": "english"
        },
        "date" : {
          "type" :   "date"
        },
        "name" : {
          "type" :   "string"
        },
        "user_id" : {
          "type" :   "long"
        }
      }
    }
  }
}

稍后,我们决定在 tweet 映射增加一个新的名为 tagnot_analyzed 的文本域,使用 _mapping

PUT /gb/_mapping/tweet
{
  "properties" : {
    "tag" : {
      "type" :    "string",
      "index":    "not_analyzed"
    }
  }
}

注意:
我们不需要再次列出所有已存在的域,因为无论如何我们都无法改变它们。新域已经被合并到存在的映射中。

复杂核心域类型

除了我们提到的简单标量数据类型, JSON 还有 null 值,数组,和对象,这些 Elasticsearch 都是支持的。

1、多值域
很有可能,我们希望 tag 域 包含多个标签。我们可以以数组的形式索引标签:

{ "tag": [ "search", "nosql" ]}

对于数组,没有特殊的映射需求。任何域都可以包含0、1或者多个值,就像全文域分析得到多个词条。

这暗示 数组中所有的值必须是相同数据类型的 。你不能将日期和字符串混在一起。如果你通过索引数组来创建新的域,Elasticsearch 会用数组中第一个值的数据类型作为这个域的 类型 。

2、空域

当然,数组可以为空。 这相当于存在零值。 事实上,在 Lucene 中是不能存储 null 值的,所以我们认为存在 null 值的域为空域。
下面三种域被认为是空的,它们将不会被索引:

"null_value":               null,
"empty_array":              [],
"array_with_null_value":    [ null ]

3、多层次对象

我们讨论的最后一个 JSON 原生数据类是 对象 – 在其他语言中称为哈希,哈希 map,字典或者关联数组。

内部对象 经常用于 嵌入一个实体或对象到其它对象中。 例如,与其在 tweet 文档中包含 user_nameuser_id 域,我们也可以这样写:

{
    "tweet":            "Elasticsearch is very flexible",
    "user": {
        "id":           "@johnsmith",
        "gender":       "male",
        "age":          26,
        "name": {
            "full":     "John Smith",
            "first":    "John",
            "last":     "Smith"
        }
    }
}

4、内部对象的映射

{
  "gb": {
    "tweet": { 
      "properties": {
        "tweet":            { "type": "string" },
        "user": { 
          "type":             "object",
          "properties": {
            "id":           { "type": "string" },
            "gender":       { "type": "string" },
            "age":          { "type": "long"   },
            "name":   { 
              "type":         "object",
              "properties": {
                "full":     { "type": "string" },
                "first":    { "type": "string" },
                "last":     { "type": "string" }
              }
            }
          }
        }
      }
    }
  }
}

username 域的映射结构与 tweet 类型的相同。事实上, type 映射只是一种特殊的 对象 映射,我们称之为 根对象 。除了它有一些文档元数据的特殊顶级域,例如 _source_all 域,它和其他对象一样。

内部对象如果如何索引

Lucene 不理解内部对象。 Lucene 文档是由一组键值对列表组成的。为了能让 Elasticsearch 有效地索引内部类,它把我们的文档转化成这样:

{
    "tweet":            [elasticsearch, flexible, very],
    "user.id":          [@johnsmith],
    "user.gender":      [male],
    "user.age":         [26],
    "user.name.full":   [john, smith],
    "user.name.first":  [john],
    "user.name.last":   [smith]
}

内部对象数组
最后,考虑包含 内部对象的数组是如何被索引的。 假设我们有个 followers 数组:

{
    "followers": [
        { "age": 35, "name": "Mary White"},
        { "age": 26, "name": "Alex Jones"},
        { "age": 19, "name": "Lisa Smith"}
    ]
}

扁平化处理后:

{
    "followers.age":    [19, 26, 35],
    "followers.name":   [alex, jones, lisa, smith, mary, white]
}

{age: 35}{name: Mary White} 之间的相关性已经丢失了,因为每个多值域只是一包无序的值,而不是有序数组。这足以让我们问,“有一个26岁的追随者?”
但是我们不能得到一个准确的答案:“是否有一个26岁 名字叫 Alex Jones 的追随者?”
相关内部对象被称为 nested 对象,可以回答上面的查询。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值