1.倒排索引

1》mysql等数据库使用正向索引

如果根据id(索引列查询),速度会非常快,但是如果根据非索引列,并且模糊查询时,速度会非常慢,流程如下(比如id是索引列,title是要模糊查询的非索引列)

1)用户搜索数据,条件是title符合"%手机%"

2)逐行获取数据,比如id为1的数据

3)判断数据中的title是否符合用户搜索条件

4)如果符合则放入结果集,不符合则丢弃。回到步骤1

2》所以要用到倒排索引,倒排索引的搜索流程如下:

elasticsearch-LMLPHP

1)用户输入条件"华为手机"进行搜索。

2)对用户输入内容分词,得到词条:华为手机

3)拿着词条在倒排索引中查找,可以发现id为1 2 3的记录,或者有手机,或者有华为,或者两个都有,

4)拿着文档id 1 2 3到正向索引中查找具体文档。

3》那么为什么一个叫做正向索引,一个叫做倒排索引呢?

  • 正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程

  • 倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程

  • 倒排索引虽然要先查询倒排索引,再查询正向索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快!无需全表扫描。

2.  ElasticSearch 和mysql 的对应关系elasticsearch-LMLPHP

 3.索引库dsl操作

3.1mapping映射属性

mapping是对索引库中文档的约束,常见的mapping属性包括:

  • type:字段数据类型,常见的简单类型有:

    • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)

    • 数值:long、integer、short、byte、double、float、

    • 布尔:boolean

    • 日期:date

    • 对象:object

  • index:是否创建索引,默认为true

  • analyzer:使用哪种分词器

  • properties:该字段的子字段

例如下面的json文档:

{
    "age": 21,
    "weight": 52.1,
    "isMarried": false,
    "info": "黑马程序员Java讲师",
    "email": "zy@itcast.cn",
    "score": [99.1, 99.5, 98.9],
    "name": {
        "firstName": "云",
        "lastName": "赵"
    }
}

对应的每个字段映射(mapping):

  • age:类型为 integer;参与搜索,因此需要index为true;无需分词器

  • weight:类型为float;参与搜索,因此需要index为true;无需分词器

  • isMarried:类型为boolean;参与搜索,因此需要index为true;无需分词器

  • info:类型为字符串,需要分词,因此是text;参与搜索,因此需要index为true;分词器可以用ik_smart

  • email:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器

  • score:虽然是数组,但是我们只看元素的类型,类型为float;参与搜索,因此需要index为true;无需分词器

  • name:类型为object,需要定义多个子属性

    • name.firstName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器

    • name.lastName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器

3.2dsl操作索引库

json格式,用于ElasticSearch的语句,进行增删改查操作

1》创建索引库(索引库相当于mysql中的表)

PUT /heima  索引库名
{
  "mappings": {
    "properties": {
      "info":{  文档名
        "type": "text",  类型
        "analyzer": "ik_smart"  使用的分词器
      },
      "email":{
        "type": "keyword",
        "index": "false" 是否使用倒排索引排序 ,电子邮箱,不会根据电子邮箱查询,所以是false,其他属性默认是true
      },
      "name":{
        "properties": {  因为name中有姓和名两个属性,所以要写个properties
          "firstName": {
            "type": "keyword"
          }
        }
      },
    }
  }
}

2》查询索引库结构

GET /heima

3》修改索引库结构

倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping

只能增加新的属性

PUT /heima/_mapping
{
  "properties":{
    "age":{
      "type":"integer"
    }
  }
}

4》删除索引库

DELETE /索引库名

4.文档dsl操作

    1.添加一条文档(记录) 如果该文档已存在,则更新该文档


POST /heima/_doc/1
{
  "info":"黑马程序员java讲师",
  "email":"1960703672@qq.com",
  "name":{
    "firstname":"云",
    "lastname":"ZHAO"
  }
}

2. 查询一条文档(记录)

GET /heima/_doc/1

3.删除一条文档(记录)

DELETE /heima/_doc/1

4.修改一条文档,如果不存在,就创建新的该条文档

POST /heima/_doc/1
{
  "info":"黑马程序员java讲师",
  "email":"1960703672@qq.com",
  "name":{
    "firstname":"云",
    "lastname":"ZHAO"
  }
}

5.局部修改文档字段

POST /heima/_update/1
{
  "doc":{
    "email":"1195"
  }
}

5.dsl创建索引练习及文档查询操作

  • location:地理坐标,里面包含精度、纬度

  • all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索,
    会创建一个新的all字段,包含使用了all的其他字段(下面代码中的name,brand,city),并创建反向索引

PUT /hotel
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "ik_max_word",
        "copy_to": "all"
      },
      "address":{
        "type": "keyword",
        "index": false
      },
      "price":{
        "type": "integer"
      },
      "score":{
        "type": "integer"
      },
      "brand":{
        "type": "keyword",
        "copy_to": "all"
      },
      "city":{
        "type": "keyword",
        "copy_to": "all"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword"
      },
      "location":{
        "type": "geo_point"
      },
      "pic":{
        "type": "keyword",
        "index": false
      },
      "all":{
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

文档查询操作

1.查询所有文档

GET /hotel/_search
{
  "query": {
    "match_all": {}
  }
}

2. 全文检索查询

模糊查询info中有java工程师的,模糊查询不太准确,因为是根据分词来倒排索引查询的,比如分成了java 和工程师,会去找eleaticsearch分词后的字典中工程师和java对应的id,所以比如查询参数的info是 java 哈哈哈哈 工程师,是可以查询出来的,但如果是java 工程大师,工程大师在eleaticsearch分词后的字典中没有(就是eleaticsearch的info中没有工程大师info的记录),就查询不出来。

GET /heima2/_search
{
  "query": {
    "match": {"info":"java工程师"}
  }
}

   在定义索引(表结构)时,定义了all字段,关联了 酒店名称 品牌 城市 三个字段
    所以可以直接根据all字段查询,会查询酒店名称  或者品牌 或者城市中有上海的

GET /hotel/_search
{
  "query": {
    "match": {
      "all": "上海"
    }
  }
}

3.全文检索查询2

如果没有定义all字段,想查询酒店名称  或者品牌 或者城市中有上海的 ,就必须使用如下的查询方式,速度很慢,推荐在定义表时使用all字段,查询all字段。

GET /hotel/_search
{
  "query": {
    "multi_match": {
      "query": "杭州",
      "fields": ["brand","name","bussiness"]
    }
  }
}

4.term精确查询

查询城市上海的 (比如有个上海市,则不能查询到)

GET /hotel/_search
{
  "query": {
    "term": {
      "city": {
        "value": "上海"
      }
    }
  }
}

5.范围查询  要查询的字段必须是数字类型,可以比较的。

GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 1000,
        "lte": 2000
      }
    }
  }
}

6.地理位置查询1(不常用)画一个矩形,查询在该矩形范围内的所有地址

GET /hotel/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": {
          "lat": 31.1,
          "lon": 121.5
        },
        "bottom_right": {
          "lat": 30.9,
          "lon": 0.0
        }
      }
    }
  }
}

7.地理位置查询2 (很常用)  附近的人,查询距离某个点什么范围的记录

GET /hotel/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km",
      "location": "31.21,121.5"
    }
  }
}

8.设置查询的优先级

在使用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列。比如在搜索虹桥如家时:

[
  {
    "_score" : 17.850193,
    "_source" : {
      "name" : "虹桥如家酒店真不错",
    }
  },
  {
    "_score" : 12.259849,
    "_source" : {
      "name" : "外滩如家酒店真不错",
    }
  },
  {
    "_score" : 11.91091,
    "_source" : {
      "name" : "迪士尼如家酒店真不错",
    }
  }
]

 算法主要有两种:

elasticsearch-LMLPHP

 而这个算分结果,是可以被干预的,比如,收了如家的广告费,想要如家的搜索结果提前

实现:

     

elasticsearch-LMLPHP

 原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)

  • 过滤条件:filter部分,符合该条件的文档才会重新算分

  • 算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数

    • weight:函数结果是常量

    • field_value_factor:以文档中的某个字段值作为函数结果

    • random_score:以随机数作为函数结果

    • script_score:自定义算分函数算法

  • 运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:

    • multiply:相乘

    • replace:用function score替换query score

    • 其它,例如:sum、avg、max、min

GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {
        "match": {
          "all": "外滩"
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "brand": "如家"
            }
          },
          "weight": 10
        }
      ]
    }
  }
}

查询结果:

elasticsearch-LMLPHP

9. 布尔查询

    一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:

  • must:必须匹配每个子查询,类似“与”

  • should:选择性匹配子查询,类似“或”

  • must_not:必须不匹配,不参与算分,类似“非”

  • filter:必须匹配,不参与算分

  • 比如在搜索酒店时,除了关键字搜索外,我们还可能根据品牌、价格、城市等字段做过滤:

    elasticsearch-LMLPHP每一个不同的字段,其查询的条件、方式都不一样,必须是多个不同的查询,而要组合这些查询,就必须用bool查询了。

  • 查询城市是上海的,品牌是皇冠假日或者华美达的,价格大于500的,评分大于45的酒店

  • #布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询
    GET /hotel/_search
    {
      "query": {
        "bool": {
          "must": [
            {
              "term": {
                "city": "上海"
              }
            }
          ],
          "should": [
            {"term": {"brand": "皇冠假日"}},
            {"term":{"brand":"华美达"}}
          ],
          "must_not": [
            {"range": {"price": {
              "lte": 500
            }}}
          ],
          "filter": [
            {
              "range": {
                "score": {
                  "gte": 45
                }
              }
            }
          ]
        }
      }
    }
  • 10.根据地理位置排序(查询距离某个点最近的)

  • GET /hotel/_search
    {
      "query": {
        "match_all": {}
      },
      "sort": [
        {
          "_geo_distance": {
            "location": {
              "lat": 40.476483,
              "lon": 115.97481
            },
            "order": "asc"
          }
        }
      ]
    }
  • 6.搜索结果处理

  • 6.1手动排序  

  • elasticsearch默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。

    手动排序后,不会再打分,也就没有_score属性了。

  • GET /indexName/_search
    {
      "query": {
        "match_all": {}
      },
      "sort": [
        {
          "FIELD": "desc"  // 排序字段、排序方式ASC、DESC
        }
      ]
    }

    6.2分页查询

  • elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:

  • from:从第几个文档开始

  • size:总共查询几个文档

  • GET /hotel/_search
    {
      "query": {
        "match_all": {}
      },
      "from": 0, // 分页开始的位置,默认为0
      "size": 10, // 期望获取的文档总数
    }

    6.3高亮

  • 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。

  • 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮

  • 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false

    #fields指定高亮的标签,如果搜索字段和高亮字段(即city和name)不是一个,需要require_field_match设为false
    GET /hotel/_search
    {
      "query": {
        "match": {
          "city": "上海"
        }
      },
      "highlight": {
        "fields": {
          "name": {
            "require_field_match": "false"
          }
        }
      }
    }
12-14 17:35