文章七:ElasticSearch索引字段类型

浮点类型:

再es中如果使用浮点类型存储精确的数值的话,使用scaled_float缩放浮点类型才没有精度问题,这个一定要注意。

复制代码
 "properties": {
      "price":{
        "type": "scaled_float",
        "scaling_factor": 100
      }
    }

日期类型

在实际的业务中,如果使用时间的话,使用标准时间是最好的,这样的话可以很好的解决时区问题

PUT test_index

{

"mappings": {

"_source": {

"enabled": true

},

"dynamic": "true",

"properties": {

"name": {

"type": "keyword"

},

"birthday": {

"type": "date",

"format": "strict_date_optional_time" //使用这种形式是最好的

}

}

},

"settings": {

"number_of_replicas": 1,

"number_of_shards": 4

}

}

data:,data_nano两种类型。

Object对象类型

创建索引

复制代码
PUT hdk_test
{
  "mappings":{
    "properties":{
      "person":{
        "type":"object",
        "properties":{
          "start":{
            "type":"object"
          },
          "end":{
            "type":"object"
          }
        }
      }
    }
  }
}

插入数据:

PUT hdk_test/_doc/1

{

"person":{

"start":{

"start_name":"lihua",

"start_age":11,

"start_class":"class1"

},

"end":{

"end_name":"lihua",

"end_age":11,

"end_class":"class1"

}

}

}

flattend类型

在 Elasticsearch(ES)中,flattened(扁平化)类型是一种特殊的字段类型,专门用于处理结构不固定的 JSON 对象,核心作用是把复杂的嵌套 JSON 对象 "拍平" 成单层的键值对结构,既保留了原始的嵌套语义,又避免了动态映射带来的字段爆炸问题。

一、核心概念(通俗解释)

你可以把 flattened 类型理解为:

  • 原本 ES 对嵌套 JSON(比如 {"user": {"name": "张三", "age": 20}})会自动创建 user.nameuser.age 等多个字段;
  • flattened 类型会把整个 user 对象当作一个字段 处理,内部的所有子字段(nameage)都作为这个字段的 "子属性",不会单独创建新字段。

二、基本用法(代码示例)

1. 创建包含 flattened 类型的索引

json

复制代码
PUT /my_index
{
  "mappings": {
    "properties": {
      "user_info": {  // 整个嵌套对象用 flattened 类型
        "type": "flattened"
      },
      "id": {  // 普通字段
        "type": "integer"
      }
    }
  }
}
2. 写入嵌套数据

json

复制代码
PUT /my_index/_doc/1
{
  "id": 1,
  "user_info": {
    "name": "张三",
    "age": 20,
    "address": {  // 支持多层嵌套
      "city": "北京",
      "district": "朝阳区"
    },
    "hobbies": ["篮球", "游戏"]  // 支持数组
  }
}
3. 查询 flattened 字段的子属性

json

复制代码
GET /my_index/_search
{
  "query": {
    "term": {
      "user_info.name": {  // 用"父字段.子字段"的格式查询
        "value": "张三"
      }
    }
  }
}

三、关键特性(优缺点)

优点:
  1. 避免字段爆炸:适合处理用户自定义的、结构不固定的 JSON(比如用户提交的自定义表单),不会因为子字段过多导致 ES 索引的字段数量超限;
  2. 简化映射管理 :无需提前定义所有嵌套子字段,动态新增的子字段会自动纳入 flattened 字段管理;
  3. 支持基本查询 :可以通过 termmatchrange 等常规查询语法操作子属性。
限制(重点):
  1. 子字段类型统一为字符串 :无论原始值是数字、布尔、数组,都会被转为字符串处理(比如 age: 20 会被当作 "20" 查询);
  2. 不支持聚合 / 排序 :无法对 flattened 字段的子属性做 aggregations(聚合)、sort(排序);
  3. 不支持全文检索 :只能做精确匹配或前缀匹配,无法像 text 类型那样分词检索;
  4. 深度限制 :默认支持最多 20 层嵌套,超过需要调整 depth_limit 参数。

四、适用场景

  • 存储用户自定义的元数据(比如商品的扩展属性:颜色、尺寸、材质,结构不固定);
  • 临时存储嵌套 JSON,只需要简单查询,不需要聚合 / 排序;
  • 避免动态映射导致的字段数量过多(比如 ES 默认的 index.mapping.total_fields.limit 是 1000)。

总结

  1. flattened 类型的核心是将嵌套 JSON 对象拍平为单个字段,子字段通过 "父字段。子字段" 格式访问;
  2. 优点是避免字段爆炸、简化映射,缺点是子字段仅支持字符串类型、无法聚合 / 排序 / 全文检索;
  3. 适合存储结构不固定的嵌套 JSON,且仅需简单查询的场景,不适合需要聚合 / 排序 / 全文检索的场景(这类场景建议用 objectnested 类型)

数组类型

在 Elasticsearch(ES)中,数组类型(Array) 并不是一个「独立的字段类型」,而是 ES 对「同一字段下多个值」的原生支持 ------ 你无需显式定义数组类型,只需在字段中传入多个值(用 [] 包裹),ES 会自动将其识别为数组,且数组内所有元素必须符合该字段的基础类型(比如 keyword 数组、integer 数组)。

可以通俗理解:ES 中没有专门的 array 类型,数组是「基础字段类型的多值形态」 ,比如 keyword 字段可以存单个值 "北京",也可以存数组 ["北京", "上海"]

一、核心特性(必掌握)

1. 数组的「类型约束」(最关键)
  • 数组内的所有元素必须是同一种基础类型 ,ES 不支持混合类型的数组(比如 [123, "abc"] 会报错);
  • 若未显式定义字段类型,ES 会根据数组第一个元素的类型自动推导(动态映射);
  • 支持的基础类型包括:keywordtextintegerfloatdateboolean 等,也支持 object/nested 类型的数组(即嵌套对象数组)。
2. 数组的「底层存储逻辑」
  • ES 会将数组「扁平化」存储,不保留元素的顺序(查询结果中顺序可能和写入不一致);
  • 数组的多值会被当作「多个独立的值」加入倒排索引,比如 keyword 数组 ["北京", "上海"],查询 北京上海 都会匹配到该文档。

二、基础用法(创建 + 写入 + 查询)

1. 创建索引(无需定义数组类型,只需定义基础类型)
复制代码
PUT /user_index
{
  "mappings": {
    "properties": {
      "user_id": { "type": "integer" },
      "name": { "type": "keyword" },
      "hobbies": { "type": "keyword" },  // 计划存数组,仍定义为基础类型
      "ages": { "type": "integer" },     // 整数数组
      "tags": { "type": "text" }         // 文本数组(支持分词)
    }
  }
}
2. 写入数组数据
复制代码
PUT /user_index/_doc/1
{
  "user_id": 1,
  "name": "张三",
  "hobbies": ["篮球", "游戏", "跑步"],  // keyword 数组
  "ages": [20, 21, 22],                // integer 数组
  "tags": ["程序员", "爱运动", "北京"]  // text 数组
}

nested 类型:解决对象数组值混叠的核心方案

在 Elasticsearch 中,nested(嵌套)类型是处理对象数组 的专用类型,可理解为「对象数组的加强版」------ 它通过将数组中每个嵌套对象存储为独立的隐藏子文档,彻底解决了普通 object 类型处理对象数组时的「值混叠」问题,保证查询和聚合的精准性。

一、核心原理:为什么需要 nested 类型?

普通 object 类型存储对象数组时,会将所有对象的字段「拍平混叠」(比如 hobbies.port 会被合并为 ["篮球", "足迹"]),导致无法精准匹配「单个对象的多字段组合」;而 nested 类型会为数组中的每个对象创建独立的子文档,主文档与子文档关联存储,查询时可精准定位单个嵌套对象。

二、实操演示:nested 类型的创建、写入与查询

1. 创建含 nested 类型的索引

首先定义索引映射,将 hobbies 字段显式指定为 nested 类型(其他字段按业务需求定义类型):

json

复制代码
PUT /user_index
{
  "mappings": {
    "properties": {
      "user_id": { "type": "integer" },  // 整型,存储用户ID
      "name": { "type": "keyword" },     // 关键字型,精准匹配用户名
      "hobbies": { "type": "nested" },   // 嵌套类型,处理爱好对象数组
      "ages": { "type": "integer" },     // 整型数组,存储年龄相关数值
      "tags": { "type": "text" }         // 文本型,支持分词检索
    }
  },
  "settings": {
    "number_of_replicas": 0  // 单机测试关闭副本,提升写入效率
  }
}
2. 写入包含嵌套对象数组的文档

向索引中写入一条包含 hobbies 嵌套对象数组的文档:

json

复制代码
PUT user_index/_doc/1
{
  "user_id": 1,
  "name": "lihua",
  "hobbies": [  // 嵌套对象数组,包含2个独立的爱好对象
    { "port": "篮球" },
    { "port": "足迹" }
  ],
  "ages": [12, 11, 24],  // 普通整型数组
  "tags": [
    "是在便宜",
    "分糖果哈帝即位开始发货二发扬好退啊我还发货的接口萨弗哈德森解开了和范德萨就开了回复回复可见度撒了"
  ]
}
3. 用 nested 语法精准查询嵌套对象

查询 hobbies.port 为「篮」的文档(需使用 nested 专属查询语法,指定嵌套路径 path):

json

复制代码
GET user_index/_search
{
  "query": {
    "nested": {   
      "path": "hobbies",  // 指定嵌套字段的路径(必填)
      "query": {  
        "term": {         // term为精准匹配,适合keyword/整型字段
          "hobbies.port": "篮"  
        }
      }
    }
  }
}
4. 验证 nested 类型的存储特性

通过查看索引的文档统计信息,可直观理解 nested 类型的存储逻辑:

json

复制代码
// 查看索引的文档数量统计
GET user_index/_stats/docs

关键结论 :返回的文档总数为 3 ------ 这是因为 nested 类型将「1 个主文档 + 2 个嵌套对象子文档」分别存储,总计 3 个文档。正是这种「独立存储」的特性,让 nested 类型能避免值混叠:每个嵌套对象的字段不会相互干扰,查询时可精准匹配单个对象的属性。

三、核心优势总结

  1. 精准查询 :解决 object 类型的「值混叠」问题,可精准匹配单个嵌套对象的字段组合;
  2. 独立存储:每个嵌套对象作为独立子文档存储,主文档与子文档关联但物理分离;
  3. 灵活聚合 :支持对嵌套对象字段做精准聚合(需配合 nested 聚合语法)。

补充说明

  • hobbies.porttext 类型(自动分词),term 查询「篮」可匹配到「篮球」(分词后包含「篮」);若需精准匹配「篮球」,建议将 port 定义为 keyword 类型,或使用 hobbies.port.keyword 子字段查询;
  • nested 类型虽精准性高,但嵌套对象越多,写入 / 查询性能开销越大,需合理设置 index.mapping.nested_objects.limit 限制单文档嵌套对象数量。

object 类型的值混叠问题(对比 nested 类型的精准性)

为了让你清晰看到「值混叠」的现象,我们通过 object 类型(出现混叠)nested 类型(精准匹配) 的对比案例,还原混淆场景的产生过程和影响。

一、先构建测试场景

假设我们要存储「用户 + 多地址」数据,每个用户有多个地址(城市 + 区域),需求是:查询「城市 = 北京 且 区域 = 浦东新区」的用户(实际无此用户,仅验证是否出现错误匹配)。

步骤 1:创建含 object 类型的索引(会出现混叠)

json

复制代码
PUT /user_object_index
{
  "mappings": {
    "properties": {
      "user_id": { "type": "integer" },
      "name": { "type": "keyword" },
      "addresses": { "type": "object" }  // 普通object类型,存储地址数组
    }
  }
}
步骤 2:写入测试数据

json

复制代码
PUT /user_object_index/_doc/1
{
  "user_id": 1,
  "name": "lihua",
  "addresses": [  // 2个地址对象:北京-朝阳区、上海-浦东新区
    { "city": "北京", "district": "朝阳区" },
    { "city": "上海", "district": "浦东新区" }
  ]
}
二、触发值混叠:错误匹配本不该命中的结果
执行查询(需求:找「北京 + 浦东新区」的用户)

json

复制代码
GET /user_object_index/_search
{
  "query": {
    "bool": {
      "must": [
        { "term": { "addresses.city": "北京" } },
        { "term": { "addresses.district": "浦东新区" } }
      ]
    }
  }
}
结果:错误匹配到了 lihua 的文档!

查询结果会返回 user_id=1 的文档,但实际上 lihua 根本没有「北京 - 浦东新区」这个地址 ------ 这就是 object 类型的「值混叠」问题

为什么会出现混叠?

object 类型会把数组中的所有对象「拍平」成单层键值对,存储逻辑如下:

plaintext

复制代码
// 实际存储的结构(拍平后)
addresses.city: ["北京", "上海"]
addresses.district: ["朝阳区", "浦东新区"]

ES 执行查询时,只会检查「是否存在北京」和「是否存在浦东新区」,但不会关联「北京对应朝阳区、上海对应浦东新区」,因此错误判定为匹配。

Elasticsearch range 字段类型:存储范围值的专用类型

你提到的 range 字段类型 (而非查询时的 range 查询),是 Elasticsearch 专为「存储一个数值 / 日期范围」设计的字段类型(比如价格区间 50-100、时间区间 2024-01-01~2024-01-31),核心作用是将「一个范围」作为整体存储,并支持基于该范围的匹配查询。

一、range 字段类型的核心特点

表格

特性 说明
支持的范围类型 integer_range/long_range(整数范围)、float_range/double_range(浮点范围)、date_range(日期范围)、ip_range(IP 范围)
存储形式 单个字段存储「最小值(gte)+ 最大值(lte)」,无需拆分为两个字段
查询方式 支持用 term/range/match 等查询,匹配「值在范围中」或「范围包含值」
适用场景 存储价格区间、时间窗口、IP 段、年龄区间等「连续范围值」

二、range 字段类型的完整使用示例

场景:存储商品的价格区间 / 活动时间区间

1. 创建含 range 字段类型的索引

json

复制代码
PUT /product_range_index
{
  "mappings": {
    "properties": {
      "product_id": { "type": "integer" },
      "price_range": { "type": "integer_range" },  // 整数范围:存储价格区间
      "activity_time": { "type": "date_range" },   // 日期范围:存储活动时间
      "ip_whitelist": { "type": "ip_range" }       // IP范围:存储白名单IP段
    }
  }
}
2. 写入含 range 类型的文档

range 字段需通过 gte(大于等于)、lte(小于等于)定义范围边界(支持 gt/lt,但 gte/lte 更常用):

json

复制代码
// 文档1:价格区间50-100,活动时间2024-01-01至2024-01-31,IP段192.168.0.0/24
PUT /product_range_index/_doc/1
{
  "product_id": 1,
  "price_range": { "gte": 50, "lte": 100 },
  "activity_time": { 
    "gte": "2024-01-01T00:00:00Z", 
    "lte": "2024-01-31T23:59:59Z",
    "format": "yyyy-MM-dd'T'HH:mm:ssZ"  // 日期范围可选指定格式
  },
  "ip_whitelist": { "gte": "192.168.0.0", "lte": "192.168.0.255" }
}

// 文档2:价格区间100-200,活动时间2024-02-01至2024-02-29,IP段10.0.0.0/8
PUT /product_range_index/_doc/2
{
  "product_id": 2,
  "price_range": { "gte": 100, "lte": 200 },
  "activity_time": { "gte": "2024-02-01", "lte": "2024-02-29" },
  "ip_whitelist": { "gte": "10.0.0.0", "lte": "10.255.255.255" }
}

三、range 字段类型的核心查询方式

range 字段的查询核心是:匹配「某个值是否落在 range 字段的范围内」,或「某个范围是否与 range 字段重叠」。

场景 1:匹配「值在 range 字段范围内」(最常用)

比如查询「价格 80 元落在商品价格区间内」的商品:

json

复制代码
GET /product_range_index/_search
{
  "query": {
    "term": {  // term查询直接匹配值是否在range范围内
      "price_range": 80
    }
  }
}

结果:返回 product_id=1 的文档(80 在 50-100 范围内)。

场景 2:匹配「日期在活动时间范围内」

查询「2024-01-15 是否在活动时间范围内」的商品:

json

复制代码
GET /product_range_index/_search
{
  "query": {
    "term": {
      "activity_time": "2024-01-15"
    }
  }
}

结果:返回 product_id=1 的文档(1 月 15 日在 1 月活动期内)。

场景 3:匹配「IP 在白名单 IP 段内」

查询「IP 192.168.0.10 是否在 IP 白名单范围内」的商品:

json

复制代码
GET /product_range_index/_search
{
  "query": {
    "term": {
      "ip_whitelist": "192.168.0.10"
    }
  }
}

结果:返回 product_id=1 的文档(IP 在 192.168.0.0-192.168.0.255 范围内)。

场景 4:范围重叠查询(用 range 查询匹配 range 字段)

比如查询「商品价格区间与 80-150 有重叠」的商品:

json

复制代码
GET /product_range_index/_search
{
  "query": {
    "range": {
      "price_range": {
        "gte": 80,
        "lte": 150,
        "relation": "intersects"  // 匹配「范围重叠」,默认值
      }
    }
  }
}

结果:返回 product_id=1(50-100 与 80-150 重叠)、product_id=2(100-200 与 80-150 重叠)的文档。

relation 参数支持 3 种匹配规则:

  • intersects:范围重叠(默认);
  • contains:range 字段包含查询的范围;
  • within:range 字段落在查询的范围内。

示例:查询「价格区间完全包含 80-90」的商品:

json

复制代码
GET /product_range_index/_search
{
  "query": {
    "range": {
      "price_range": {
        "gte": 80,
        "lte": 90,
        "relation": "contains"
      }
    }
  }
}

结果:仅返回 product_id=1 的文档(50-100 包含 80-90)。

四、range 字段类型 vs 普通字段拆分

如果把范围拆分为 min_price/max_price 两个普通整型字段,也能实现范围查询,但 range 字段类型有明显优势:

表格

维度 range 字段类型 普通字段拆分(min/max)
存储形式 单个字段存储完整范围,语义清晰 两个字段拆分,语义分散
查询简洁性 直接用 term 匹配值是否在范围中 需用 bool+range 组合查询(gt min && lt max)
范围重叠查询 原生支持(relation 参数) 需复杂 bool 查询,性能差

示例:用普通字段拆分实现「价格 80 在区间内」的查询(对比 range 字段的简洁性):

json

复制代码
// 普通字段拆分的查询(繁琐)
GET /product_common_index/_search
{
  "query": {
    "bool": {
      "must": [
        { "range": { "min_price": { "lte": 80 } } },
        { "range": { "max_price": { "gte": 80 } } }
      ]
    }
  }
}

总结

  1. range 字段类型是 ES 专为「存储数值 / 日期 / IP 范围」设计的类型,单个字段存储 gte(最小值)+ lte(最大值);
  2. 核心查询方式:用 term 匹配「值是否在 range 范围内」,用 range+relation 匹配「范围是否重叠 / 包含 / 被包含」;
  3. 对比普通字段拆分,range 字段类型语义更清晰、查询更简洁,尤其适合需要做「范围重叠匹配」的场景。
相关推荐
渔民小镇2 小时前
告别 Redis/MQ —— ionet 分布式事件总线实战
java·服务器·分布式
2501_918126912 小时前
学习所有6502游戏的系统
java·汇编·嵌入式硬件·学习·游戏
DX_水位流量监测2 小时前
德希科技在线水质浮标站
大数据·水质监测·水质传感器·水质厂家·在线水质浮标站·水质监测系统·水文水利
Mr Aokey2 小时前
快速入门 Spring Boot 拦截器、统一响应格式和全局异常处理
java·开发语言·aop·拦截器
D愿你归来仍是少年2 小时前
Apache Spark 第五章:Spark SQL 与 DataFrame
大数据·spark
鬼蛟2 小时前
Spring_MVC
java·spring·mvc
敲上瘾2 小时前
位图与布隆过滤器:原理、实现与海量数据处理方案
大数据·数据结构·算法·位图·布隆过滤器
珠海西格2 小时前
4 月 1 日起执行分布式光伏监控新规,直接影响从业者与项目收益
大数据·运维·服务器·分布式·能源
怀旧诚子3 小时前
timeshift之Fedora43设置,已在VM虚拟机验证,待真机验证。
java·服务器·数据库