ES 操作

170 阅读20分钟

分页查询

分页查询的三种方式

新增与修改

  在 ES 7.0 之后,type 只有 _doc。

  • PUT 与 POST 的区别,post 可不用指定ID,会自动生成 ID
  • POST 在没指定 ID 的时候,多次提交会生成多个数据,是非幂等的
  • PUT 修改的时候只允许全量替换,POST 可以使用指定的语法修改指定字段。
PUT product/_doc/1
{
  "name": "xiaomi",
  "price": 123.123456,
  "dateTime" : "2021-12-04",
  "age" : 1,
  "owner" : {
    "id" : 2,
    "color" : "black"
  },
  "desc" : "我是描述202112"
}
POST product/_doc
{
  "name": "xiaomi",
  "price": 123.123456,
  "dateTime" : "2021-12-04",
  "age" : 1,
  "owner" : {
    "id" : 2,
    "color" : "black"
  },
  "desc" : "我是描述202112"
}

--------------------指定字段修改----------------------------
POST product/_update/1
{
  "doc": {
    "age": 2
  }
}

批量操作

  速度快,可读性差。

语法 :action 是操作类型,metadata 是 index 的数据与要生成的数据的 ID,data 是元数据 action 类型

严格按照以下格式,不能换行与空格等
可以新增修改删除一起操作
POST /_bulk
{"action":{"metadata"}}
{"data"}

或者
POST /<index名>/_bulk
{"action":{"metadata"}}
{"data"}

批量新增

POST _bulk
{"create":{"_index":"employee","_id":16}}
{"name":"我会失败,因为id已经存在"}
{"create":{"_index":"employee","_id":17}}
{"name":"我会成功"}
{"create":{"_index":"employee","_id":18}}
{"name":"我会成功"}
{"create":{"_index":"employee","_id":19}}
{"name":"我会成功"}
{"create":{"_index":"employee","_id":20}}
{"name":"我会成功"}

# POST /<index名>/_bulk
# {"action":{"metadata"}}
# {"data"}
POST /employee/_bulk
{"create":{"_id":21}}
{"name":"我会成功"}
{"create":{"_id":22}}
{"name":"我会成功"}
{"create":{"_id":23}}
{"name":"我会成功"}

批量修改

POST /employee/_bulk
{"update":{"_id":21}}
{"doc":{"name":"我修改了"}}
{"update":{"_id":22}}
{"doc":{"name":"指定字段修改"}}
{"update":{"_id":23}}
{"doc":{"name":"修改成功"}}
{"update":{"_id":23}}

删除数据

DELETE product/_doc/2

批量删除

POST /employee/_bulk
{"delete":{"_id":17}}
{"delete":{"_id":18}}
{"delete":{"_id":19}}
{"delete":{"_id":20}}
{"delete":{"_id":21}}
{"delete":{"_id":22}}
{"delete":{"_id":23}}

查询

  term 和 match 的区别就是 match 会把数据进行规范化分词然后匹配上一个就返回,term 准确查询<关键词>

  1. 首先插入两条数据
PUT product/_doc/1
{
  "name": "xiaomi",
  "price": 123.123456,
  "dateTime" : "2021-12-04",
  "age" : 1,
  "owner" : {
    "id" : 2,
    "color" : "black"
  },
  "desc" : "我是描述202112"
}
PUT product/_doc/2
{
  "name": "huawei",
  "price": 234.123456,
  "dateTime" : "2021-12-05",
  "age" : 2,
  "owner" : {
    "id" : 1,
    "color" : "red"
  },
  "desc" : "我-也包含2021-12-04"
}

简单查询

# 相当于 select * from product
GET /product/_search
# 相当于 select * from product where 1 = 1 
GET /product/_search
{
  "query": {
    "match_all": {}
  }
}
# 相当于 select name,age,owner.* from product
# * 是通配符,和 mysql 类似
GET /product/_search
{
  "_source": [
    "name",
    "age",
    "owner.*"
  ]
}
# 和上面一个一样
# 还有一个 excludes,和 includes 作用相反,如果发生冲突,以 excludes 为准
GET /product/_search
{
  "_source": {
    "includes": [
      "name",
      "age",
      "owner.*"
    ]
  }
}


# select * from product where name = xiaomi
GET /product/_search?q=name:xiaomi

# select * from product order by price desc limit 2
GET /product/_search?from=0&size=2&sort=price:desc

# 精准查询 exact value 相当于 select * from product where dateTime = 2021-12-04
GET /product/_search?q=dateTime:2021-12-04

# _all 搜索,会在所有创建了倒排索引的字段中检索,也就是没有生成倒排索引就不会查询到
GET /product/_search?q=2021-12-04

# 这样查询,xiaomi 就查不到了,因为dateTime是只支持精确查询的,而desc字段被分词器分为了多个词项
GET /product/_search?q=2021

# 这个查询会把两个都查出来,但是本该在上面的xiaomi会到下边
# 因为<我也> 查询的时候也会被分词,然后<我>都满足,而<也>只有 huawei 满足
GET /product/_search?q=我也

# 查到的结果和上面一样,支持 fuzziness 也就是模糊查询,和模糊查询的区别就是 match 会分词
GET /product/_search
{
  "query": {
    "match": {
      "desc": "我也",
      # "fuzziness":1
    }
  }
}


# 在 desc 和 name 两个字段中查询,只要有一个字段包含分割后的 query 里的数据就会返回
GET /product/_search
{
  "query": {
    "multi_match": {
      "query": "也 xiaomi",
      "fields": ["desc", "name"]
    }
  }
}

# 短语匹配,将词进行切分,匹配倒排索引,顺序不一致也会查不到,中间有别的词项也会查不到
# 下面这种可以查询出来
GET /product/_search
{
  "query": {
    "match_phrase": {
      "desc": "我 - --- 也"
    }
  }
}

---------------------------------精确查询----------------------------------------------

# 精准匹配 这个查不到,因为分词器把 <我也包含2021-12-04> 分词之后创建了索引。
# 如果单独查<也>的话会查询到 huawei
# term 和 match 的区别就是 match 会把数据进行规范化和分词然后匹配上一个就返回,term 准确查询<关键词>
GET /product/_search
{
  "query": {
    "term": {
      "desc": "也"
    }
  }
}

# 这个能查询出来,因为默认情况下,会为 text类型的数据生成一个 keyword 类型的<子列>
# keyword可以改名字,类型是固定的,会把前 256 位字符一块使用创建一个索引,所以这样可以查出来
# 但是<我>和<也>中间的<->不能删除,因为一个字符串是一个整体
GET /product/_search
{
  "query": {
    "term": {
      "desc.keyword": "我-也包含2021-12-04"
    }
  }
}

# 相当于 select * from product where desc in ('我','也')
GET /product/_search
{
  "query": {
    "terms": {
      "desc": [
        "我",
        "也"
      ]
    }
  }
}
# 范围查询  >=gte   and  <= lte 不带 e 就是 > 或 <
# time_zone 时区
GET /product/_search
{
  "query": {
    "range": {
      "dateTime": {
        "time_zone": "+08:00", 
        "gte": "2021-12-05T08:00:00",
        "lte": "2021-12-05T08:00:00"
      }
    }
  }
}
# 日期范围查询可以用函数, now+1d/d  今天 +1天/天(单位)
GET /product/_search
{
  "query": {
    "range": {
      "dateTime": {
        "gte": "2021-12-05",
        "lte": "2021-12-05"
      }
    }
  }
}

固定相关度评分查询

  不再计算 _score(相关度评分),不再按照_score 排序,返回的 _score more 是1.0 或者由 boost 指定

---------------------------------固定相关度评分查询--------------------------------------
GET /product/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "term": {
          "desc": "我"
        }
      },
      "boost": 1.2
    }
  }
}

bool 查询

  支持多个 filter 条件, filter 可以换成 must 和 must_not 或者 should。

  • filter : 里面的条件必须都满足,不计算相关度评分,并且会有缓存;
  • must : 里面的条件必须都满足,计算相关度评分;
  • must_not : 里面的条件必须都不满足,不计算相关度评分,但是和 filter 一块使用就不计算相关度评分了;
  • should : 里面的条件满足一个就行,计算相关度评分。

  几种可以组合查询
  要注意 should 在和 filter、must 或 must_not 组合后,里面的条件一个也不满足都行, 可以使用minimum_should_match 设置最低应该匹配的条件数。

---------------------------------bool查询----------------------------------------------
# must 和 should 联合查询
GET /product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "desc": "我"
          }
        }
      ],
      "should": [
        {
          "term": {
            "name": "huawei"
          }
        },
        {
          "bool": {
            "filter": [
              {
                "term": {
                  "name": "xiaomi"
                }
              }
            ]
          }
        }
      ],
      "minimum_should_match": 1,
      "boost": 1.2
    }
  }
}

# must_not 和 filter 组合使用
GET /product/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
          "term": {
            "desc": "嗷嗷嗷嗷"
          }
        }
      ],
      "filter": {
        "term": {
          "desc": "我"
        }
      }
    }
  }
}

批量查询

# 批量查询,7.0 之后 _type 不再需要指定
GET employee/_mget
{
  "docs" : [
    {
      "_index" : "employee",
      "_id" : 1
    },
    {
      "_index" : "employee",
      "_id" : 2
    }
    ]
}
GET employee/_mget
{
  "ids" : [1,2]
}

响应结果

{
# 请求消耗了多长时间
  "took" : 0,
# 请求是否超时
  "timed_out" : false,
# 请求的分片信息
  "_shards" : {
    # 请求了一个分片
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
# 返回结果
  "hits" : {
    "total" : {
      # 访问到几行数据
      "value" : 2,
      "relation" : "eq"
    },
     # 返回的数据最大的相关度评分
    "max_score" : 1.0,
    "hits" : [
      {
        # 这条数据属于的索引
        "_index" : "product",
        # 这条数据属于的 type
        "_type" : "_doc",
        # 这条数据的 id
        "_id" : "1",
        # 这条数据的相关度评分
        "_score" : 1.0,
        # 真正存储的数据
        "_source" : {
          "dateTime" : "2021-12-04",
          "owner" : {
            "color" : "black"
          },
          "price" : 123.123456
        }
      },
      {
        "_index" : "product",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "dateTime" : "2021-12-05",
          "owner" : {
            "color" : "red"
          },
          "price" : 234.123456
        }
      }
    ]
  }
}

嵌套类型查询

  nested 属于 object 类型的一种,是Elasticsearch中用于复杂类型对象数组的索引操作。Elasticsearch没有内部对象的概念,因此,ES在存储复杂类型的时候会把对象的复杂层次结果扁平化为一个键值对列表。

  比如:

{	
  "group" : "group_a",	
  "users" : [	
    {	
      "name" : "John",	
      "age" :  23	
    },	
    {	
      "name" : "Alice",	
      "age" :  18
    }	
  ]	
}

  文档创建后,user数组中的每个json对象会以下面的形式存储,name 和 age 的关联性丢失

{
	"group" : "group_a",
  "users.name" : ["John","Alice"],
  "users.age" : [23,18],
}

  因为 name 和 age 的关联性丢失,所以在以下查询的时候,John 的 age 为 13 但是也会把该数据查出来

GET /XXX/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "users.name": "John"
          }
        },
        {
          "term": {
            "users.age": 18
          }
        }
      ]
    }
  }
}

  那么把 users 设置为 nested 类型,再进行 nested 查询就可以了

# 如果 name 是中文,那么还需要指定 name 的中文分词器
PUT test_must
{
  "mappings": {
    "properties": {
      "users" : {
        "type": "nested"
      }
    }
  }
}

nested 查询

参数:

  • path:相当于对的字段名
  • score_mode:评分计算方式
    • avg(默认)︰使用所有匹配的子对象的平均相关性得分。
    • max:使用所有匹配的子对象中的最高相关性得分。
    • min:使用所有匹配的子对象中最低的相关性得分。
    • none:不要使用匹配的子对象的相关性分数。该查询为父文档分配得分为0。
    • sum:将所有匹配的子对象的相关性得分相加。
GET /test_must/_search
{
  "query": {
    "nested": {
      "path": "users", 
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "users.name": "John"
              }
            },
            {
              "term": {
                "users.age": "18"
              }
            }
          ]
        }
      }
    }
  }
}

模糊查询和智能搜索推荐

前缀搜索

  以 XX 开头的搜索,不计算相关度评分。
  匹配的是 term,而不是完整的数据(field 对应的 value)

  • 前缀搜索的性能很差
  • 前缀搜索没有缓存
  • 前缀搜索尽可把前缀长度设置的更长,这样查更准确。

语法:

GET <index>/_search
{
  "query": {
    "prefix": {
      "FIELD": {
        "value": ""
      }
    }
  }
}

通配符查询

  匹配的也是 term
  语法:

GET <index>/_search
{
  "query": {
    "wildcard": {
      "FIELD": {
        "value": ""
      }
    }
  }
}

模糊查询

  匹配的也是词项。
  混淆字符(box → fox) 缺少字符(black → lack)
  多出字符(sic → sick) 颠倒次序(act→ cat)
  语法:

GET <index>/_search
{
  "query": {
    "fuzzy": {
      "FIELD": {
        "value": ""
      }
    }
  }
}

  参数:

  • value:必须,关键词
  • fuzziness:编辑距离,(0,1,2)并非越大越好,大了召回率高但不准确
    • 两文本之间的 Damerau-Levenshtein 距离是使一字符串与另一字符串匹配所需的插入、删除、替换和调换的数量
    • 距离公式:Levenshtein 是 Luncene 的,es 改进版:Damerau-Levenshtein,比如 axe=>aex 的距离Levenshtein=2 ,Damerau-Levenshtein=1
  • transpositions:可选,布尔值,为 false 时,两个相邻字符的变位(ab→ba)使用 Levenshtein 计算距离,默认为 true。

match_phrase_prefix

  和 match_phrase 不同。match_phrase 中 term 的顺序不能颠倒,match_phrase_prefix 可以通过 slop 进行一些设置。

  如果是一个短语比如"this is zha" .他会先在倒排索引中做以 zha 做前缀搜索,然后在匹配到的 doc 中做match_phrase 查询。

参数:

  • max_expansions:限制查询的 term 的个数,比如"this is zha" ,只会匹配 N 个符合 zha 的 term,但是这个匹配动作是分片级的,也就是每个分片匹配 N 个 term,然后根据这个 term 对应的倒排表去进行 match_phrase 查询,就是说返回的数据可能比 max_expansions 设置的数量多。
  • slop:slop参数告诉match_phrase查询词条相隔多远时仍然能将文档视为匹配,什么是相隔多远?意思是说为了让查询和文档匹配你需要移动词条多少次。

聚合查询

聚合查询

桶 Buckets 、指标 Metrics

  • 桶在概念上类似于 SQL 的分组(GROUP BY),提供了一种给文档分组的方法来让我们可以计算感兴趣的指标。
  • 而指标则类似于 COUNT() 、 SUM() 、 MAX() 等统计方法。

创建测试数据

# 因为默认是不为 text 类型的数据创建正排索引的,所以这里设置为 keyword,没有正排索引就没法进行聚合查询和排序
# 如果非要为 text 设置正排索引,那么可以用 XXX.keyword 或者 filedata 设置为 true
PUT employee
{
  "mappings": {
    "properties": {
      "id": {
        "type": "integer"
      },
      "name": {
        "type": "keyword"
      },
      "job": {
        "type": "keyword"
      },
      "age": {
        "type": "integer"
      },
      "gender": {
        "type": "keyword"
      }
    }
  }
}
PUT employee/_bulk
{"index":{"_id":1}}
{"id":1,"name":"Bob","job":"java","age":21,"sal":8000,"gender":"male"}
{"index":{"_id":2}}
{"id":2,"name":"Rod","job":"html","age":31,"sal":18000,"gender":"female"}
{"index":{"_id":3}}
{"id":3,"name":"Gaving","job":"java","age":24,"sal":12000,"gender":"male"}
{"index":{"_id":4}}
{"id":4,"name":"King","job":"dba","age":26,"sal":15000,"gender":"female"}
{"index":{"_id":5}}
{"id":5,"name":"Jonhson","job":"dba","age":29,"sal":16000,"gender":"male"}
{"index":{"_id":6}}
{"id":6,"name":"Douge","job":"java","age":41,"sal":20000,"gender":"female"}
{"index":{"_id":7}}
{"id":7,"name":"cutting","job":"dba","age":27,"sal":7000,"gender":"male"}
{"index":{"_id":8}}
{"id":8,"name":"Bona","job":"html","age":22,"sal":14000,"gender":"female"}
{"index":{"_id":9}}
{"id":9,"name":"Shyon","job":"dba","age":20,"sal":19000,"gender":"female"}
{"index":{"_id":10}}
{"id":10,"name":"James","job":"html","age":18,"sal":22000,"gender":"male"}
{"index":{"_id":11}}
{"id":11,"name":"Golsling","job":"java","age":32,"sal":23000,"gender":"female"}
{"index":{"_id":12}}
{"id":12,"name":"Lily","job":"java","age":24,"sal":2000,"gender":"male"}
{"index":{"_id":13}}
{"id":13,"name":"Jack","job":"html","age":23,"sal":3000,"gender":"female"}
{"index":{"_id":14}}
{"id":14,"name":"Rose","job":"java","age":36,"sal":6000,"gender":"female"}
{"index":{"_id":15}}
{"id":15,"name":"Will","job":"dba","age":38,"sal":4500,"gender":"male"}
{"index":{"_id":16}}
{"id":16,"name":"smith","job":"java","age":32,"sal":23000,"gender":"male"}

普通聚合查询

  相当于 select XX ,count(*) from table_name group by XX
  order 可以用 _key(key) 或 _count(doc_count)倒序或正序排序
  aggs 外层的 size 意思是原来数据的个数,放在返回结果的 hits 里,如果不需要,就设置为 0
  里面的 size 是 bucket 的个数。

GET employee/_search
{
  "size": 0, 
  "aggs": {
    "suiyiqiming": {
      "terms": {
        "field": "job",
        "size": 10,
        "order": {
          "_count": "asc"
        }
      }
    }
  }
}

指数聚合

GET employee/_search
{
  "size": 0, 
  "aggs": {
    "max_sal": {
      "max": {
        "field": "sal"
      }
    },
    "min_sal": {
      "min": {
        "field": "sal"
      }
    },
    "avg_sal": {
      "avg": {
        "field": "sal"
      }
    },
    "sum_sal": {
      "sum": {
        "field": "sal"
      }
    },
    "count_sal": {
      "value_count": {
        "field": "sal"
      }
    }
  }
}
# 查看所有指标、包含最大值、最小值、平均值、总和、有数据的个数
GET employee/_search
{
  "size": 0,
  "aggs": {
    "price_state": {
      "stats": {
        "field": "sal"
      }
    }
  }
}

# 查看<分词后>的词项   然后去重后的个数
# 就是说如果是 text,即使只有一条数据,但是被分成100个不同的词项,那么取到的数据也是 100 
GET employee/_search
{
  "size": 0,
  "aggs": {
    "cardinality_job_count": {
      "cardinality": {
        "field": "job"
      }
    }
  }
}

区间查询

范围区间

范围区间,前闭后开 [-∞,10000) [10000,20000)

GET employee/_search
{
  "size": 0,
  "aggs": {
    "range_job_count": {
      # 时间范围可以用 date_range,也可以还用 range
      "range": {
        "field": "sal",
        "ranges": [
          {
            "to": 10000
          },
          {
            "from": 10000, 
            "to": 20000
          }
        ]
      }
    }
  }
}

聚合之后,返回每个 bucket 里的元数据

GET employee/_search
{
  "size": 0,
  "aggs": {
    "range_job_count": {
      "range": {
        "field": "sal",
        "ranges": [
          {
            "to": 12000
          },
          {
            "from": 12000, 
            "to": 24000
          }
        ]
      },
      "aggs": {
        "hits_data": {
          "top_hits": {
            "size": 10,
            "_source": ["id","name"]
          }
        }
      }
    }
  }
}

区间分组

  区间分组 前闭后开 [0,10000) [10000,20000).......

  • keyed 默认是 false,只是返回的数据结构不一样,min_doc_count 是小于多少的不返回
  • 还有 missing 可以把字段上没值的数据都设置为指定值
GET employee/_search
{
  "size": 0,
  "aggs": {
    "interval_job_count": {
      "histogram": {
        "field": "sal",
        "interval": 10000,
        "keyed": false,
        "min_doc_count": 7
      }
    }
  }

时间区间分组

  按时间区间分组,在 ES7.0 之后 interval 废弃,推荐使用 calendar_interval 和 fixed_interval;

  fixed_interval:按多长时间分割,支持单位 ms s m h d 最大支持到天,格式 1d / 365d...

calendar_interval 支持单位:

  • year(1y)年
  • quarter(1q)季度
  • month(1M)月份
  • week(1w)星期
  • day(1d)天
  • hour(1h)小时
  • minute(1m)分钟
  • second(1s)秒

  extended_bounds 的意思是扩展范围,就算这个范围内的数据没值,也会展示出来并且 count 为 0

  extended_bounds 和 min_doc_count > 1 时冲突

GET employee/_search
{
   "size" : 0,
   "aggs": {
      "sales": {
         "date_histogram": {
            "field": "sold_date",
            "interval": "month",
            "format": "yyyy-MM-dd",
            "min_doc_count" : 0,
            "extended_bounds" : {
                "min" : "2016-01-01",
                "max" : "2017-12-31"
            },
            "order": {
              "_count": "asc"
            }
         }
      }
   }
}

百分位管道聚合

百分之1的价格在 XXX内、百分之5的价格在 XXX内........,根据 percents 中的值。

GET employee/_search
{
  "size": 0,
  "aggs": {
    "percentiles_sal": {
      "percentiles": {
        "field": "sal",
        "percents": [
          1,
          5,
          50,
          99
        ]
      }
    }
  }
}

小于等于1999 的占的百分比、小于等于2300占得百分比 ...... 根据 values 中的值。

GET employee/_search
{
  "size": 0,
  "aggs": {
    "percentiles_sal": {
      "percentile_ranks": {
        "field": "sal",
        "values": [
          1999,
          2300,
          14500,
          23000
        ]
      }
    }
  }
}

管道(bucket)聚合与嵌套聚合

  在聚合结果的基础上再进行聚合。

GET employee/_search
{
  "size": 0, 
  "aggs": {
    "job_bucket": {
      "terms": {
        "field": "job",
        "size": 10
      },
      # 这个意思是先按上面的条件分组,再按照分组后的数据取每个分组的平均值
        "aggs" : {
          "in_sal_bucket" : {
            "avg": {
              "field": "sal"
            }
          }
        }
    },
    # 名字随便起
    "min_sal" : {
      # 这个是固定的参数,min_bucket 里的意思是在 job_bucket 基础上 
			# 再按 in_sal_bucket 里的值中求最小值
      "min_bucket": {
        "buckets_path": "job_bucket>in_sal_bucket"
      }
    }
  }
}


--------------query 和 aggs 可以同时使用,先按 query筛选之后再进行聚合查询------------------
GET employee/_search
{
  "size": 0,
  "query": {
    "range": {
      "sal": {
        "gte": 10000,
        "lte": 20000
      }
    }
  }, 
  "aggs": {
    "interval_job_count": {
      "histogram": {
        "field": "sal",
        "interval": 10000
      }
    }
  }
}
--------------先进行聚合,再在聚合结果里进行过滤得到的数据放到 hits 里-----------------------
GET employee/_search
{
  "size": 10,
  "post_filter": {
    "range": {
      "sal": {
        "gte": 10000,
        "lte": 20000
      }
    }
  }, 
  "aggs": {
    "interval_job_count": {
      "histogram": {
        "field": "sal",
        "interval": 10000
      }
    }
  }
}
-----------query 和 aggs 可以同时使用后可以使用两种方式放弃query过的的聚合查询----------------
# global 全局的聚合查询
# filter 是filter 之后的聚合查询
GET employee/_search
{
  "size": 0,
  "query": {
    "range": {
      "sal": {
        "gte": 10000,
        "lte": 20000
      }
    }
  },
  "aggs": {
    "max_sal": {
      "max": {
        "field": "sal"
      }
    },
    "html_max_sal": {
    	"filter": {
        "term": {
          "job": "html"
        }
    }, 
    "all_max_sal": {
      "global": {},
      "aggs": {
        "max_sal": {
          "max": {
            "field": "sal"
          }
        }
      }
    }
  }
}

Scripting

  不仅是以下方式直接使用脚本,还可以在 query aggs 使用脚本。

脚本修改数据

# 用脚本的方式修改id为2的age+=1,默认是使用 painless 语言
# ctx 是<GET employee/_doc/2>返回结果的上下文,ctx._source.age 是返回结果里的 _source.age
POST employee/_update/2
{
  "script": {
    "source": "ctx._source.age+=1"
  }
}

POST employee/_update/2
{
  "script": {
    "source": "ctx._source.age+=ctx._version"
  }
}

# 简写
POST employee/_update/2
{
  "script": "ctx._source.age+=ctx._version"
}

# 修改:往数组里添加数据
POST employee/_update/2
{
  "script": "ctx._source.arrayName.add(数据)"
}

# 查询修改过的数据
GET employee/_doc/2

脚本删除操作

# 删除 ID = 5 的数据
POST product/_update/5
{
  "script": {
    "lang": "painless",
    "source": "ctx.op='delete'"
  }  
}

脚本数据不存在新增,存在就修改

# 存在就执行 source 里的脚本,不存在就新增 upsert 里的数据
POST product/_update/4
{
  "script": {
    "lang": "painless", 
    "source": "ctx._source.price += 1"
  },
  "upsert": {
    "name":"小米10",
    "price":4999
  }
}

脚本查询

  doc 是元数据取数据,只能用于简单数据类型,如果是复杂类型(类似于对象)需要用 params['_source']['field']。

  • 使用 doc 关键字,将导致该字段的条件被加载到内存(缓存),这将导致更快的执行,但更多的内存消耗。
  • params 的方式速度较慢,每次都会去加载,主要用在复杂类型上。

  取数据需要.value,否则数据是个数组,也可以.size(),如果.size() > 0 ,说明这个字段对应的 value 为空。

# 查询,只返回 script_fields 里定义的字段,expression语言只能是数字、布尔值、日期和 geo_point 
# source 里可以进行数据处理,doc 是元数据
GET employee/_search
{
  "script_fields": {
    "my_sal": {
      "script": {
        "lang": "expression",
        "source": "doc['sal'] * 10"
      }
    },
    "my_age": {
      "script": {
        "lang": "expression",
        "source": "doc['age']"
      }
    }
  }
}

# 查询,只返回 script_fields 里定义的字段
# source 里可以进行数据处理,也可以是多个数据
# doc['XX'] []可带可不带
GET employee/_search
{
  "script_fields": {
    "my": {
      "script": {
        "lang": "painless",
        "source": "[doc['sal'].value*10,doc['sal'].value]"
        
      }
    },
    "my_gender": {
      "script": {
        "lang": "painless",
        "source": "doc['gender'].value + '拼接的字符串'"
      }
    }
  }
}

参数化脚本

  在首次执行脚本的时候,会对目前的脚本进行编译,然后放到缓冲区里,缓冲区默认只有 100 M ,编译很浪费性能。

POST product/_update/4
{
  "script": {
    "lang": "painless",
    "source": "ctx._source.tags.add(params.tag_name)",
    "params": {"tag_name":"无线充电"}
  }
}

脚本模板

# 创建脚本模板 语法 _scripts/{id}
POST _scripts/discount_sal
{
  "script" : {
    "lang": "painless",
    "source": "doc.sal.value * params.discount"
  }
}

# 查看脚本模板
GET _scripts/discount_sal

# 使用脚本模板
GET employee/_search
{
  "script_fields": {
    "my": {
      "script": {
        "id": "discount_sal",
        "params": {
          "discount": 10
        }
      }
    }
  }
}

函数式编程

  Painless 可以使用函数式编程

# 函数式编程,三对"" 相当于 {},支持 for while if else 等关键字
GET employee/_search
{
  "script_fields": {
    "my": {
      "script": {
        "source": """
          double a = doc.sal.value *  params.discount;
          return a * 10;
        """,
        "params": {
          "discount": 10
        }
      }
    }
  }
}

搜索推荐:Suggester

  ES 针对不同的应用场景,把 Suggester 主要分为以下四种:

  • Term Suggester
  • Phrase Suggester
  • Comletion Suggester
  • Context Suggester

Term Suggester

  针对单独 Term 的搜索,不考虑搜索短语中多个 term 的关系

重要参数

  • text:用户搜索的文本
  • field:要从哪个字段选取推荐数据
  • analyzer:使用哪种分词器
  • size:每个建议返回的最大结果数
  • sort:如何按照提示词项排序,参数值只可以是以下两个枚举:
    • score:分数>词频>词项本身
    • frequency:词频>分数>词项本身
  • suggest_mode:搜索推荐的推荐模式,参数值亦是枚举:
    • missing:默认值,仅匹配不在索引中的词项,如果这个词在索引中存在,就不推荐了,也就是返回的 options 为空数组
    • popular:仅推荐比原始推荐词项文档词频(doc count)更高的相似词项,包含在索引中的词项也不会进行推荐,同 missing
    • always:根据 建议文本中的词项 推荐 任何匹配的建议词
  • max_edits:可以具有最大偏移距离候选建议以便被认为是建议。只能是1到2之间的值。任何其他值都将导致引发错误的请求错误。默认为2
  • prefix_length:前缀匹配的时候,必须满足的最少字符
  • min_word_length:最少包含的单词数量
  • min_doc_freq:最少的文档频率

添加数据

POST _bulk
{ "index" : { "_index" : "news","_id":1 } }
{ "title": "baoqiang bought a new hat with the same color of this font, which is very beautiful baoqiangba baoqiangda baoqiangdada baoqian baoqia"}
{ "index" : { "_index" : "news","_id":2 } }
{ "title": "baoqiangge gave birth to two children, one is upstairs, one is downstairs baoqiangba baoqiangda baoqiangdada baoqian baoqia"}
{ "index" : { "_index" : "news","_id":3} }
{ "title": "baoqiangge 's money was rolled away baoqiangba baoqiangda baoqiangdada baoqian baoqia"}
{ "index" : { "_index" : "news","_id":4} }
{ "title": "baoqiangda baoqiangda baoqiangda baoqiangda baoqiangda baoqian baoqia"}

搜索推荐

  baoqiang 是有的,但是推荐里没有,是因为 suggest_mode 是 missing 时,在索引里有的词项不会推荐,而 baoqing 会进行一些推荐。

  如果是 baoqia 虽然词项有 baoqian,返回的 options 也是空数组。

POST /news/_search
{
  "suggest": {
    "my-suggestion": {
      "text": "baoqing baoqiang",
      "term": {
        "suggest_mode":"missing",
        "field": "title"
      }
    }
  }
}

Phrase Suggester

  在 Term suggester 的基础上,会考量多个 term 之间的关系,比如是否同时出现在索引的原文里,相邻程度,以及词频等等。

  phrase 推荐器需要先前做过映射配置(即结构化数据 mapping )的来工作。

Phrase Suggester 使用

Completion Suggester

  主要针对场景是用户输入速度较快的情况下对后端响应速度要求比较苛刻。

  和上面两种推荐器采用了不同的数据结构,索引并非倒排索引来完成,而是将 analyze(切词)的数据编码成 FST 和索引一起存放。对于一个 open 状态的索引,FST 会被 ES 整个装载到内存中去,为了使用 Completion Suggester。这句介绍来源的链接

优点:

  基于内存,速度快

缺点:

  只适合前缀索引,因为 FST 只适合前缀索引

需要:

  需要结合特定的completion类型。

Context Suggester

  • Context Suggester 是 Completion Suggester 的扩展;

  • 可以在搜索中加入更多的上下文信息,然后根据不同的上下文信息,对相同的输入,比如"star",提供不同的建议值,比如:

  • 在咖啡页面:建议 "starbucks";

  • 在电影页面:建议 "star wars";

作者:乌鲁木齐001号程序员
链接:www.jianshu.com/p/4b477d023…
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

介绍与使用1 介绍与使用2