【ES三周年】elasticsearch 常用数据类型详解和范例

2023-04-08 13:16:46 浏览数 (1)

主要内容

  • elasticsearch 中的字符串(keyword)类型 的详解和范例
  • elasticsearch 中的字符串/文本(text)类型 的详解和范例
  • elasticsearch 中的数字(数值)类型 的详解和范例
  • elasticsearch 中的布尔(boolean)类型 的详解和范例
  • elasticsearch 中的日期(date)类型 的详解和范例
  • elasticsearch 中的地理(geo_point、geo_shape)类型 的详解和范例
  • elasticsearch 中的对象类型 的详解和范例
  • elasticsearch 中的数组类型 的详解和范例

概要

本篇文章主要讲解elasticsearch在业务中经常用到的字段类型,通过大量的范例来学习和理解不同字段类型的应用场景。范例elasticsearch使用的版本为7.17.5。

简述

在Elasticsearch的映射关系中,每个字段都对应一个数据类型或者字段类型,这些类型规范了字段存储的值和用途。例如,可以将字符串索引到text和keyword字段。text字段的值用于全文搜索;keyword字段的值存储时不会被分词建立索引,主要用于统计计算等操作。

内容

字符串(keyword)类型 详解

  • keyword类型用于存储结构化的内容
  • keyword类型是不进行切分的字符串类型
    • 不进行切分
      • 在索引时,对keyword类型的数据不进行切分,直接构建倒排索引
      • 在搜索时,对该类型的查询字符串不进行切分后的部分匹配
  • keyword类型数据一般用于对文档的过滤、排序和聚合
  • 在现实场景中,keyword经常用于描述ID、电子邮件、主机名、邮政编码、标签、姓名、产品类型、用户ID、URL和状态码等
  • keyword类型数据一般用于比较字符串是否相等,不对数据进行部分匹配,因此一般查询这种类型的数据时使用term查询
  • 范例如下

字符串(keyword)类型 范例

1.创建user索引库并插入一条数据

代码语言:javascript复制
#建立一个人名索引,设定姓名字段为keyword字段
PUT /user
{
  "mappings": {
    "properties": {
      "user_name": {
        "type": "keyword"
      }
    }
  }
}

#插入数据
PUT /user/_doc/001
{
  "user_name": "张三"
}

2.使用term查询刚刚写入的数据

代码语言:javascript复制
#使用term查询刚刚写入的数据
GET /user/_search
{
  "query": {
    "term": {
      "user_name": {
        "value": "张三"
      }
    }
  }
}

#返回的结果如下
{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 0.2876821,
    "hits": [
      {
        "_index": "user",
        "_type": "_doc",
        "_id": "001",
        "_score": 0.2876821,
        "_source": {
          "user_name": "张三"
        }
      }
    ]
  }
}

由搜索结果可以看出,使用term进行全字符串匹配"张三"可以搜索到命中文档。

3.使用match查询刚刚写入的数据中带有"张"的记录

代码语言:javascript复制
#使用match查询刚刚写入的数据中带有"张"的记录
GET /user/_search
{
  "query": {
    "match": {
      "user_name": "张"
    }
  }
}

#返回的结果如下
{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 0,
      "relation": "eq"
    },
    "max_score": null,
    "hits": []
  }
}

由搜索结果可以看出,对keyword类型使用match搜索进行匹配是不会命中文档的。

字符串/文本(text)类型 的详解

  • text类型是可进行切分的字符串类型。
    • 可切分
      • 在索引时,可按照相应的切词算法对文本内容进行切分,然后构建倒排索引
      • 在搜索时,对该类型的查询字符串按照用户的切词算法进行切分,然后对切分后的部分匹配打分
  • text类型用于进行全文搜索(也称为全文检索),text类型允许用户在每个全文字段中搜索单个单词
  • 在现实场景中,text经常用于电子邮箱正文或产品描述的全文等
  • text不适合进行排序,也不适合进行聚合计算。如果字段需要聚合计算或者排序,推荐使用keyword类型

字符串/文本(text)类型 的范例(一)

1.创建一个hotel索引库,并插入一条数据

代码语言:javascript复制
#建立一个hotel索引,可以设定title字段为text字段
PUT /hotel
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text"
      }
    }
  }
}

#插入数据
POST /hotel/_doc/001
{
  "title":"文雅酒店"
}

2.使用term查询刚刚写入的数据

代码语言:javascript复制
#按照普通的term进行搜索
GET /hotel/_search
{
  "query": {
    "term": {
      "title": {
        "value": "文雅酒店"
      }
    }
  }
}

#返回的结果如下
{
  "took" : 570,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  }
}
  • 根据返回的结果可知,上面的请求并没有搜索到文档。
  • term搜索用于搜索值和文档对应的字段是否完全相等,而对于text类型的数据,在建立索引时ES已经进行了切分并建立了倒排索引,因此使用term没有查询到数据。一般情况下,搜索text类型的数据时应使用match搜索。

3.使用match查询刚刚写入的数据中带有"文雅"的记录

代码语言:javascript复制
#按照普通的match进行搜索
GET /hotel/_search
{
  "query": {
    "match": {
      "title": "文雅"
    }
  }
}

#返回结果如下
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "001",
        "_score" : 0.5753642,
        "_source" : {
          "title" : "文雅酒店"
        }
      }
    ]
  }
}

字符串/文本(text)类型 的范例(二)

1.创建myindex-2_13索引库,并插入数据

代码语言:javascript复制
#创建索引映射并指定tagname字段的字段类型为text类型
PUT myindex-2_13
{
  "mappings": {
    "properties": {
      "tagname": {
        "type": "text"
      }
    }
  }
}

#插入文档数据
PUT myindex-2_13/_doc/1
{
  "tagname":"江苏省"
}

#插入文档数据
PUT myindex-2_13/_doc/2
{
  "tagname":"河北省"
}

2.根据tagname字段内容分词,然后对所有分词进行匹配搜索

代码语言:javascript复制
#根据tagname字段内容分词,然后对所有分词进行匹配搜索
GET myindex-2_13/_doc/_search
{
  "query": {
    "match": {
      "tagname": "河南省"
    }
  }
}

#返回的结果如下
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 0.8754687,
    "hits" : [
      {
        "_index" : "myindex-2_13",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.8754687,
        "_source" : {
          "tagname" : "河北省"
        }
      },
      {
        "_index" : "myindex-2_13",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.18232156,
        "_source" : {
          "tagname" : "江苏省"
        }
      }
    ]
  }
}
  • 以上搜索结果中把"江苏省"和"河北省"这两行数据都返回了,这是因为目前默认的分词器把"河南省"分了"河"、"南"、"省"三个词,而"河北省"和"江苏省"分别分成"河"、"北"、"省"和"江"、"苏"、"省",这两个词被分词后都有一个"省"字,所以搜索时被全文匹配到了。在实际业务中,如果我们要对字段的内容进行全文搜索,可以使用text类型;如果要聚合查询或者精准匹配,则尽量使用keyword类型。

字符串/文本(text)类型 的范例(三)

  • 对于大多数想要对文本字段执行更过操作的用户,也可以使用多字段映射,其中既有text类型可以用于全文检索,又有keyword类型可以用于聚合分析,语法如下:
代码语言:javascript复制
PUT 索引库名称
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      }
    }
  }
}
  • 由以上语句可知,my_field字段的映射关系是:父字段类型是text类型,子字段类型是keyword类型。

1.创建myindex-2_14索引库,并插入数据

代码语言:javascript复制
#创建索引映射
PUT myindex-2_14
{
  "mappings": {
    "properties": {
      "tagname": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

#插入文档数据
PUT myindex-2_14/_doc/1
{
  "tagname":"江苏省"
}

#插入文档数据
PUT myindex-2_14/_doc/2
{
  "tagname":"河北省"
}

2.根据父字段(text类型)搜索符合要求的文档数据

代码语言:javascript复制
#根据父字段(text类型)搜索符合要求的文档数据
GET myindex-2_14/_doc/_search
{
  "query": {
    "match": {
      "tagname": "河南省"
    }
  }
}

#返回的结果为
{
  "took" : 1029,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 0.8754687,
    "hits" : [
      {
        "_index" : "myindex-2_14",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.8754687,
        "_source" : {
          "tagname" : "河北省"
        }
      },
      {
        "_index" : "myindex-2_14",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.18232156,
        "_source" : {
          "tagname" : "江苏省"
        }
      }
    ]
  }
}
  • 其中,使用tagname字段的父字段(text类型)进行搜索,因为父类型是text类型。所以搜索时会进行分词。结果返回了包含"河北省"和"江苏省"的文档信息

3.利用tagname字段的子字段(keyword类型)进行匹配查询

代码语言:javascript复制
#利用tagname字段的子字段(keyword类型)进行匹配查询
GET myindex-2_14/_search
{
  "query": {
    "match": {
      "tagname.keyword": "江苏省"
    }
  }
}

#返回结果为
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.6931471,
    "hits" : [
      {
        "_index" : "myindex-2_14",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6931471,
        "_source" : {
          "tagname" : "江苏省"
        }
      }
    ]
  }
}
  • 其中,tagname.keyword代表使用了tagname字段的子字段(keyword类型)进行了不分词搜索,需要保证搜索的内容和字段存储的内容完全匹配,所以从当前索引库中匹配到了数据。

elasticsearch 中的数字(数值)类型 的详解

  • elasticsearch支持的数据类型有long、integer、short、byte、double、float、scaled_float、half_float和unsigned_long等。各类型所表达的数值范围可以参考官方文档,网址为https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html。
  • 为节约存储空间并提升搜索和索引的效率,在实际应用中,在满足需求的情况下应尽可能选择范围小的数据类型。比如,年龄字段的取值最大值不会超过200,因此选择byte类型即可
  • 数值类型的数据也可用于对进行过滤、排序和聚合
  • 对于数值型数据,一般使用term搜索或者范围搜索

elasticsearch 中的数字(数值)类型 的范例(一)

1.更新hotel索引库的mapping,并为hotel索引库定义价格、星级和评论数等字段;更新后再插入数据。

代码语言:javascript复制
#一个酒店搜索项目,酒店的索引除了包含酒店名称和城市之外,还需要定义价格、星级和评论数等。
PUT /hotel/_mapping
{
  "properties": {
    "city": {
      "type": "keyword"
    },
    "price": {
      "type": "double"
    },
    "start": {
      "type": "byte"
    },
    "comment_count": {
      "type": "integer"
    }
  }
}

#插入数据
POST /hotel/_doc/001
{
  "title":"文雅酒店",
  "city":"上海",
  "price":270,
  "start":10,
  "comment_count":2
}

2.搜索价格为200~300(包含200和300)的酒店

代码语言:javascript复制
#搜索价格为200~300(包含200和300)的酒店
GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 200,
        "lte": 300
      }
    }
  }
}

#返回结果如下
{
  "took" : 596,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "001",
        "_score" : 1.0,
        "_source" : {
          "title" : "文雅酒店",
          "city" : "上海",
          "price" : 270,
          "start" : 10,
          "comment_count" : 2
        }
      }
    ]
  }
}

elasticsearch 中的数字(数值)类型 的范例(二)

1.创建索引并创建字段映射关系

代码语言:javascript复制
#创建索引并创建字段映射关系
PUT myindex-2_09
{
  "mappings": {
    "properties": {
      "number": {
        "type": "integer"
      },
      "time_in_secondes": {
        "type": "float"
      },
      "price": {
        "type": "scaled_float",
        "scaling_factor": 100
      }
    }
  }
}

#插入文档数据
PUT myindex-2_09/_doc/1?refresh
{
  "number":1,
  "time_in_secondes":1.001,
  "price":1.11
}
  • 在以上语句创建的索引映射中,"scaling_factor"参数表示数值在存储时使用了缩放因子,该值在存储时乘以缩放因子并四舍五入到最接近long类型的值(比如1.11实际存储的数据是111).注意,这个参数是必不可少的。
  • 就上面范例中的数字类型而言,他们可以存储任何数字,但是我们在使用时尽量选择可以满足需求的最小数值类型,这样可以更有效地编制索引和进行搜索,同时也可以节省一部分的存储空间。

2.精确查询price为1.11的数据

代码语言:javascript复制
#精确查询price为1.11的数据
GET myindex-2_09/_search
{
  "query": {
    "term": {
      "price": {
        "value": "1.11"
      }
    }
  }
}

elasticsearch 中的布尔(boolean)类型 的详解

  • 布尔字段接受JSON格式的true和false,但也可以接受解释为真或假的字符串,false,"false",true,"true"
  • 布尔类型使用boolean定义,用于业务中的二值表示,如商品是否售罄,房屋是否已租,酒店房间是否满房等。

elasticsearch 中的布尔(boolean)类型 的范例(一)

1.一个酒店搜索项目,酒店的索引除了包含酒店名称、城市、价格、星级、评论数之外,还需要定义是否 满房等。其次插入数据

代码语言:javascript复制
#一个酒店搜索项目,酒店的索引除了包含酒店名称、城市、价格、星级、评论数之外,还需要定义是否
满房等。
PUT /hotel/_mapping
{
  "properties": {
    "full_room": {
      "type": "boolean"
    }
  }
}

#插入数据
POST /hotel/_doc/001
{
  "title":"文雅酒店",
  "city":"上海",
  "price":270,
  "start":10,
  "comment_count":2,
  "full_room":true
}

2.查询满房的酒店

代码语言:javascript复制
#查询满房的酒店
GET hotel/_search
{
  "query": {
    "term": {
      "full_room": {
        "value": "true"
      }
    }
  }
}

#返回结果如下
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.2876821,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "001",
        "_score" : 0.2876821,
        "_source" : {
          "title" : "文雅酒店",
          "city" : "上海",
          "price" : 270,
          "start" : 10,
          "comment_count" : 2,
          "full_room" : true
        }
      }
    ]
  }
}

elasticsearch 中的布尔(boolean)类型 的范例(二)

1.创建索引myindex-2_03映射并指定is_published字段类型为布尔类型

代码语言:javascript复制
#创建索引映射并指定is_published字段类型为布尔类型
PUT myindex-2_03
{
  "mappings": {
    "properties": {
      "is_published": {
        "type": "boolean"
      }
    }
  }
}

#新增数据,字段值必须和映射类型匹配
POST myindex-2_03/_doc/1?pretty
{
  "is_published": "true"
}

POST myindex-2_03/_doc/2?pretty
{
  "is_published": true
}

2.查询索引库中is_publish字段的值是true的数据

代码语言:javascript复制
#查询索引库中is_publish字段的值是true的数据
GET myindex-2_03/_search
{
  "query": {
    "term": {
      "is_published": {
        "value": "true"
      }
    }
  }
}
#返回结果如下
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.6931471,
    "hits" : [
      {
        "_index" : "myindex-2_03",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.6931471,
        "_source" : {
          "is_published" : "true"
        }
      }
    ]
  }
}

3.在使用布尔类型字段时需要注意的是,布尔类型的查询不能使用0或者1代替,否则会抛出异常

代码语言:javascript复制
#在使用布尔类型字段时需要注意的是,布尔类型的查询不能使用0或者1代替,否则会抛出异常
POST myindex-2_03/_doc/3?pretty
{
  "is_published": 1
}

#返回结果如下
{
  "error" : {
    "root_cause" : [
      {
        "type" : "mapper_parsing_exception",
        "reason" : "failed to parse field [is_published] of type [boolean] in 
document with id '3'. Preview of field's value: '1'"
      }
    ],
    "type" : "mapper_parsing_exception",
    "reason" : "failed to parse field [is_published] of type [boolean] in document 
with id '3'. Preview of field's value: '1'",
    "caused_by" : {
      "type" : "json_parse_exception",
      "reason" : "Current token (VALUE_NUMBER_INT) not of boolean typen at [Source: 
(ByteArrayInputStream); line: 2, column: 20]"
    }
  },
  "status" : 400
}

#可以看到,使用1作为筛选值进行查询时不能正确地转换为布尔类型的值。如果需要对布尔值进行转换,可以使用
"运行时"脚本来处理
GET myindex-2_03/_search
{
  "fields": [
    {
      "field": "weight"
    }
  ],
  "runtime_mappings": {
    "weight": {
      "type": "long",
      "script": "emit(doc['is_published'].value?1:0)"
    }
  }
}
#返回结果如下
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "myindex-2_03",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "is_published" : false
        },
        "fields" : {
          "weight" : [
            0
          ]
        }
      },
      {
        "_index" : "myindex-2_03",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "is_published" : "true"
        },
        "fields" : {
          "weight" : [
            1
          ]
        }
      }
    ]
  }
}

elasticsearch 中的日期(date)类型 的详解

  • JSON格式规范中没有对日期数据类型进行定义。
  • elasticsearch一般使用如下形式表示日期类型数据
    • 格式化的日期字符串,例如 2015-01-01 或 2015/01/01 12:10:30
    • 毫秒级的长整型(一个表示自纪元以来毫秒数的长整形数字),表示从1970年1月1日0点到现在的毫秒数
    • 秒级别的整形(表示从纪元开始的秒数的整数),表示从1970年1月1日0点到现在的秒数
  • 在Elasticsearch内部,日期转换为UTC(如果指定了时区),并存储为毫秒数时间戳。
  • 日期类型的默认格式为strict_date_optional_time||epoch_millis。其中,strict_date_optional_time的含义是严格的时间类型,支持yyyy-MM-dd、yyyyMMdd、yyyyMMddHHmmss、yyyy-MM-ddTHH:mm:ss、yyyy-MM-ddTHH:mm:ss.SSS和yyyy-MM-ddTHH:mm:ss:SSSZ等格式;epoch_millis的含义是从1970年1月1日0点到现在的毫秒数。
  • Elasticsearch中的日期类型可以时包含日期格式的字符串,例如"2021-01-01"或"2021/01/01 12:10:30"等格式,也可以使用自纪元以来的毫秒数来表示(注:在Unix中,纪元是指UTC时间1970年1月1日00:00:00)。
  • 对日期的查询在内部转换为范围查询,聚合和存储字段的结果将根据与字段关联的日期格式转换回字符串。
  • 日期类型默认不支持yyyy-MM-dd HH:mm:ss格式,如果经常使用这种格式,可以在索引的mapping中设置日期字段的 format属性为自定义格式。
  • 搜索日期数据时,一般使用范围查询。

elasticsearch 中的日期(date)类型 的范例(一)

1.一个酒店搜索项目,酒店的索引除了包含酒店名称、城市、价格、星级、评论数、是否满房之外,还需要定义日期等。

代码语言:javascript复制
#一个酒店搜索项目,酒店的索引除了包含酒店名称、城市、价格、星级、评论数、是否满房之外,还需要定义日期等。
PUT /hotel/_mapping
{
  "properties": {
    "create_time": {
      "type": "date"
    }
  }
}

#插入数据
POST /hotel/_doc/001
{
  "title":"文雅酒店",
  "city":"上海",
  "price":270,
  "start":10,
  "comment_count":2,
  "full_room":true,
  "create_time":"20210115"
}

2.搜索创建日期为2015年的酒店

代码语言:javascript复制
#搜索创建日期为2015年的酒店
GET hotel/_search
{
  "query": {
    "range": {
      "create_time": {
        "gte": "20150101",
        "lt": "20220112"
      }
    }
  }
}

#返回结果如下
{
  "took" : 14,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "001",
        "_score" : 1.0,
        "_source" : {
          "title" : "文雅酒店",
          "city" : "上海",
          "price" : 270,
          "start" : 10,
          "comment_count" : 2,
          "full_room" : true,
          "create_time" : "20210115"
        }
      }
    ]
  }
}

3.删除原来的hotel索引库,构建新的hotel索引库,并设置create_time字段的格式为yyyy-MM-dd HH:mm:ss

代码语言:javascript复制
#设置create_time字段的格式为yyyy-MM-dd HH:mm:ss
PUT /hotel
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text"
      },
      "city": {
        "type": "keyword"
      },
      "price": {
        "type": "double"
      },
      "start": {
        "type": "byte"
      },
      "comment_count": {
        "type": "integer"
      },
      "full_room": {
        "type": "boolean"
      },
      "create_time": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      }
    }
  }
}

#插入数据
POST /hotel/_doc/001
{
  "title":"文雅酒店",
  "city":"上海",
  "price":270,
  "start":10,
  "comment_count":2,
  "full_room":true,
  "create_time":"20210115"
}

#返回结果如下
{
  "error" : {
    "root_cause" : [
      {
        "type" : "mapper_parsing_exception",
        "reason" : "failed to parse field [create_time] of type [date] in document
with id '001'. Preview of field's value: '20210115'"
      }
    ],
    "type" : "mapper_parsing_exception",
    "reason" : "failed to parse field [create_time] of type [date] in document 
with id '001'. Preview of field's value: '20210115'",
    "caused_by" : {
      "type" : "illegal_argument_exception",
      "reason" : "failed to parse date field [20210115] with format [yyyy-MM-dd 
HH:mm:ss]",
      "caused_by" : {
        "type" : "date_time_parse_exception",
        "reason" : "Text '20210115' could not be parsed at index 0"
      }
    }
  },
  "status" : 400
}
  • 根据错误信息可知,错误的原因是写入的数据格式和定义的数据格式不同

3.插入create_time的格式为yyyy-MM-dd HH:mm:ss数据

代码语言:javascript复制
#插入create_time的格式为yyyy-MM-dd HH:mm:ss数据
POST /hotel/_doc/001
{
  "title":"文雅酒店",
  "city":"上海",
  "price":270,
  "start":10,
  "comment_count":2,
  "full_room":true,
  "create_time":"2021-01-15 01:23:30"
}

elasticsearch 中的日期(date)类型 的范例(二)

代码语言:javascript复制
#创建索引映射并指定date字段的字段类型为日期类型
PUT myindex-2_04
{
  "mappings": {
    "properties": {
      "date":{
        "type": "date"
      }
    }
  }
}

#插入文档数据
PUT myindex-2_04/_doc/1
{
  "date":"2015-01-01"
}

#插入文档数据
PUT myindex-2_04/_doc/2
{
  "date": "2015-01-01T12:10:30Z"
}

#插入文档数据
PUT myindex-2_04/_doc/3
{
  "date":1420070400001
}
#以上3种不同格式的日期数据都被插入到索引库中。

#创建索引并为日期类型的字段指定具体的日期格式
PUT myindex-2_05
{
  "mappings": {
    "properties": {
      "date": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      }
    }
  }
}
#以下语句插入文档数据时将会抛出异常,因为日期内容不符合映射格式
PUT myindex-2_05/_doc/1
{
  "date":"2015-01-01"
}

#以下语句插入文档数据时将会抛出异常,因为日期内容不符合映射格式
PUT myindex-2_05/_doc/2
{
  "date":"2015-01-01T12:10:30Z"
}

#以下语句插入文档数据时将会抛出异常,因为日期内容不符合映射格式
PUT myindex-2_05/_doc/3
{
  "date":1420070400001
}
#以下语句文档数据正常插入,因为日期内容符合字段指定的日期格式
PUT myindex-2_05/_doc/4
{
  "date":"2015-01-01 12:02:56"
}

elasticsearch 中的地理(geo_point、geo_shape)类型 的详解

  • 地理位置(geo)是用于存储经纬度的字段类型。
  • 用例场景如下
    • 在边界框内、中心点的特定距离内或多边形内查找地理点
    • 按地理位置或距中心点的距离聚合文档
    • 将距离整合到文档的相关性得分中
    • 按距离对文档排序
  • 在生活中,我们可能会遇到根据当前所在的位置找到离自己最近的符合条件的一些商店、酒店之类的情况。在elasticsearch中也支持这种业务的查询,它主要支持两种类型的地理查询:一种是地理点(geo_point)查询,即经纬度查询;另一种是地理形状(geo_shape)查询,支持点、线、圈、多边形查询等。

geo_shape

  • geo_shape(空间位置)类型支持地理形状的搜索,即点、线、圈、多边形搜索等。比如我们想要找到最接近给定位置的路线,就可以使用此类型。语法如下
代码语言:javascript复制
PUT /索引库名称
{
  "mappings": {
    "properties": {
      "location": {
        "type": "geo_shape"
      }
    }
  }
}

geo_point

  • 在移动互联网时代,用户借助移动设备产生的消费越来越多。例如:用户要根据某个地理位置来搜索酒店,此时可以把酒店的经纬度数据设置为地理数据类型。该类型的定义需要在mapping中指定目标字段的数据类型为geo_point类型
  • elasticseach也提供了地理点查询的类型,即geo_point类型。使用语法如下
代码语言:javascript复制
PUT 索引库名称
{
  "mappings": {
    "properties": {
      "location": {
        "type": "geo_point"
      }
    }
  }
}

elasticsearch 中的地理(geo_point)类型 范例

代码语言:javascript复制
#一个酒店搜索项目,酒店的索引除了包含酒店名称、城市、价格、星级、评论数、是否满房、日期之外,还需要定义位
置等。
PUT /hotel/_mapping
{
  "properties": {
    "location": {
      "type": "geo_point"
    }
  }
}

#插入数据
POST /hotel/_doc/001
{
  "title":"文雅酒店",
  "city":"上海",
  "price":270,
  "start":10,
  "comment_count":2,
  "full_room":true,
  "create_time":"2021-01-15 01:23:30",
  "location":{
    "lat":40.012134,
    "lon":116.497553
  }
}

#搜索指定的两个地理位置形成的矩形范围中包含的酒店信息
GET hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match_all": {}
        }
      ],
      "filter": {
        "geo_bounding_box": {
          "location": {
            "top_left": {
              "lat": 40.73,
              "lon": -74.1
            },
            "bottom_right": {
              "lat": 40.01,
              "lon": -71.12
            }
          }
        }
      }
    }
  }
}

#搜索指定的多个地理位置形成的多边形范围中包含酒店信息
GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match_all": {}
        }
      ],
      "filter": {
        "geo_polygon": {
          "location": {
            "points": [
              {
                "lat": 40.73,
                "lon": -74.1
              },
              {
                "lat": 40.83,
                "lon": -75.1
              },
              {
                "lat": 40.93,
                "lon": -76.1
              }
            ]
          }
        }
      }
    }
  }
}

#搜索指定位置1000km范围内的酒店数据
GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match_all": {}
        }
      ],
      "filter":{
        "geo_distance": {
          "distance": "1000km",
          "location": {
            "lat": 40.73,
            "lon": -74.1
          }
        }
      }
    }
  }
}

#搜索距离指定位置一定范围内有多少个酒店
GET /hotel/_search
{
  "size": 0,
  "aggs": {
    "count_by_distinct": {
      "geo_distance": {
        "field": "location",
        "origin": {
          "lat": 52.376,
          "lon": 4.894
        },
        "ranges": [
          {
            "from": 100,
            "to": 300
          }
        ],
        "unit": "mi",
        "distance_type": "arc"
      }
    }
  }
}

elasticsearch 中的地理(geo_shape)类型 范例

1.创建索引映射并指定location字段的字段类型为geo_shape类型

代码语言:javascript复制
#创建索引映射并指定location字段的字段类型为geo_shape类型
PUT myindex-geo_shape
{
  "mappings": {
    "properties": {
      "location": {
        "type": "geo_shape"
      }
    }
  }
}

#插入地点相关信息
POST /myindex-geo_shape/_doc?refresh
{
  "name": "Wind & Wetter,Berlin,Germany",
  "location": {
    "type": "point",
    "coordinates": [
      13.400544,
      52.530286
    ]
  }
}

2.搜索指定的两个位置范围内的地点

代码语言:javascript复制
#搜索指定的两个位置范围内的地点
GET /myindex-geo_shape/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match_all": {}
        }
      ],
      "filter": {
        "geo_shape": {
          "location": {
            "shape": {
              "type": "envelope",
              "coordinates": [
                [
                  13,
                  53
                ],
                [
                  14,
                  52
                ]
              ]
            },
            "relation": "within"
          }
        }
      }
    }
  }
}

#返回的结果
{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "myindex-geo_shape",
        "_type" : "_doc",
        "_id" : "oKSWWYcB7w4YX5_iGU4l",
        "_score" : 1.0,
        "_source" : {
          "name" : "Wind & Wetter,Berlin,Germany",
          "location" : {
            "type" : "point",
            "coordinates" : [
              13.400544,
              52.530286
            ]
          }
        }
      }
    ]
  }
}
  • 由以上语句可知,根据需求返回了两个地理位置范围内符合条件的地点。

elasticsearch 中的对象类型 的详解

  • elasticsearch中的object类型实际就是JSON数据格式
  • 在实际业务中,一个文档需要包含其他内部对象。例如,在酒店搜索需求中,用户希望酒店信息中包含评论数据。评论数据分为好评数量和差评数量。为了支持这种业务,在ES中可以使用对象类型。
  • 对象类型和数组类型一样,对象类型也不用事先定义,在写入文档的时候ES会自动识别并转换为对象类型。

elasticsearch 中的对象类型 的范例(一)

代码语言:javascript复制
#向hotel中添加一条数据
PUT /hotel/_doc/002
{
  "title": "好再来酒店",
  "city": "青岛",
  "price": 578.23,
  "comment_info": {
    "properties": {
      "favourable_comment": 199,
      "negative_comment": 68
    }
  }
}
#执行以上DSL后,索引hotel增加了一个字段comment_info,它有两个属性,分别是favourable_comment
和negative_comment,二者的类型都是long。

#查看hotel的mapping
GET /hotel/_mapping
#hotel 的mapping 响应结果为:
{
  "hotel" : {
    "mappings" : {
      "properties" : {
        "city" : {
          "type" : "keyword"
        },
        "comment_count" : {
          "type" : "integer"
        },
        "comment_info" : {
          "properties" : {
            "properties" : {
              "properties" : {
                "favourable_comment" : {
                  "type" : "long"
                },
                "negative_comment" : {
                  "type" : "long"
                }
              }
            }
          }
        },
        "create_time" : {
          "type" : "date",
          "format" : "yyyy-MM-dd HH:mm:ss"
        },
        "full_room" : {
          "type" : "boolean"
        },
        "location" : {
          "type" : "geo_point"
        },
        "price" : {
          "type" : "double"
        },
        "start" : {
          "type" : "byte"
        },
        "title" : {
          "type" : "text"
        }
      }
    }
  }
}
#根据对象类型的属性进行搜索,可以直接用"."操作符进行指向。
#例如搜索hotel索引中好评数大于200的文档
GET /hotel/_search
{
  "query": {
    "range": {
      "comment_info.properties.favourable_comment": {
        "gte": 36
      }
    }
  }
}
#当然,对象内部还可以包含对象
#评论信息字段comment_info可以增加前3条好评数据
POST /hotel/_doc/002
{
  "title": "好再来酒店",
  "city": "青岛",
  "price": 578.23,
  "comment_info": {
    "properties": {
      "favourable_comment": 199,
      "negative_comment": 68,
      "top3_favourable_comment": {
        "top1": {
          "content": "干净整洁的一家酒店",
          "score":87
        },
        "top2": {
          "content": "服务周到,停车方便",
          "score":89
        },
        "top3": {
          "content": "闹中取静,环境优美",
          "score":90
        }
      }
    }
  }
}

elasticsearch 中的对象类型 的范例(二)

代码语言:javascript复制
#创建索引映射
PUT myindex-object
{
  "mappings": {
    "properties": {
      "region": {
        "type": "keyword"
      },
      "manager": {
        "properties": {
          "age": {
            "type": "integer"
          },
          "name": {
            "properties": {
              "first": {
                "type": "text"
              },
              "last": {
                "type": "text"
              }
            }
          }
        }
      }
    }
  }
}

#对于_id等于1的文档而言,manager是一个对象,该对象中又包含一个name对象,而name对象中有两个键值对。如果需
要向这个索引映射中插入文档,可以使用下面任何一种方式写入

#范例一:使用嵌套的JSON数据格式进行写入
#使用嵌套的JSON数据格式进行写入
PUT myindex-object/_doc/1
{
  "region":"China",
  "manager":{
    "age":30,
    "name":{
      "first":"clay",
      "last":"zhagng"
    }
  }
}

#范例二:使用简单的JSON数据格式进行写入
#使用简单的JSON数据格式进行写入
PUT myindex-object/_doc/2
{
  "region": "US",
  "manager.age": 30,
  "manager.name.first": "John",
  "manager.name.last": "Smith"
}

#使用以上两种方式写入数据不会影响数据的存储,但是会影响查询返回的结果。
#查询数据
GET myindex-object/_doc/_search

#返回的结果为
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "myindex-object",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "region" : "China",
          "manager" : {
            "age" : 30,
            "name" : {
              "first" : "clay",
              "last" : "zhagng"
            }
          }
        }
      },
      {
        "_index" : "myindex-object",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "region" : "US",
          "manager.age" : 30,
          "manager.name.first" : "John",
          "manager.name.last" : "Smith"
        }
      }
    ]
  }
}
#以上返回信息的写入格式与查询返回的格式一致。

elasticsearch 中的数组类型 的详解

  • ES数组没有定义方式,其使用方式是开箱即用的,即无须事先声明,在写入时把数据用中括号[]括起来,由ES对该字段完成定义。当然,如果事先已经定义了字段类型,在写数据时以数组形式写入,ES也会将该类型转为数组。

elasticsearch 中的数组类型 的范例

代码语言:javascript复制
#为hotel索引增加一个标签字段,名称为tag
PUT /hotel/_mapping
{
  "properties": {
    "tag": {
      "type": "keyword"
    }
  }
}
#查看一下索引hotel的mapping
GET /hotel/_mapping
#返回的结果如下
{
  "hotel" : {
    "mappings" : {
      "properties" : {
        "city" : {
          "type" : "keyword"
        },
        "comment_count" : {
          "type" : "integer"
        },
        "comment_info" : {
          "properties" : {
            "properties" : {
              "properties" : {
                "favourable_comment" : {
                  "type" : "long"
                },
                "negative_comment" : {
                  "type" : "long"
                },
                "top3_favourable_comment" : {
                  "properties" : {
                    "top1" : {
                      "properties" : {
                        "content" : {
                          "type" : "text",
                          "fields" : {
                            "keyword" : {
                              "type" : "keyword",
                              "ignore_above" : 256
                            }
                          }
                        },
                        "score" : {
                          "type" : "long"
                        }
                      }
                    },
                    "top2" : {
                      "properties" : {
                        "content" : {
                          "type" : "text",
                          "fields" : {
                            "keyword" : {
                              "type" : "keyword",
                              "ignore_above" : 256
                            }
                          }
                        },
                        "score" : {
                          "type" : "long"
                        }
                      }
                    },
                    "top3" : {
                      "properties" : {
                        "content" : {
                          "type" : "text",
                          "fields" : {
                            "keyword" : {
                              "type" : "keyword",
                              "ignore_above" : 256
                            }
                          }
                        },
                        "score" : {
                          "type" : "long"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "create_time" : {
          "type" : "date",
          "format" : "yyyy-MM-dd HH:mm:ss"
        },
        "full_room" : {
          "type" : "boolean"
        },
        "location" : {
          "type" : "geo_point"
        },
        "price" : {
          "type" : "double"
        },
        "start" : {
          "type" : "byte"
        },
        "tag" : {
          "type" : "keyword"
        },
        "title" : {
          "type" : "text"
        }
      }
    }
  }
}
#通过返回的mapping信息来看,新增的tag字段与普通的keyword类型字段没什么区别,现在写入一条数据
PUT /hotel/_doc/004
{
  "title":"好再来酒店",
  "city":"青岛",
  "price":"578.23",
  "tags":["有车位","免费WIFI"]
}

#查询一下写入的数据
GET /hotel/_search

#返回的数据为
{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "001",
        "_score" : 1.0,
        "_source" : {
          "title" : "文雅酒店",
          "city" : "上海",
          "price" : 270,
          "start" : 10,
          "comment_count" : 2,
          "full_room" : true,
          "create_time" : "2021-01-15 01:23:30",
          "location" : {
            "lat" : 40.012134,
            "lon" : 116.497553
          }
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "002",
        "_score" : 1.0,
        "_source" : {
          "title" : "好再来酒店",
          "city" : "青岛",
          "price" : 578.23,
          "comment_info" : {
            "properties" : {
              "favourable_comment" : 199,
              "negative_comment" : 68,
              "top3_favourable_comment" : {
                "top1" : {
                  "content" : "干净整洁的一家酒店",
                  "score" : 87
                },
                "top2" : {
                  "content" : "服务周到,停车方便",
                  "score" : 89
                },
                "top3" : {
                  "content" : "闹中取静,环境优美",
                  "score" : 90
                }
              }
            }
          }
        }
      },
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "004",
        "_score" : 1.0,
        "_source" : {
          "title" : "好再来酒店",
          "city" : "青岛",
          "price" : "578.23",
          "tags" : [
            "有车位",
            "免费WIFI"
          ]
        }
      }
    ]
  }
}
#通过以上信息可以看到,写入的数据的tag字段已经是数组类型了。那么,数组类型的数据如何搜索呢?
#数组类型的字段适用于元素类型的搜索方式,也就是说,数组元素适用于什么搜索,数组字段就适用于什么搜索。
#在上面的示例中,数组元素类型是keyword,该类型可以适用于term搜索,则tag字段也可以适用于term搜索
GET /hotel/_search
{
  "query": {
    "term": {
      "tags": {
        "value": "有车位"
      }
    }
  }
}

#ES中的空数组可以作为missing field,即没有值的字段,下面的DSL将插入一条tag为空的数组
POST /hotel/_doc/003
{
  "title":"环球酒店",
  "city":"青岛",
  "price":"530.00",
  "tags":[]
}

0 人点赞