天天看點

es的幾個騷操作

前言

上一篇部落格寫到ElasticSearch有中文分詞檢索的能力,但如果僅僅就這個就完全沒辦法展現ElasticSearch的強大了,ElasticSearch還能支援短語搜尋,近似搜尋,搜尋推薦,搜尋糾正等搜尋引擎進階特性。可以極大地友善使用者,極大地提高使用者體驗。站内垂直搜尋幾乎在所有網際網路産品中都有運用,往往首頁最顯眼處都有一個搜尋框,如天貓,京東,拼多多,當當網,美團,餓了麼,優酷,愛奇藝,哔哩哔哩,QQ音樂,網易雲音樂,攜程,途牛等等等等,不出意外的話應該都是用ElasticSearch或者solr來實作的。

ES不錯的教學視訊:

  1. https://www.bilibili.com/video/av50555094/?p=66
  2. https://www.bilibili.com/video/av50555916

es會把每一個分詞器分好的詞建立反向索引,存儲每一個包含該詞語的id等資訊,個人猜測分詞的清單還會建立Hash表或者B樹,因為詞語的資料量一大,達到千萬級億級,性能無法保證,如果這樣檢索單個詞語的時候時間複雜度則為O(1)或者O(log(M,N)),當然純屬個人臆測啦,并沒有找到相關資料。

es的幾個騷操作

ElasticSearch

短語搜尋&近似搜尋

es會對搜尋關鍵字進行分詞,然後到反向索引中去比對,但如果使用者希望根據輸入的短語比對,比如就是要搜尋‘是一個靓仔’一定連一起的,跟資料庫的LIKE關鍵字效果一樣,而不是分詞之後隻要有【是,一個,靓仔】其中一個就OK,這個時候需要用到短語搜尋了;短語搜尋也支援任意兩個詞設定一個最大的距離,隻要滿足詞語詞之間不要超過這個距離都可以搜尋出來,比如包含【是不是 一個鮮肉 靓仔】需要搜尋出來,那麼距離就要設定為2了,這種可以稱之為近似搜尋。

原理:因為文檔分詞後建立反向索引時會記錄該詞在文檔中的position,比對到之後再比較一下position的內插補點即可,如果必須連在一起,那麼內插補點不能超過1。es也可以使用prefix,wildcard,regexp來檢索,但是這些是不能利用es索引的。

-- 建立映射
PUT /parse_search
{
  "mappings": {
    "properties": {
      "con_parse": {
        "type": "text"
      },
      "con_notana": {
        "type": "text",
        "fields": {
          "raw": {
            "type": "text",
            "index": false
          }
        }
      }
    }
  }
}

-- 插入資料
PUT /parse_search/_doc/1
{
  "con_parse":"淩章是一個靓仔",
  "con_notana":"淩章是一個靓仔"
}
PUT /parse_search/_doc/2
{
  "con_parse":"ling is a good looking boy",
  "con_notana":"ling is a good looking boy"
}

可以搜到,不符合要求
GET /parse_search/_search
{
  "query": {
    "match": {
      "con_parse": "a good"
    }
  }
}

可以搜到,不符合要求
GET /parse_search/_search
{
  "query": {
    "match": {
      "con_parse": "is good"
    }
  }
}

搜不到,不符合要求
GET /parse_search/_search
{
  "query": {
    "term": {
      "con_parse": {
        "value": "a good"
      }
    }
  }
}

搜不到,不符合要求
GET /parse_search/_search
{
  "query": {
    "term": {
      "con_parse": {
        "value": "a good"
      }
    }
  }
}

搜不到,符合要求
GET /parse_search/_search
{
  "query": {
    "match_phrase": {
      "con_parse": "is good"
    }
  }
}

可以搜到,符合要求
GET /parse_search/_search
{
  "query": {
    "match_phrase": {
      "con_parse": "a good"
    }
  }
}
可以搜到,間隔一個詞,符合要求
GET /parse_search/_search
{
  "query": {
    "match_phrase": {
      "con_parse": {
        "query": "is good",
        "slop": 2
      }
    }
  }
}

搜不到,不符合要求
GET /parse_search/_search
{
  "query": {
    "match_phrase": {
      "con_parse": {
        "query": "is goo",
        "slop": 2
      }
    }
  }
}
可以搜到,符合要求
GET /parse_search/_search
{
  "query": {
    "match_phrase_prefix": {
      "con_parse": {
        "query": "is goo",
        "slop": 2
      }
    }
  }
}
           

搜尋推薦&搜尋糾正

有些時候,使用者去做搜尋是,未必記得全名,這個時候我們需要給使用者一些推薦詞清單,喚起使用者的記憶,友好的幫助使用者搜尋;又有些時候,使用者會有意或無意把詞語拼錯,比如靓仔拼成靓崽,good拼成god,像筆者這種相當愛國、過四級還差兩分、又要經常搜一些代碼資料的人自然是無可避免(拿他真沒辦法),是以搜尋糾正對于es來說也是必不可少。

原理:es的實作使用的N-gram算法模型,我查了一下,他是自然語言處理NLP的一種模型,說起來有一點AI的意味了。使用fuzzy也可以完成檢索,但是不能利用es索引。

--- 搜尋推薦操作
PUT /ngram_search
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "my_tokenizer"
        }
      },
      "tokenizer": {
        "my_tokenizer": {
          "type": "edge_ngram",
          "min_gram": 1,
          "max_gram": 30,
          "token_chars": [
            "letter",
            "digit"
          ]
        }
      }
    }
  },"mappings": {
    "properties": {
      "con_ngram":{
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}
-- 測試
POST ngram_search/_analyze
{
  "analyzer": "my_analyzer",
  "text": "ling is a good looking boy"
}
PUT /parse_search/_doc/1
{
  "con_parse":"淩章是一個靓仔"
}
PUT /parse_search/_doc/2
{
  "con_parse":"ling is a good looking boy"
}
---  搜尋糾正操作
PUT /autoc_search
{
  "settings": {
    "analysis": {
      "filter": {
        "autocomplete_filter" : {
          "type" : "edge_ngram",
          "min_gram" : 1,
          "max_gram" : 20
        }
      },
      "analyzer": {
        "autocomplete" : {
          "type" : "custom",
          "tokenizer" : "standard",
          "filter" : [
            "lowercase",
            "autocomplete_filter"
          ]
        }
      } 
    }
  }
  , "mappings": {
    "properties": {
       "con_ngram":{
        "type": "text",
        "analyzer": "autocomplete"
      }
    }
  }
}
-- 檢視分詞
POST autoc_search/_analyze
{
  "analyzer": "autocomplete",
  "text": "ling is a good looking boy"
}

PUT /autoc_search/_doc/1
{
  "con_ngram":"淩章是一個靓仔"
}
PUT /autoc_search/_doc/2
{
  "con_ngram":"ling is a good looking boy"
}
-- 可以查到,搜尋糾正
GET /autoc_search/_search
{
  "query": {
    "match_phrase": {
      "con_ngram":{
        "query": "is a god"
      }
    }
  }
}
-- 不可以查到,隔了一個
GET /autoc_search/_search
{
  "query": {
    "match_phrase": {
      "con_ngram":{
        "query": "is god"
      }
    }
  }
} 
--可以查到,搜尋糾正
GET /autoc_search/_search
{
  "query": {
    "match_phrase": {
      "con_ngram":{
        "query": "is god",
        "slop": 1
      }
    }
  }
} 
-- 搜尋不到
GET /autoc_search/_search
{
  "query": {
    "match_phrase_prefix": {
      "con_ngram":{
        "query": "is god"
      }
    }
  }
} 
-- 可以搜到
GET /autoc_search/_search
{
  "query": {
    "match_phrase_prefix": {
      "con_ngram":{
        "query": "is god",
        "slop": 1
      }
    }
  }
}
           

PostgreSQL再提一筆

PostgreSQL也支援N-gram,是以再提一筆,需要添加pg_trgm擴充,使用gist_trgm後對左右比對都可以利用索引了,也可對正規表達式利用索引,可以實作近似搜尋,但是搜尋推薦和搜尋糾正貌似還是不行。

-- 建立擴充
create extension pg_trgm;
 
create table pg_ngram(
	id serial,
	con text,
	con_btree text,
	con_gin text,
	con_gist text
);

-- 索引
create index k_btree on pg_ngram using btree(con_btree);
create index k_gin on pg_ngram using gin(con_gin gin_trgm_ops);
create index k_gist on pg_ngram using gist(con_gist gist_trgm_ops);

-- 查詢
explain select * from pg_ngram where con like '%god looking%';
explain select * from pg_ngram where con_btree like '%god looking%';
explain select * from pg_ngram where con_gin like '%good looking%';
explain select * from pg_ngram where con_gist like '%good looking%';

explain select * from pg_ngram where con_gist ~ 'good looking';
explain select * from pg_ngram where con_gist ~ 'good looking[^。]';

--檢視分解
select show_trgm(con_gin) from pg_ngram;
select show_trgm(con_gist) from pg_ngram;
select similarity(con_gin,'god looking') from pg_ngram;
select similarity(con_gist,'god looking') from pg_ngram;