Trovare corrispondenze approssimative con la ricerca approssimativa

Questa pagina descrive come utilizzare una ricerca approssimativa nell'ambito di una ricerca full-text.

Oltre a eseguire ricerche esatte di token utilizzando le funzioni SEARCH e SEARCH_SUBSTRING, Spanner supporta anche le ricerche approssimative (o fuzzy). Le ricerche approssimative trovano documenti corrispondenti nonostante le piccole differenze tra la query e il documento.

Spanner supporta i seguenti tipi di ricerca fuzzy:

  • Ricerca approssimativa basata su n-grammi
  • Ricerca fonetica tramite Soundex

La ricerca approssimativa basata su n-grammi si basa sulla stessa tokenizzazione delle sottostringhe richiesta dalla ricerca di sottostringhe. La configurazione del tokenizer è importante perché influisce sulla qualità e sul rendimento della ricerca. Il seguente esempio mostra come creare una query con parole con errori ortografici o scritte in modo diverso per trovare corrispondenze approssimative nell'indice di ricerca.

Schema

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

Questo esempio utilizza 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);

Query

La seguente query trova gli album con titoli più simili a "Hatel Kaliphorn", ad esempio "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

Questo esempio utilizza spanner.score_ngrams e 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

Ottimizzare il rendimento e il richiamo per una ricerca approssimativa basata su n-grammi

La query di esempio nella sezione precedente esegue la ricerca in due fasi, utilizzando due funzioni diverse:

  1. SEARCH_NGRAMS trova tutti gli album candidati che hanno condiviso n-grammi con la query di ricerca. Ad esempio, i trigrammi per "California" includono [cal, ali, lif, ifo, for, orn, rni, nia] e per "Kaliphorn" includono [kal, ali, lip, iph, pho, hor, orn]. Gli n-grammi condivisi in questi set di dati sono [ali, orn]. Per impostazione predefinita, SEARCH_NGRAMS corrisponde a tutti i documenti con almeno due n-grammi condivisi, pertanto "Kaliphorn" corrisponde a "California".
  2. SCORE_NGRAMS classifica le corrispondenze in base alla somiglianza. La somiglianza di due stringhe è definita come un rapporto tra n-grammi condivisi distinti e n-grammi non condivisi distinti:
$$ \frac{shared\_ngrams}{total\_ngrams_{index} + total\_ngrams_{query} - shared\_ngrams} $$

Di solito, la query di ricerca è la stessa sia per le funzioni SEARCH_NGRAMS che per quelle SCORE_NGRAMS. Il modo consigliato per farlo è utilizzare l'argomento con i parametri di query anziché con i valori letterali stringa e specificare lo stesso parametro di query nelle funzioni SEARCH_NGRAMS e SCORE_NGRAMS.

Spanner ha tre argomenti di configurazione che possono essere utilizzati con SEARCH_NGRAMS:

  • Le dimensioni minime e massime per gli n-grammi sono specificate con le funzioni TOKENIZE_SUBSTRING o TOKENIZE_NGRAMS. Non consigliamo n-grammi di un solo carattere perché potrebbero corrispondere a un numero molto elevato di documenti. D'altra parte, gli n-grammi lunghi SEARCH_NGRAMS non rilevano le parole brevi con errori ortografici.
  • Il numero minimo di n-grammi che SEARCH_NGRAMS deve corrispondere (impostato con gli argomenti min_ngrams e min_ngrams_percent in SEARCH_NGRAMS). I numeri più alti in genere rendono la query più veloce, ma riducono il richiamo.

Per ottenere un buon equilibrio tra prestazioni e richiamo, puoi configurare questi argomenti in base alla query e al carico di lavoro specifici.

Consigliamo inoltre di includere un LIMIT interno per evitare di creare query molto costose quando viene rilevata una combinazione di n-grammi popolari.

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

Questo esempio utilizza spanner.score_ngrams e spanner.search_ngrams. Il parametro di ricerca $1 è associato a "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

Ricerca approssimativa basata su n-grammi e modalità query avanzata

Oltre alla ricerca approssimativa basata sugli n-grammi, la modalità di query avanzata gestisce anche alcune parole con errori ortografici. Pertanto, esiste una certa sovrapposizione tra le due funzionalità. La seguente tabella riassume le differenze:

Ricerca approssimativa basata su n-grammi Modalità di query avanzata
Costo Richiede una tokenizzazione di sottostringhe più costosa basata su n-grammi Richiede una tokenizzazione full-text meno costosa
Tipi di query di ricerca Funziona bene con documenti brevi con poche parole, ad esempio con il nome di una persona, di una città o di un prodotto Funziona altrettanto bene con documenti di qualsiasi dimensione e query di ricerca di qualsiasi dimensione
Ricerca di parole parziali Esegue una ricerca di sottostringhe che consente errori ortografici Supporta solo la ricerca di parole intere (SEARCH_SUBSTRING non supporta l'argomento enhance_query)
Parole con errori ortografici Supporta le parole con errori ortografici nell'indice o nella query Supporta solo le parole errate nella query
Correzioni Trova tutte le corrispondenze con errori ortografici, anche se la corrispondenza non è una parola reale Corregge gli errori ortografici per le parole comuni e note

Eseguire una ricerca fonetica con Soundex

Spanner fornisce la funzione SOUNDEX per trovare parole scritte in modo diverso, ma che hanno lo stesso suono. Ad esempio, SOUNDEX("steven"), SOUNDEX("stephen") eSOUNDEX("stefan") sono tutti "s315", mentre SOUNDEX("stella") è "s340". SOUNDEX fa distinzione tra maiuscole e minuscole e funziona solo per gli alfabeti basati sul latino.

La ricerca fonetica con SOUNDEX può essere implementata con una colonna generata e un indice di ricerca come mostrato nell'esempio seguente:

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

Questo esempio utilizza 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);

La seguente query associa "stefan" a "Steven" su SOUNDEX, insieme a AlbumTitle contenente "cat":

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')

Passaggi successivi