PHP中使用ElasticSearch(二)


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

這幾個比較常用:

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

注:不要以為date只能表示 2015-01-01 這種類型,2015/01/01 12:10:30這種類型也一樣可以,不像MySQL里面時間還分很多種細分的類型,ES就一個date類型。

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

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

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

插入數據:

$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,模板里面這樣定義:

'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對英文的分詞都基於空格拆分

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM