PHP中使用ElasticSearch(二)

首先從ES的支持的字段提及,ES文檔中字段有多種類型 官方文檔html

這幾個比較經常使用:數組

text,keyword,integer,float,boolean,object,geo_point(地理座標),geo_shape(描述地理區域),date.網絡

注:不要覺得date只能表示 2015-01-01 這種類型,2015/01/01 12:10:30這種類型也同樣能夠,不像MySQL裏面時間還分不少種細分的類型,ES就一個date類型。app

注意:這裏沒有列出array,在ES中,array不是一種單獨的類型,可是你能夠往ES裏面存數組,這個地方有點難以理解,
舉個例子: 文檔裏面我要定義一個字段叫 friends ,用來存儲用戶的朋友列表,
用 text 類型定義字段:dom

'friends' => [ 'type' => 'text' ]

看似這僅僅定義了一個text類型的字段,並非咱們想要的數組,重點解釋來了,雖然咱們的friends是字符串類型,可是
咱們在存入數據的時候 往 friends裏面存儲兩個或者三個字符串,他就變成數組了!
其實這句話描述仍是不許確,不是從字符串變成數組,而是多個字符串組成了一個數組!elasticsearch

插入數據:ide

$this->putDoc([ 'first_name' => $this->faker->name,
    'last_name' => $this->faker->name,
    'age' => $this->faker->numberBetween(20,80),
    'height' => (float)($this->faker->numberBetween(160,200)/100),
    'friends' => [ $this->faker->name(),
        $this->faker->name(),
        $this->faker->name(),
        $this->faker->name() ] ]);

這個putDoc來得有點忽然是否是?由於這是延續上一篇文章的續集,請看上一篇文章 使用PHP操做ElasticSearch性能

注意:faker是用來隨機生成數據的,詳細信息參考谷歌。測試

你看,friends明明是 text 類型,可是我在插入的時候插入了多條數據,他變成了一個字符串類型的集合,前面我說他是數組,這個地方
我把他說成是集合,這回更準確了,由於數組在ES中查詢是不能保證順序的,因此集合更準確,官方文檔中也表示他更像集合
再說一下object,模板裏面這樣定義:ui

'info' => [ 'type' => 'object',
    'properties' => [ 'country' => [ 'type' => 'text',
            'analyzer' => 'ik_max_word' ],
        'sex' => [ 'type' => 'keyword' ] ] ]

這裏定義了一個對象文檔,指定了下面兩個屬性的基本信息,可是不表明這個對象就只能存儲兩個屬性,
好比我還能夠在添加文檔的時候往裏面添加一個skin 膚色的字段,徹底沒有問題,只不過這裏定義的兩個
字段咱們設置了類型和具體的analyzer,沒有在這裏定義,可是咱們實際上添加了的字段好比skin,ES會
自動設置正確的類型,以及默認的analyzer.

存入數據:

'info' => [ 'country' => ['中國','印度','法國','英國','瑞士','剛果共和國'][random_int(0,5)],
    'sex' => ['男','女'][random_int(0,1)],
    'skin' => ['白','黑','黃'][random_int(0,2)], ]

還有一個keyword,他和text都表示字符串,區別在於 keyword裏面的值不會被分詞器分詞,text裏面的值會被分詞器智能拆分,
記住這一點,這一點很是重要,後面還會講到這個區別。

在定義text字段的時候 analyzer和index你須要清楚的地方:

'last_name' => [ 'type' => 'text',
    //'analyzer' => 'standard', // 這個地方不設置analyzer會默認standard //'index' => false
]

analyzer不設置analyzer會默認standard
對於老版本的 ES,這裏的index容許設置爲 analyzed/not_analyzed/no,
大部分網絡上的文章都是這樣講的,可是,最新版本已經移除了這些選項,
如今只能是 true或false,因此我建議當你有一點基礎後,通讀一下官方最新文檔,雖然是英文的
若是這裏設置爲false,這個字段不加入索引,不能在查詢條件中出現,默認爲true
等一下,這裏忽然發現有點不對勁,之前能夠設置 分析/不分析/不索引,如今只能設置索引和不索引了,
若是想實現索引且不分析,那keyword類型恰好符合,而text字段是爲分析而生的。

ES中的搜索分兩個概念,匹配和過濾
匹配一般針對的是 text 類型的字段,而過濾針一般對的精確的類型,好比 integer,keyword,date等,
之因此加了一般二字,說明他們之間沒有明確的規定,匹配能夠去匹配data,integer等類型,過濾也能夠去過濾text字段,
經過匹配的方式去找精確類型一般不會出現什麼問題,經過過濾去找text類型的數據一般會獲得意外的結果。
謹記:若是沒有特殊狀況,匹配針對text類型,過濾針對其餘類型,
可是對於精確類型使用過濾的性能一般比匹配更高,因此能使用過濾的地方都過濾。
注意:這裏要區別一下MySQL中的過濾概念,MySQL中的過濾是對查找後的數據進行過濾,而在ES中,過濾和匹配都等同於MySQL中的查找,
匹配適合查找模糊數據,過濾適合查找精確數據而已。
爲了簡化代碼,下面的搜索都基於一下這份代碼,更改的部分只是 $query:

$params = [ 'index' => $this->index,
    'type' => $this->type,
    'body' => array_merge([ 'from' => $from,
        'size' => $size ],$query) ];

經常使用的過濾:
term(精確查找)
查找倪玲爲44的數據

$query = [ 'query' => [ 'term' => [ 'age' => 44 ] ] ];

terms(精確查找多個字段)
查找年齡爲 44或55或66的數據

$query = [ 'query' => [ 'terms' => [ 'age' => [44,55,66] ] ] ];

range(範圍查找),

$query = [ 'query' => [ 'range' => [ 'age' => [ 'gt' => 43,
                'lt' => 45 ] ] ] ];

exists(等同於MySQL中的 is not null),
查找存在age屬性的文檔

$query = [ 'query' => [ 'exists' => [ 'field' => 'age' ] ] ];

missing(等同於 MySQL中的 is null),
注意:這個過濾方法在2.x版本就廢棄了,請使用 must_not 嵌套 exists 來實現
bool(用來組合其餘過濾條件,包含 must,must_not,should操做)

$query = [ 'query' => [ 'bool' => [ 'should' => [ 'range' => [ 'height' => ['gt' => 1.8] ] ],
            'must_not' => [ 'term' => [ 'info.sex' => '女' ] ],
            'must' => [ [ 'term' => [ 'info.country' => '法國' ] ], [ 'term' => [ 'info.skin' => '白' ] ] ] ] ] ];

上面這個查詢的意思是,身高應該大於1.8,性別不能是女,國家是法國且膚色是黑色。
這裏country其實是text類型,可是我任然經過過濾的方法找到了正確的值,可是這種方式是很是危險的,
這裏之因此找到了正確的值,是由於country類型很簡單,碰巧
analyzer(這裏用的ik,若是是standard就沒那麼好運了)沒有對其進行拆分。

經常使用的查詢:
match(匹配一個字段)

$query = [ 'query' => [ 'match' => [ 'height' => '1.8' ] ] ];

match_all(匹配全部文檔,至關於沒有條件)
等因而 $query = []
multi_match(匹配多個字段)
匹配姓和名裏面包含 'Riley Libby Preston' 的數據

$query = [ 'query' => [ 'multi_match' => [ 'query' => 'Riley Libby Preston',
            'fields' => ['first_name','last_name'] ] ] ];

bool(用來組合其餘匹配條件,包含 must,must_not,should操做)

$query = [ 'query' => [ 'bool' => [ 'should' => [ 'match' => [ 'height' => '1.8' ] ],
            'must_not' => [ 'match' => [ 'info.sex' => '男' ] ] ] ] ];

在實際使用中,匹配和過濾都是混合搭配使用的,好比:

$query = [ 'query' => [ 'bool' => [ 'should' => [ 'match' => [ 'height' => '1.8' ] ],
            'must_not' => [ 'term' => [ 'info.sex' => '女' ] ],
            'must' => [ [ 'match' => [ 'info.country' => '法國' ] ], [ 'match' => [ 'info.skin' => '白' ] ] ] ] ] ];

match時常會出現一些怪異的現象,若是你不清楚你用的analyzer,好比這個例子:

$query = [ 'query' => [ 'bool' => [ 'must' => [ [ 'match' => [ 'last_name' => 'Hamill' ] ], [ 'match' => [ 'info.country' => '法國' ] ] ] ] ] ];

這個查詢的需求是選出last_name中匹配到Hamill而且國家匹配到法國的結果,可是查詢的結果是這樣的,
last_name 的中包含 Hamill,在咱們意料之中,可是 country出現了英國,法國等不少國家,這個太意外了,
如今來改造一下這個 $query,很小的改造,只須要把法國改爲法,再次查詢,此次的結果完美的實現了咱們的需求。
緣由在於:
文檔中的法國二字被analyzer拆分紅 (法,國) 存儲在索引中,同理,英國被拆分爲 (英,國),
如今你搜索法國的時候,你的這個搜索詞默認會被拆分紅 (法,國),而後拿着這兩個詞去分別查找,
第一個法能夠匹配全部法國,第二個國字能夠匹配到英國,美國等全部包含國字的結果。
如今你知道結果的造成緣由了。
這個很大程度上上取決於你使用的analyzer,不一樣的analyzer分詞的策略不同,因此你有必要先搞明白你用的分詞器
他的大概分詞策略,上面這個例子沒有指定analyzer,是ES默認的分詞器在起做用,當我指定analyzer爲 ik_max_word後,狀況
發生了變化,這個時候法國被當成了一個總體,沒有被拆分。
能夠經過簡單的測試來看看具體分詞器的分詞方式:

$params = [ 'body' => [ 'analyzer' => 'ik_max_word', //默認 standard
        'text' => '我在廣場吃着炸雞' ] ]; return $this->EsClient->indices()->analyze($params);

默認分詞器standard會把這句話簡單的拆分紅單個字,而ik相對就更懂中文一點,拆分出來的詞更有語義化,大部分的analyzer對英文的分詞都基於空格拆分

相關文章
相關標籤/搜索