PHP上Elasticsearch應用-搜尋篇

懶人包

比對方式:

  • TERM : 完全符合。
  • MATCH : 拆字,模糊搜尋。
  • MATCH_PHRASE : 不拆字,模糊搜尋。
  • MATCH_PHRASE_PREFIX : 不拆字,精準,模糊搜尋。

搜尋條件:

  • must : 多項查詢條件完全匹配,相當於 AND。
  • must_not : 多個查詢條件相反匹配,相當於 NOT。
  • should : 至少有一個條件匹配,相當於 OR。

因為筆者比較喜歡看到東西能動,再去了解他為什麼可以動。

所以此處我們先做一個小範例(此處不在教學環境建置及套件載入,各位讀者請自行各顯神通讓程式碼能動)。

搜尋精準度

讓我們先放置資料進 ES 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

require 'vendor/autoload.php';
use Elasticsearch\ClientBuilder;

$client = ClientBuilder::create()->build();

$params = [
'index' => 'my_index2',
'type' => 'my_type',
'id' => 1,
'body' => ['count' => '後山大火雞'],
];
$client->index($params);


$params = [
'index' => 'my_index2',
'type' => 'my_type',
'id' => 2,
'body' => ['count' => '後山大火災'],
];
$client->index($params);

在下搜尋指令

1
2
3
4
5
6
7
8
9
10
$paramsSeach['index'] = 'my_index2';
$paramsSeach['body'] = array(
'query' => array(
'match' => array(
'count' => '火雞',
),
)
);
$results = $client->search($paramsSeach);
var_dump($results['hits']['hits']);

畫面就會出現資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/Applications/MAMP/htdocs/jesda/es/index.php:97:
array (size=2)
0 =>
array (size=5)
'_index' => string 'my_index2' (length=9)
'_type' => string 'my_type' (length=7)
'_id' => string '1' (length=1)
'_score' => float 0.5753642
'_source' =>
array (size=1)
'count' => string '後山大火雞' (length=15)
1 =>
array (size=5)
'_index' => string 'my_index2' (length=9)
'_type' => string 'my_type' (length=7)
'_id' => string '2' (length=1)
'_score' => float 0.2876821
'_source' =>
array (size=1)
'count' => string '後山大火災' (length=15)

怪了?我搜尋火雞關你後山大火災屁事。

對於 ES 有基本程度了解都知道,因為 ES 進行了字詞切割並比對搜尋。

那如果我們就是想要搜尋火雞就好,那怎麼辦呢?

讓我們將指令內 match 替換成 match_phrase 試試看。

代碼如下:

1
2
3
4
5
6
7
8
9
10
$paramsSeach['index'] = 'my_index2';
$paramsSeach['body'] = array(
'query' => array(
'match_phrase' => array(
'count' => '火雞',
),
)
);
$results = $client->search($paramsSeach);
var_dump($results['hits']['hits']);

你將會看到,結果只剩下後山大火雞,而火災呢? 天要下雨娘要嫁人,隨它去吧~

對此搜尋關鍵字筆者有找到幾個比較常用的可以做一下介紹。

  • TERM : 完全符合。

  • MATCH : 拆字,模糊搜尋。

  • MATCH_PHRASE : 不拆字,模糊搜尋。

  • MATCH_PHRASE_PREFIX : 不拆字,精準,模糊搜尋。

在此特別提一下 MATCH_PHRASEMATCH_PHRASE_PREFIX

兩者差別在於,MATCH_PHRASE 在文章內搜尋關鍵字比重過低情況下該文檔會被忽略。而 MATCH_PHRASE_PREFIX 可解決這個問題。

當然還有調整搜尋的精準度可以進行調整,但此時筆者功力太淺。先以此解法解決部分文檔因為搜尋比重太低而被忽略問題了。

看到這裡可能會有人覺得,那我可不可以對文檔內資料做複合式搜尋。

猶如 SQL 指令 AND、OR、NOT IN 等等,要 A 要 B 不要 C。

有!都有! 讓我們繼續往下走。

複合式搜尋

在此我們先將資料洗一下。

讓我們來見識 NBA 大名鼎鼎的四位球員(文章主軸不是探討 NBA 球員,故只取球員部分生涯數據 )。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 $params = [
'index' => 'nba',
'type' => 'nba',
'id' => 1,
'body' => [
'name' => 'Kobe Bryant',
'position' => '後衛',
'glory' => '總冠軍 最有價值球員 得分王 全明星賽 神'
],
];
$client->index($params);

$params = [
'index' => 'nba',
'type' => 'nba',
'id' => 2,
'body' => [
'name' => 'Steve Nash',
'position' => '後衛',
'glory' => '最有價值球員 助攻王 全明星賽'
],
];
$client->index($params);

$params = [
'index' => 'nba',
'type' => 'nba',
'id' => 3,
'body' => [
'name' => 'LeBron James',
'position' => '前鋒',
'glory' => '總冠軍 最有價值球員 得分王 全明星賽'
],
];
$client->index($params);

$params = [
'index' => 'nba',
'type' => 'nba',
'id' => 4,
'body' => [
'name' => 'Shaquille O\'Neal',
'position' => '中鋒',
'glory' => '總冠軍 最有價值球員 得分王 全明星賽'
],
];
$client->index($params);

讓我們看一下,ES 提供的搜尋條件有哪些:

  • must : 多項查詢條件完全匹配,相當於 AND。
  • must_not : 多個查詢條件相反匹配,相當於 NOT。
  • should : 至少有一個條件匹配,相當於 OR。

讓我們來看看如何使用。
先來最簡單的,找出得過 最有價值球員 榮耀的。

1
2
3
4
5
6
7
8
9
10
$paramsSeach['index'] = 'nba';
$paramsSeach['body'] = array(
'query' => array(
'match_phrase' => array(
'glory' => '最有價值球員',
),
)
);
$results = $client->search($paramsSeach);
var_dump($results['hits']['hits']);

結果輸出是四位球員,那如果要同時擁有 最有價值球員得分王呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
$paramsSeach['index'] = 'nba';
$paramsSeach['body'] = array(
'query' => [
'bool' => [
'must' => [
'match_phrase' => ['glory' => '最有價值球員'],
'match_phrase' => ['glory' => '得分王'],
],
],
],
);
$results = $client->search($paramsSeach);
var_dump($results['hits']['hits']);

這邊直接將搜尋條件 query 內應用換掉,讓他可以達成 最有價值球員 AND 得分王 的搜尋條件。
可以看到 Steve Nash 被篩選掉了。

小叮嚀:若是搜尋類型使用 match 因為會幫你拆字搜尋,所以 得分王/助攻王 都會被條件找出來。導致沒有達到此處想要的效果。

這邊想要或是 只要得過 助攻王 的就被顯示出來。 等同搜尋條件上的 OR。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$paramsSeach['index'] = 'nba';
$paramsSeach['body'] = array(
'query' => [
'bool' => [
'should' => [
[
'bool' => [
'must' => [
'match_phrase' => ['glory' => '最有價值球員'],
'match_phrase' => ['glory' => '得分王'],
],
],
],
[
'bool' => [
'must' => [
'match_phrase' => ['glory' => '助攻王'],
],
],
]
]
],

],
);
$results = $client->search($paramsSeach);
var_dump($results['hits']['hits']);

這邊組成比較複雜一些,我們可以將搜尋條件看成:

1
2
3
$bool['bool']['msut'] => [
'match_phrase' => ['glory' => '最有價值球員'],
];

在一個一個放入 should 的陣列中,就能在裡面形成 OR 的條件效果。

仔細想想,把 放入凡人中是不是太不公平了些,我們這邊就將 排除掉(添加條件 must_not)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$paramsSeach['index'] = 'nba';
$paramsSeach['body'] = array(
'query' => [
'bool' => [
'should' => [
[
'bool' => [
'must' => [
'match_phrase' => ['glory' => '最有價值球員'],
'match_phrase' => ['glory' => '得分王'],
],
],
],
[
'bool' => [
'must' => [
'match_phrase' => ['glory' => '助攻王'],
],
],
]
],
'must_not' => [
'match_phrase' => ['glory' => '神'],
],
],

],
);
$results = $client->search($paramsSeach);
var_dump($results['hits']['hits']);

以上就是搜尋的簡單應用,其實還有一個搜尋的使用訪法。只是筆者還未能掌握,在此順便提供。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 加了這段
[
'match' => [
'position' => [
'query' => '前鋒 後衛',
'operator' => 'or',
],
],
],

$paramsSeach['index'] = 'nba';
$paramsSeach['body'] = array(
'query' => [
'bool' => [
'should' => [
[
'bool' => [
'must' => [
['match_phrase' => ['glory' => '最有價值球員']],
['match_phrase' => ['glory' => '得分王']],
[
'match' => [
'position' => [
'query' => '前鋒 後衛',
'operator' => 'or',
],
],
],
],
],
],
[
'bool' => [
'must' => [
'match_phrase' => ['glory' => '助攻王'],
],
],
]
],
'must_not' => [
'match_phrase' => ['glory' => '神'],
],
],

],
);
$results = $client->search($paramsSeach);
var_dump($results['hits']['hits']);

本來預想篩選出 position前鋒後衛 的資料。但因 match 關係 前鋒會被進行拆字搜尋。
待筆者日後 ES 精進了再來進行編寫。

聲明:此篇教學僅提供學習與交流使用,請勿用於商業用途。

參考資料

Elasticsearch 学习笔记(一)

初窥 Elasticsearch-PHP [1.0]

Author

LinYoYo

Posted on

2022-02-22

Updated on

2022-02-22

Licensed under