如何使用Elasticsearch实现对动态字段的搜索

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用Elasticsearch实现对动态字段的搜索相关的知识,希望对你有一定的参考价值。

参考技术A 需求如下:在Blog中保存着一串标签,标签名由用户自定义,如TagSex,其值也是可以自由输入的字符串,现需要根据这个动态的标签名和值筛选出这一篇Blog。

你可能使用mysql实现一个类似Elasticsearch倒排索引一样的关联tag和blog的关联表, 但如果还需要支持其他条件的筛选+排序那想一想都觉得复杂.

那我们换成Elasticsearch来实现吧.

索引(类似mysql的表)的Mapping(类似mysql的字段)如下

其他字段就不用多说, 我们来关注tags字段, tags类型是 Object 类型, 其可以存放任意字段.

多个tag就可以存放在这个Object里面:

或是还可以支持像这样的多个value:

在搜索时, 直接使用普通的query语句即可:

当然, 事情没这么简单, 你还需要考虑这几个问题:

下面的方案2就会解决key太多的问题,不过你觉得方案1也可行的话,我们就继续来解决第二个问题:如何将动态添加的字段类型固定为keyword?

当动态添加字段的时候Elasticsearch默认会自动推断类型, 如string就会使用 text 类型存储.

如果需要修改这个逻辑就需要使用到 dynamic templates .

在这个案例中, 我们需要对动态添加的tags字段进行精确搜索, 而不是全文搜索, 所以需要使用到keyword类型, 那么就可以这样写dynamic_templates:

现在动态的向tags对象里添加字段都将作为keyword类型存储。

key的多少会不会影响ES的性能笔者也没找到资料,抱歉也太多时间去验证,有兴趣的可以自己试验一下,不过猜测这对ES的性能影响很小,但如果你觉得太多的key不美观或者担心性能,那可以使用另一种方案:

设置tags字段的类型的Array,然后将tag和value拼接起来放在tags里,在搜索时就可以使用 term-query 来查询

document:

如果一个tag有多个值,那么可以这样存储:

"id": 1,
"tags": ["tagAbc=blue", "tagAbc=green"]


其中 = 作为tagKey和value的分隔符,可根据项目需要而定。
同样在搜索的时候也需要拼接key和value:

query:

这种方案的优点是不会生成太多的key,性能稳定,麻烦的是在存储到ES之前需要先处理一次(不过实际上不算什么问题)。

https://stackoverflow.com/questions/34556585/supporting-query-on-dynamic-columns-in-elastic-search

Elasticsearch:Dynamic field mapping

当 Elasticsearch 在文档中检测到新字段时,默认情况下会动态将该字段添加到类型映射中。 dynamic 参数控制此行为。

你可以通过将 dynamic 参数设置为 true 或 runtime 来明确指示 Elasticsearch 基于传入文档动态创建字段。 启用动态字段映射后,Elasticsearch 使用下表中的规则来确定如何映射每个字段的数据类型。

这篇文章是我之前文章 “Elasticsearch:Dynamic mapping” 的一个补充。

注意:下表中的字段数据类型是 Elasticsearch 动态检测的唯一字段数据类型。 你必须显式映射所有其他数据类型。

Elasticsearch 数据类型
JSON data type "dynamic": "true""dynamic": "runtime"
null不添加任何字段不添加任何字段
true 或者 falsebooleanboolean
doublefloatdouble
longlonglong
objectobject不添加任何字段
array依赖于数组里的第一个非 null 值依赖于数组里的第一个非 null 值
通过 date detection 的字符串datedate
通过 numeric detection 的字符串float 或者 longdouble 或者 long
不通过 date detection 或者 numberic detection 的字符串含有 .keyword 子字段的 text 类型keyword

你可以在文档和 object 级别禁用动态映射。 将 dynamic 参数设置为 false 会忽略新字段,如果 Elasticsearch 遇到未知字段,把 dynamic 设置为 strict 则会拒绝文档。

提示:使用  update mapping API 更新现有字段的 dynamic 设置。

你可以自定义 date detection 和 numeric detection 的动态字段映射规则。 要定义可应用于其他动态字段的自定义映射规则,请使用 dynamic_templates

我们可以使用如下的例子来进行展示:

PUT test_index

  "mappings": 
    "dynamic": "true",
    "date_detection": false, 
    "numeric_detection": false, 
    "properties": 
      "name": 
        "properties": 
          "firstname": 
            "type": "text"
          ,
          "lastname": 
            "type": "text"
          
        
      
    
  


PUT test_index/_doc/1

  "null": null,
  "bool": true,
  "double": 1.0,
  "long": 10,
  "name": 
    "firstname": "xiaoguo",
    "lastname": "liu"
  ,
  "subjects": ["Math", "Chinese", "English"],
  "date": "2015/09/02",
  "numeric": "1.0"



GET test_index/_mapping

在上面,我们创建了一个叫做 test_index 的索引。我们把它的 dynamic 属性设置为 true。那么上面最后的一个命令返回的 mapping 值为:


  "test_index" : 
    "mappings" : 
      "dynamic" : "true",
      "date_detection" : false,
      "numeric_detection" : false,
      "properties" : 
        "bool" : 
          "type" : "boolean"
        ,
        "date" : 
          "type" : "text",
          "fields" : 
            "keyword" : 
              "type" : "keyword",
              "ignore_above" : 256
            
          
        ,
        "double" : 
          "type" : "float"
        ,
        "long" : 
          "type" : "long"
        ,
        "name" : 
          "properties" : 
            "firstname" : 
              "type" : "text"
            ,
            "lastname" : 
              "type" : "text"
            
          
        ,
        "numeric" : 
          "type" : "text",
          "fields" : 
            "keyword" : 
              "type" : "keyword",
              "ignore_above" : 256
            
          
        ,
        "subjects" : 
          "type" : "text",
          "fields" : 
            "keyword" : 
              "type" : "keyword",
              "ignore_above" : 256
            
          
        
      
    
  

我们可以把 dynamic 设置为 runtime。运行如下的命令:

DELETE test_index

PUT test_index

  "mappings": 
    "dynamic": "runtime",
    "date_detection": false, 
    "numeric_detection": false, 
    "properties": 
      "name": 
        "properties": 
          "firstname": 
            "type": "text"
          ,
          "lastname": 
            "type": "text"
          
        
      
    
  


PUT test_index/_doc/1

  "null": null,
  "bool": true,
  "double": 1.0,
  "long": 10,
  "name": 
    "firstname": "xiaoguo",
    "lastname": "liu"
  ,
  "subjects": ["Math", "Chinese", "English"],
  "date": "2015/09/02",
  "numeric": "1.0"



GET test_index/_mapping

那么 test_index 的 mapping 为:


  "test_index" : 
    "mappings" : 
      "dynamic" : "runtime",
      "date_detection" : false,
      "numeric_detection" : false,
      "runtime" : 
        "bool" : 
          "type" : "boolean"
        ,
        "date" : 
          "type" : "keyword"
        ,
        "double" : 
          "type" : "double"
        ,
        "long" : 
          "type" : "long"
        ,
        "numeric" : 
          "type" : "keyword"
        ,
        "subjects" : 
          "type" : "keyword"
        
      ,
      "properties" : 
        "name" : 
          "properties" : 
            "firstname" : 
              "type" : "text"
            ,
            "lastname" : 
              "type" : "text"
            
          
        
      
    
  

我们和上面的结果比较一下,还是可以找到它们直接的区别的。

Date detection

如果启用 date_detection(默认),则检查新字符串字段以查看其内容是否与 dynamic_date_formats 中指定的任何日期模式匹配。 如果找到匹配项,则会添加一个具有相应格式的新日期字段。

dynamic_date_formats 的默认值为:

"strict_date_optional_time","yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"]

例如:

PUT my-index-000001/_doc/1

  "create_date": "2015/09/02"

上面的命令生成一个叫做 my-index-000001 的索引。它的 mapping 是:

GET my-index-000001/_mapping

  "my-index-000001" : 
    "mappings" : 
      "properties" : 
        "create_date" : 
          "type" : "date",
          "format" : "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
        
      
    
  

在默认的情况下,date detection 是启动的,所以我们可以看到 create_date 被识别为 date 类型的数据。

禁止 date detection

在有些时候,我们可以禁止 date detection,这样摄入的数据可以当做是字符串。可以通过将 date_detection 设置为 false 来禁用动态日期检测:

DELETE my-index-000001

PUT my-index-000001

  "mappings": 
    "date_detection": false
  


PUT my-index-000001/_doc/1 

  "create_date": "2015/09/02"


GET my-index-000001/_mapping

在上面,我们禁止了 date detection。上面最后一个命令返回的 mapping 值如下:


  "my-index-000001" : 
    "mappings" : 
      "date_detection" : false,
      "properties" : 
        "create_date" : 
          "type" : "text",
          "fields" : 
            "keyword" : 
              "type" : "keyword",
              "ignore_above" : 256
            
          
        
      
    
  

显然在这种情况下, create_date 就被当做是一般的 text 来进行处理。

定制 date detection 的日期格式

在上面,我们显示了在默认的情况下日期的检测格式如下:

"strict_date_optional_time","yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"]

在实际的很多生产环境中,可能这种格式并不是我们想要的,比如对于中国的许多应用软件,它们的输出格式会和其它地区的格式有所不同。例如:

DELETE my-index-000001

PUT my-index-000001

  "mappings": 
    "dynamic_date_formats": ["yyyy年MM月dd日"]
  


PUT my-index-000001/_doc/1

  "create_date": "2022年03月18日"


GET my-index-000001/_mapping

在上面,我们重新定义了 date detection 的日期格式,那么上面最好一个命令的返回的值为:


  "my-index-000001" : 
    "mappings" : 
      "dynamic_date_formats" : [
        "yyyy年MM月dd日"
      ],
      "properties" : 
        "create_date" : 
          "type" : "date",
          "format" : "yyyy年MM月dd日"
        
      
    
  

从上面,我们可以看出来 create_date 是一个 date 类型的字段。

Numeric detection

虽然 JSON 支持本机浮点和整数数据类型,但某些应用程序或语言有时可能会将数字呈现为字符串。 通常正确的解决方案是显式映射这些字段,但可以启用 numeric detection(默认情况下禁用)以自动执行此操作:

DELETE my-index-000001

PUT my-index-000001

  "mappings": 
    "numeric_detection": true
  


PUT my-index-000001/_doc/1

  "my_float":   "1.0", 
  "my_integer": "1" 


GET my-index-000001/_mapping

在上面,我们启动了 number detection,那么最后一个命令返回的结果为:


  "my-index-000001" : 
    "mappings" : 
      "numeric_detection" : true,
      "properties" : 
        "my_float" : 
          "type" : "float"
        ,
        "my_integer" : 
          "type" : "long"
        
      
    
  

更多阅读,请参阅文章 “Elasticsearch:Elasticsearch 中的数据强制匹配”。

以上是关于如何使用Elasticsearch实现对动态字段的搜索的主要内容,如果未能解决你的问题,请参考以下文章

如何通过Elasticsearch 6.x中的动态或未知字段进行聚合

利用kibana插件对Elasticsearch进行映射

Elasticsearch 8.X 如何动态的为正文添加摘要字段?

Elasticsearch 8.X 如何动态的为正文添加摘要字段?

Elasticsearch 8.X 如何动态的为正文添加摘要字段?

对 Elasticsearch 字段进行汇总和计数