本頁說明如何使用模糊搜尋,做為全文搜尋的一部分。
除了使用 SEARCH 和 SEARCH_SUBSTRING 函式執行精確權杖搜尋外,Spanner 也支援近似 (或模糊) 搜尋。模糊搜尋功能可找出相符的文件,即使查詢和文件之間有細微差異也沒問題。
Spanner 支援下列類型的模糊搜尋:
- 以 N 元語法為基礎的近似搜尋
- 使用 Soundex 進行語音搜尋
使用以 n 元語法為基礎的近似搜尋
以 N 元文為基礎的模糊搜尋,與子字串搜尋採用的子字串權杖化方式相同。分詞器設定非常重要,因為這會影響搜尋品質和效能。以下範例說明如何建立查詢,使用拼錯或拼法不同的字詞,在搜尋索引中尋找近似相符項目。
結構定義
GoogleSQL
CREATE TABLE Albums (
AlbumId STRING(MAX) NOT NULL,
AlbumTitle STRING(MAX),
AlbumTitle_Tokens TOKENLIST AS (
TOKENIZE_SUBSTRING(AlbumTitle, ngram_size_min=>2, ngram_size_max=>3,
relative_search_types=>["word_prefix", "word_suffix"])) HIDDEN
) PRIMARY KEY(AlbumId);
CREATE SEARCH INDEX AlbumsIndex
ON Albums(AlbumTitle_Tokens)
STORING (AlbumTitle);
PostgreSQL
本範例使用 spanner.tokenize_substring。
CREATE TABLE albums (
albumid character varying NOT NULL,
albumtitle character varying,
albumtitle_tokens spanner.tokenlist GENERATED ALWAYS AS (
spanner.tokenize_substring(albumtitle, ngram_size_min=>2, ngram_size_max=>3,
relative_search_types=>'{word_prefix, word_suffix}'::text[])) VIRTUAL HIDDEN,
PRIMARY KEY(albumid));
CREATE SEARCH INDEX albumsindex
ON albums(albumtitle_tokens)
INCLUDE (albumtitle);
查詢
以下查詢會找出與「Hatel Kaliphorn」最接近的專輯名稱,例如「Hotel California」。
GoogleSQL
SELECT AlbumId
FROM Albums
WHERE SEARCH_NGRAMS(AlbumTitle_Tokens, "Hatel Kaliphorn")
ORDER BY SCORE_NGRAMS(AlbumTitle_Tokens, "Hatel Kaliphorn") DESC
LIMIT 10
PostgreSQL
本範例使用 spanner.score_ngrams 和 spanner.search_ngrams。
SELECT albumid
FROM albums
WHERE spanner.search_ngrams(albumtitle_tokens, 'Hatel Kaliphorn')
ORDER BY spanner.score_ngrams(albumtitle_tokens, 'Hatel Kaliphorn') DESC
LIMIT 10
針對以 N 元文法為基礎的近似搜尋,盡可能提升效能和召回率
上一節中的範例查詢會使用兩種不同的函式,分兩個階段進行搜尋:
SEARCH_NGRAMS找出與搜尋查詢共用 n 元語法的所有候選相簿。 舉例來說,「California」的三字元 n 元包含[cal, ali, lif, ifo, for, orn, rni, nia],「Kaliphorn」則包含[kal, ali, lip, iph, pho, hor, orn]。這些資料集中的共用 n 元語法是[ali, orn]。根據預設,SEARCH_NGRAMS會比對至少有兩個共用 n 元語法的任何文件,因此「Kaliphorn」會比對到「California」。SCORE_NGRAMS會依相似度排序相符結果。兩個字串的相似度定義為不重複的共用 n 元語法與不重複的非共用 n 元語法的比率:
通常 SEARCH_NGRAMS 和 SCORE_NGRAMS 函式會使用相同的搜尋查詢。建議您使用附有查詢參數的引數,而非字串常值,並在 SEARCH_NGRAMS 和 SCORE_NGRAMS 函式中指定相同的查詢參數。
Spanner 有三個設定引數,可與 SEARCH_NGRAMS 搭配使用:
- n 元的最小和最大大小是使用
TOKENIZE_SUBSTRING或TOKENIZE_NGRAMS函式指定。我們不建議使用單一字元 n 元語法,因為這類語法可能會比對到大量文件。另一方面,長 n 元語法會導致SEARCH_NGRAMS錯過短的錯字。 SEARCH_NGRAMS必須相符的 n 元語法數量下限 (在SEARCH_NGRAMS中使用min_ngrams和min_ngrams_percent引數設定)。數字越高,查詢速度通常越快,但喚回度會降低。
為在效能和召回率之間取得良好平衡,您可以設定這些引數,以符合特定查詢和工作負載。
此外,我們也建議加入內部 LIMIT,以免遇到熱門 n 元組合時,產生費用高昂的查詢。
GoogleSQL
SELECT AlbumId
FROM (
SELECT AlbumId,
SCORE_NGRAMS(AlbumTitle_Tokens, @p) AS score
FROM Albums
WHERE SEARCH_NGRAMS(AlbumTitle_Tokens, @p)
LIMIT 10000 # inner limit
)
ORDER BY score DESC
LIMIT 10 # outer limit
PostgreSQL
本範例使用 spanner.score_ngrams 和 spanner.search_ngrams。查詢參數 $1 會繫結至「Hatel Kaliphorn」。
SELECT albumid
FROM
(
SELECT albumid, spanner.score_ngrams(albumtitle_tokens, $1) AS score
FROM albums
WHERE spanner.search_ngrams(albumtitle_tokens, $1)
LIMIT 10000
) AS inner_query
ORDER BY inner_query.score DESC
LIMIT 10
以 N 元文法為基礎的模糊搜尋與進階查詢模式
除了以 n 元語法為基礎的模糊搜尋外,強化查詢模式也會處理部分拼字錯誤。因此,這兩項功能有些重疊。下表列出兩者的差異:
| 以 n 元語法為基礎的模糊搜尋 | 強化查詢模式 | |
| 費用 | 需要根據 n 元語法進行成本較高的子字串權杖化 | 需要較便宜的全文權杖化 |
| 搜尋查詢類型 | 適合用於字數較少的簡短文件,例如人名、城市名稱或產品名稱 | 無論文件或搜尋查詢的大小為何,都能同樣順暢運作 |
| 部分字詞搜尋 | 執行子字串搜尋,允許錯別字 | 僅支援搜尋完整字詞 (SEARCH_SUBSTRING 不支援 enhance_query 引數) |
| 錯別字 | 支援索引或查詢中的錯別字 | 僅支援查詢中的錯別字 |
| 更正 | 找出任何拼字錯誤的相符項目,即使該項目不是真正的字詞 | 修正常見知名單字的拼字錯誤 |
使用 Soundex 執行語音搜尋
Spanner 提供 SOUNDEX 函式,可找出拼法不同但發音相同的字詞。舉例來說,SOUNDEX("steven")、SOUNDEX("stephen") 和 SOUNDEX("stefan") 都是「s315」,而 SOUNDEX("stella") 則是「s340」。SOUNDEX 須區分大小寫,且僅適用於拉丁字母。
如要使用 SOUNDEX 實作語音搜尋,可以採用產生的資料欄和搜尋索引,如下列範例所示:
GoogleSQL
CREATE TABLE Singers (
SingerId INT64,
AlbumTitle STRING(MAX),
AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN,
Name STRING(MAX),
NameSoundex STRING(MAX) AS (LOWER(SOUNDEX(Name))),
NameSoundex_Tokens TOKENLIST AS (TOKEN(NameSoundex)) HIDDEN
) PRIMARY KEY(SingerId);
CREATE SEARCH INDEX SingersPhoneticIndex ON Singers(AlbumTitle_Tokens, NameSoundex_Tokens);
PostgreSQL
本範例使用 spanner.soundex。
CREATE TABLE singers (
singerid bigint,
albumtitle character varying,
albumtitle_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) VIRTUAL HIDDEN,
name character varying,
namesoundex character varying GENERATED ALWAYS AS (lower(spanner.soundex(name))) VIRTUAL,
namesoundex_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.token(lower(spanner.soundex(name))) VIRTUAL HIDDEN,
PRIMARY KEY(singerid));
CREATE SEARCH INDEX singersphoneticindex ON singers(albumtitle_tokens, namesoundex_tokens);
下列查詢會將 SOUNDEX 上的「stefan」比對至「Steven」,以及包含「cat」的 AlbumTitle:
GoogleSQL
SELECT SingerId
FROM Singers
WHERE NameSoundex = LOWER(SOUNDEX("stefan")) AND SEARCH(AlbumTitle_Tokens, "cat")
PostgreSQL
SELECT singerid
FROM singers
WHERE namesoundex = lower(spanner.soundex('stefan')) AND spanner.search(albumtitle_tokens, 'cat')
後續步驟
- 瞭解權杖化和 Spanner 權杖化工具。
- 瞭解搜尋索引。
- 瞭解全文搜尋查詢。