N-gram 기반 퍼지 검색은 하위 문자열 검색에 필요한 것과 동일한 하위 문자열 토큰화를 사용합니다. 토크나이저 구성은 검색 품질 및 성능에 영향을 주므로 중요합니다. 다음 예시에서는 잘못된 철자 또는 다른 철자의 단어가 포함된 쿼리를 만들어 검색 색인에서 근사 일치를 찾는 방법을 보여줍니다.
SEARCH_NGRAMS는 검색어로 N-그램을 공유한 모든 후보 앨범을 찾습니다.
예를 들어 'California'의 3자로 된 N-그램에는 [cal, ali,
lif, ifo, for, orn, rni, nia]가, 'Kaliphorn'의 경우 [kal, ali, lip,
iph, pho, hor, orn]이 포함됩니다. 이러한 데이터 세트에서 공유되는 N-그램은 [ali,
orn]입니다. 기본적으로 SEARCH_NGRAMS는 공유 N-그램이 2개 이상 있는 모든 문서와 일치시키므로 'Kaliphorn'은 'California'와 일치합니다.
SCORE_NGRAMS는 유사성을 기준으로 일치 항목의 순위를 매깁니다. 두 문자열의 유사성은 고유한 공유 N-그램과 고유한 비공유 N-그램의 비율로 정의됩니다.
일반적으로 검색어는 SEARCH_NGRAMS 함수와 SCORE_NGRAMS 함수에서 동일합니다. 이를 실행하는 권장 방법은 문자열 리터럴 대신 쿼리 파라미터와 함께 인수를 사용하고 SEARCH_NGRAMS 및 SCORE_NGRAMS 함수에 동일한 쿼리 파라미터를 지정하는 것입니다.
Spanner에는 SEARCH_NGRAMS와 함께 사용할 수 있는 세 가지 구성 인수가 있습니다.
N-그램의 최소 및 최대 크기는 TOKENIZE_SUBSTRING(/spanner/docs/reference/standard-sql/search_functions#tokenize_substring) 또는 TOKENIZE_NGRAMS 함수로 지정됩니다. 한 글자 N-gram은 매우 많은 문서와 일치할 수 있어 권장하지 않습니다. 반면에 긴 N-그램을 사용하면 SEARCH_NGRAMS가 철자가 틀린 짧은 단어를 놓칠 수 있습니다.
SEARCH_NGRAMS가 일치해야 하는 최소 N-그램 개수(SEARCH_NGRAMS의 min_ngrams 및 min_ngrams_percent 인수로 설정). 숫자가 클수록 일반적으로 쿼리 속도가 빨라지지만 재현율이 감소합니다.
성능과 재현율 간의 균형을 유지하기 위해 이러한 인수를 특정 쿼리 및 워크로드에 맞게 구성할 수 있습니다.
또한 인기 있는 N-그램 조합이 발생할 때 쿼리 비용이 크게 높지 않도록 내부 LIMIT를 포함하는 것이 좋습니다.
N-그램 기반 퍼지 검색 외에 향상된 쿼리 모드도 맞춤법이 틀린 단어를 처리합니다. 따라서 두 기능 사이에는 약간의 중복이 있습니다. 다음 표에는 차이점이 요약되어 있습니다.
N-그램 기반 퍼지 검색
향상된 쿼리 모드
비용
N-그램을 기반으로 더 비싼 하위 문자열 토큰화가 필요합니다.
비용이 더 저렴한 전체 텍스트 토큰화가 필요합니다.
검색어 유형
사람 이름, 도시 이름, 제품 이름과 같이 단어가 몇 개 밖에 없는 짧은 문서에 적합합니다.
모든 크기의 문서와 모든 크기의 검색어에 동일하게 작동합니다.
부분 단어 검색
맞춤법이 틀려도 검색할 수 있는 하위 문자열 검색을 실행합니다.
전체 단어 검색만 지원합니다(SEARCH_SUBSTRING은 enhance_query 인수를 지원하지 않음).
맞춤법이 틀린 단어
색인 또는 쿼리에서 맞춤법이 잘못된 단어를 지원합니다.
쿼리에서 맞춤법이 잘못된 단어만 지원합니다.
수정사항
실제 단어가 아니더라도, 철자가 틀린 항목과 일치하는 모든 경우를 찾아냅니다.
일반적이고 잘 알려진 단어의 철자 오류를 수정합니다.
Soundex로 음성 검색 수행
Spanner는 철자는 다르지만 발음은 동일한 단어를 찾는 SOUNDEX 함수를 제공합니다. 예를 들어 SOUNDEX("steven"), SOUNDEX("stephen"), SOUNDEX("stefan")은 모두 's315'이고 SOUNDEX("stella")은 's340'입니다. SOUNDEX는 대소문자를 구분하며 라틴어 기반 알파벳에만 작동합니다.
SOUNDEX를 사용한 음성 검색은 다음 예와 같이 생성된 열과 검색 색인으로 구현할 수 있습니다.
[[["이해하기 쉬움","easyToUnderstand","thumb-up"],["문제가 해결됨","solvedMyProblem","thumb-up"],["기타","otherUp","thumb-up"]],[["이해하기 어려움","hardToUnderstand","thumb-down"],["잘못된 정보 또는 샘플 코드","incorrectInformationOrSampleCode","thumb-down"],["필요한 정보/샘플이 없음","missingTheInformationSamplesINeed","thumb-down"],["번역 문제","translationIssue","thumb-down"],["기타","otherDown","thumb-down"]],["최종 업데이트: 2025-09-05(UTC)"],[],[],null,["# Find approximate matches with fuzzy search\n\n\u003cbr /\u003e\n\n\n| **Note:** This feature is available with the Spanner Enterprise edition and Enterprise Plus edition. For more information, see the [Spanner editions overview](/spanner/docs/editions-overview).\n\n\u003cbr /\u003e\n\nThis page describes how to use a fuzzy search as part of a\n[full-text search](/spanner/docs/full-text-search).\n\nIn addition to performing exact token searches using the\n[`SEARCH`](/spanner/docs/reference/standard-sql/search_functions#search_fulltext)\nand\n[`SEARCH_SUBSTRING`](/spanner/docs/reference/standard-sql/search_functions#search_substring)\nfunctions, Spanner also supports approximate (or fuzzy) searches. Fuzzy\nsearches find matching documents despite small differences between the query and\nthe document.\n\nSpanner supports the following types of fuzzy search:\n\n- N-grams-based approximate search\n- Phonetic search using [Soundex](https://en.wikipedia.org/wiki/Soundex)\n\nUse an n-grams-based approximate search\n---------------------------------------\n\nN-grams-based fuzzy search relies on the same\nsubstring tokenization that a\n[substring search](/spanner/docs/full-text-search/substring-search)\nrequires. The configuration of the tokenizer is important as it affects\nsearch quality and performance. The following example shows how to create a\nquery with misspelled or differently spelled words to find approximate matches\nin the search index.\n\n**Schema** \n\n### GoogleSQL\n\n CREATE TABLE Albums (\n AlbumId STRING(MAX) NOT NULL,\n AlbumTitle STRING(MAX),\n AlbumTitle_Tokens TOKENLIST AS (\n TOKENIZE_SUBSTRING(AlbumTitle, ngram_size_min=\u003e2, ngram_size_max=\u003e3,\n relative_search_types=\u003e[\"word_prefix\", \"word_suffix\"])) HIDDEN\n ) PRIMARY KEY(AlbumId);\n\n CREATE SEARCH INDEX AlbumsIndex\n ON Albums(AlbumTitle_Tokens)\n STORING (AlbumTitle);\n\n### PostgreSQL\n\nThis example uses\n[`spanner.tokenize_substring`](/spanner/docs/reference/postgresql/functions-and-operators#search_functions). \n\n CREATE TABLE albums (\n albumid character varying NOT NULL,\n albumtitle character varying,\n albumtitle_tokens spanner.tokenlist GENERATED ALWAYS AS (\n spanner.tokenize_substring(albumtitle, ngram_size_min=\u003e2, ngram_size_max=\u003e3,\n relative_search_types=\u003e'{word_prefix, word_suffix}'::text[])) VIRTUAL HIDDEN,\n PRIMARY KEY(albumid));\n\n CREATE SEARCH INDEX albumsindex\n ON albums(albumtitle_tokens)\n INCLUDE (albumtitle);\n\n**Query**\n\nThe following query finds the albums with titles that are the closest to\n\"Hatel Kaliphorn\", such as \"Hotel California\". \n\n### GoogleSQL\n\n SELECT AlbumId\n FROM Albums\n WHERE SEARCH_NGRAMS(AlbumTitle_Tokens, \"Hatel Kaliphorn\")\n ORDER BY SCORE_NGRAMS(AlbumTitle_Tokens, \"Hatel Kaliphorn\") DESC\n LIMIT 10\n\n### PostgreSQL\n\nThis examples uses\n[`spanner.score_ngrams`](/spanner/docs/reference/postgresql/functions-and-operators#search_functions)\nand\n[`spanner.search_ngrams`](/spanner/docs/reference/postgresql/functions-and-operators#search_functions). \n\n SELECT albumid\n FROM albums\n WHERE spanner.search_ngrams(albumtitle_tokens, 'Hatel Kaliphorn')\n ORDER BY spanner.score_ngrams(albumtitle_tokens, 'Hatel Kaliphorn') DESC\n LIMIT 10\n\n### Optimize performance and recall for an n-grams-based approximate search\n\nThe sample query in the previous section searches in two phases, using two\ndifferent functions:\n\n1. [`SEARCH_NGRAMS`](/spanner/docs/reference/postgresql/functions-and-operators#search_functions) finds all candidate albums that have shared n-grams with the search query. For example, three-character n-grams for \"California\" include `[cal, ali,\n lif, ifo, for, orn, rni, nia]` and for \"Kaliphorn\" include `[kal, ali, lip,\n iph, pho, hor, orn]`. The shared n-grams in these data sets are `[ali,\n orn]`. By default, `SEARCH_NGRAMS` matches all documents with at least two shared n-grams, therefore \"Kaliphorn\" matches \"California\".\n2. [`SCORE_NGRAMS`](/spanner/docs/reference/standard-sql/search_functions#score_ngrams) ranks matches by similarity. The similarity of two strings is defined as a ratio of distinct shared n-grams to distinct non-shared n-grams:\n\n$$ \\\\frac{shared\\\\_ngrams}{total\\\\_ngrams_{index} + total\\\\_ngrams_{query} - shared\\\\_ngrams} $$\n\nUsually the search query is the same across both the `SEARCH_NGRAMS` and\n`SCORE_NGRAMS` functions. The recommended way to do this is to use the argument\nwith\n[query parameters](/spanner/docs/reference/standard-sql/lexical#query_parameters)\nrather than with string literals, and specify the same query parameter in the\n`SEARCH_NGRAMS` and `SCORE_NGRAMS` functions.\n\nSpanner has three configuration arguments that can be used with\n`SEARCH_NGRAMS`:\n\n- The minimum and maximum sizes for n-grams are specified with the `TOKENIZE_SUBSTRING`(/spanner/docs/reference/standard-sql/search_functions#tokenize_substring) or [`TOKENIZE_NGRAMS`](/spanner/docs/reference/standard-sql/search_functions#tokenize_ngrams) functions. We don't recommend one character n-grams because they could match a very large number of documents. On the other hand, long n-grams cause `SEARCH_NGRAMS` to miss short misspelled words.\n- The minimum number of n-grams that `SEARCH_NGRAMS` must match (set with the `min_ngrams` and `min_ngrams_percent` arguments in `SEARCH_NGRAMS`). Higher numbers typically make the query faster, but reduce recall.\n\nIn order to achieve a good balance between performance and recall, you can\nconfigure these arguments to fit the specific query and workload.\n\nWe also recommend including an inner `LIMIT` to avoid creating very expensive\nqueries when a combination of popular n-grams is encountered. \n\n### GoogleSQL\n\n SELECT AlbumId\n FROM (\n SELECT AlbumId,\n SCORE_NGRAMS(AlbumTitle_Tokens, @p) AS score\n FROM Albums\n WHERE SEARCH_NGRAMS(AlbumTitle_Tokens, @p)\n LIMIT 10000 # inner limit\n )\n ORDER BY score DESC\n LIMIT 10 # outer limit\n\n### PostgreSQL\n\nThis example uses query parameter `$1` which is bound to 'Hatel Kaliphorn'. \n\n SELECT albumid\n FROM\n (\n SELECT albumid, spanner.score_ngrams(albumtitle_tokens, $1) AS score\n FROM albums\n WHERE spanner.search_ngrams(albumtitle_tokens, $1)\n LIMIT 10000\n ) AS inner_query\n ORDER BY inner_query.score DESC\n LIMIT 10\n\n### N-grams-based fuzzy search versus enhanced query mode\n\nAlongside n-grams-based fuzzy search, the\n[enhanced query mode](/spanner/docs/full-text-search/query-overview#enhanced_query_mode)\nalso handles some misspelled words. Thus, there is some overlap between the two\nfeatures. The following table summarizes the differences:\n\nPerform a phonetic search with Soundex\n--------------------------------------\n\nSpanner provides the\n[`SOUNDEX`](/spanner/docs/reference/standard-sql/string_functions#soundex)\nfunction for finding words that are spelled differently, but sound the same. For\nexample, `SOUNDEX(\"steven\")`, `SOUNDEX(\"stephen\")` and`SOUNDEX(\"stefan\")` are\nall \"s315\", while `SOUNDEX(\"stella\")` is \"s340\". `SOUNDEX` is case sensitive and\nonly works for Latin-based alphabets.\n\nPhonetic search with `SOUNDEX` can be implemented with a generated column and a\nsearch index as shown in the following example: \n\n### GoogleSQL\n\n CREATE TABLE Singers (\n SingerId INT64,\n AlbumTitle STRING(MAX),\n AlbumTitle_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(AlbumTitle)) HIDDEN,\n Name STRING(MAX),\n NameSoundex STRING(MAX) AS (LOWER(SOUNDEX(Name))),\n NameSoundex_Tokens TOKENLIST AS (TOKEN(NameSoundex)) HIDDEN\n ) PRIMARY KEY(SingerId);\n\n CREATE SEARCH INDEX SingersPhoneticIndex ON Singers(AlbumTitle_Tokens, NameSoundex_Tokens);\n\n### PostgreSQL\n\nThis example uses\n[`spanner.soundex`](/spanner/docs/reference/postgresql/functions-and-operators#string_functions). \n\n CREATE TABLE singers (\n singerid bigint,\n albumtitle character varying,\n albumtitle_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.tokenize_fulltext(albumtitle)) VIRTUAL HIDDEN,\n name character varying,\n namesoundex character varying GENERATED ALWAYS AS (lower(spanner.soundex(name))) VIRTUAL,\n namesoundex_tokens spanner.tokenlist GENERATED ALWAYS AS (spanner.token(lower(spanner.soundex(name))) VIRTUAL HIDDEN,\n PRIMARY KEY(singerid));\n\n CREATE SEARCH INDEX singersphoneticindex ON singers(albumtitle_tokens, namesoundex_tokens);\n\nThe following query matches \"stefan\" to \"Steven\" on `SOUNDEX`, along with\n`AlbumTitle` containing \"cat\": \n\n### GoogleSQL\n\n SELECT SingerId\n FROM Singers\n WHERE NameSoundex = LOWER(SOUNDEX(\"stefan\")) AND SEARCH(AlbumTitle_Tokens, \"cat\")\n\n### PostgreSQL\n\n SELECT singerid\n FROM singers\n WHERE namesoundex = lower(spanner.soundex('stefan')) AND spanner.search(albumtitle_tokens, 'cat')\n\n\u003cbr /\u003e\n\nWhat's next\n-----------\n\n- Learn about [tokenization and Spanner tokenizers](/spanner/docs/full-text-search/tokenization).\n- Learn about [search indexes](/spanner/docs/full-text-search/search-indexes).\n- Learn about [full-text search queries](/spanner/docs/full-text-search/query-overview)."]]