PostgreSQL supporta regole di confronto "insensibili all'accento"?


98

In Microsoft SQL Server, è possibile specificare un confronto "accent insensitive" (per un database, una tabella o una colonna), il che significa che è possibile per una query come

SELECT * FROM users WHERE name LIKE 'João'

per trovare una riga con un Joaonome.

So che è possibile rimuovere gli accenti dalle stringhe in PostgreSQL utilizzando la funzione contrib unaccent_string , ma mi chiedo se PostgreSQL supporti queste regole di confronto "accent insensitive" in modo che quanto SELECTsopra possa funzionare.


Vedi questa risposta per la creazione di un dizionario FTS con unaccent: stackoverflow.com/a/50595181/124486
Evan Carroll

Desideri ricerche con distinzione tra maiuscole e minuscole o senza distinzione tra maiuscole e minuscole?
Evan Carroll

Risposte:


204

Usa il modulo unaccent per quello, che è completamente diverso da quello a cui ti stai collegando.

unaccent è un dizionario di ricerca testuale che rimuove gli accenti (segni diacritici) dai lessemi.

Installa una volta per database con:

CREATE EXTENSION unaccent;

Se ricevi un errore del tipo:

ERROR: could not open extension control file
"/usr/share/postgresql/<version>/extension/unaccent.control": No such file or directory

Installa il pacchetto contrib sul tuo server database come indicato in questa risposta correlata:

Tra le altre cose, fornisce la funzione unaccent()che puoi usare con il tuo esempio (dove LIKEsembra non necessaria).

SELECT *
FROM   users
WHERE  unaccent(name) = unaccent('João');

Indice

Per utilizzare un indice per quel tipo di query, crea un indice sull'espressione . Tuttavia , Postgres accetta solo IMMUTABLEfunzioni per gli indici. Se una funzione può restituire un risultato diverso per lo stesso input, l'indice potrebbe interrompersi silenziosamente.

unaccent()solo STABLEnoIMMUTABLE

Sfortunatamente, unaccent()è solo STABLE, non IMMUTABLE. Secondo questo thread su pgsql-bugs , ciò è dovuto a tre motivi:

  1. Dipende dal comportamento di un dizionario.
  2. Non esiste una connessione cablata a questo dizionario.
  3. Dipende quindi anche dalla corrente search_path, che può cambiare facilmente.

Alcuni tutorial sul Web indicano di modificare la volatilità della funzione in IMMUTABLE. Questo metodo di forza bruta può rompersi in determinate condizioni.

Altri suggeriscono una semplice IMMUTABLEfunzione wrapper (come ho fatto io in passato).

È in corso il dibattito se rendere la variante con due parametri IMMUTABLE che dichiari esplicitamente il dizionario utilizzato. Leggi qui o qui .

Un'altra alternativa sarebbe questo modulo con un IMMUTABILE unaccent()funzione Musicbrainz , disponibile su Github. Non l'ho testato da solo. Penso di aver avuto un'idea migliore :

Meglio per ora

Questo approccio è più efficiente poiché altre soluzioni fluttuano e più sicure .
Creare una IMMUTABLEfunzione wrapper SQL che esegua il modulo a due parametri con funzione e dizionario qualificati per schema cablati.

Poiché annidare una funzione non immutabile disabiliterebbe la funzione inlining, basarla su una copia della funzione C, dichiarata IMMUTABLEanche (fake) . Il suo unico scopo è quello di essere utilizzato nel wrapper della funzione SQL. Non pensato per essere utilizzato da solo.

La sofisticazione è necessaria in quanto non c'è modo di cablare il dizionario nella dichiarazione della funzione C. (Richiederebbe l'hacking del codice C stesso.) La funzione wrapper SQL lo fa e consente sia l'inlining di funzioni che gli indici di espressione.

CREATE OR REPLACE FUNCTION public.immutable_unaccent(regdictionary, text)
  RETURNS text LANGUAGE c IMMUTABLE PARALLEL SAFE STRICT AS
'$libdir/unaccent', 'unaccent_dict';

CREATE OR REPLACE FUNCTION public.f_unaccent(text)
  RETURNS text LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT AS
$func$
SELECT public.immutable_unaccent(regdictionary 'public.unaccent', $1)
$func$;

Elimina PARALLEL SAFEda entrambe le funzioni per Postgres 9.5 o versioni precedenti.

publicessendo lo schema in cui hai installato l'estensione ( publicè l'impostazione predefinita).

La dichiarazione di tipo esplicita ( regdictionary) difende da attacchi ipotetici con varianti sovraccariche della funzione da parte di utenti malintenzionati.

In precedenza, ho sostenuto una funzione wrapper basata sulla STABLEfunzione unaccent()fornita con il modulo unaccent. Quella funzione disabilitata in linea . Questa versione viene eseguita dieci volte più velocemente della semplice funzione wrapper che avevo qui prima.
E questo era già due volte più veloce della prima versione aggiunta SET search_path = public, pg_tempalla funzione, finché non ho scoperto che anche il dizionario può essere qualificato come schema. Ancora (Postgres 12) non troppo ovvio dalla documentazione.

Se ti mancano i privilegi necessari per creare funzioni C, sei tornato alla seconda migliore implementazione: Un IMMUTABLEwrapper di funzione attorno alla STABLE unaccent()funzione fornita dal modulo:

CREATE OR REPLACE FUNCTION public.f_unaccent(text)
  RETURNS text AS
$func$
SELECT public.unaccent('public.unaccent', $1)  -- schema-qualify function and dictionary
$func$  LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT;

Infine, l' indice di espressione per rendere veloci le query :

CREATE INDEX users_unaccent_name_idx ON users(public.f_unaccent(name));

Ricordarsi di ricreare gli indici che coinvolgono questa funzione dopo qualsiasi modifica alla funzione o al dizionario, come un aggiornamento sul posto di una versione principale che non ricrea gli indici. Tutte le versioni principali recenti avevano aggiornamenti per il unaccentmodulo.

Adatta le query in modo che corrispondano all'indice (in modo che venga utilizzato dal pianificatore di query):

SELECT * FROM users
WHERE  f_unaccent(name) = f_unaccent('João');

Non hai bisogno della funzione nell'espressione giusta. Lì puoi anche fornire stringhe non accentate come 'Joao'direttamente.

La funzione più veloce non si traduce in query molto più veloci utilizzando l' indice di espressione . Funziona su valori precalcolati ed è già molto veloce. Ma la manutenzione dell'indice e le query non utilizzano il vantaggio dell'indice.

La sicurezza per i programmi client è stata rafforzata con Postgres 10.3 / 9.6.8 ecc. È necessario qualificare la funzione e il nome del dizionario come dimostrato quando utilizzati in qualsiasi indice. Vedere:

Legature

In Postgres 9.5 o versioni precedenti, le legature come 'or' o 'ß' devono essere espanse manualmente (se necessario), poiché unaccent()sostituisce sempre una singola lettera:

SELECT unaccent('Œ Æ œ æ ß');

unaccent
----------
E A e a S

Ti piacerà questo aggiornamento a unaccent in Postgres 9.6 :

Estendi contrib/unaccentil unaccent.rulesfile standard di per gestire tutti i segni diacritici noti a Unicode ed espandi correttamente le legature (Thomas Munro, Léonard Benedetti)

Grassetto mio. Ora otteniamo:

SELECT unaccent('Œ Æ œ æ ß');

unaccent
----------
OE AE oe ae ss

Corrispondenza del modello

Per LIKEo ILIKEcon pattern arbitrari, combinalo con il modulo pg_trgmin PostgreSQL 9.1 o successivo. Crea un trigramma GIN (in genere preferibile) o un indice di espressione GIST. Esempio per GIN:

CREATE INDEX users_unaccent_name_trgm_idx ON users
USING gin (f_unaccent(name) gin_trgm_ops);

Può essere utilizzato per query come:

SELECT * FROM users
WHERE  f_unaccent(name) LIKE ('%' || f_unaccent('João') || '%');

Gli indici GIN e GIST sono più costosi da mantenere rispetto al semplice btree:

Esistono soluzioni più semplici per i modelli ancorati a sinistra. Ulteriori informazioni sulla corrispondenza dei modelli e sulle prestazioni:

pg_trgmfornisce anche utili operatori per "similarità" ( %) e "distanza" ( <->) .

Gli indici Trigram supportano anche semplici espressioni regolari con ~et al. e pattern senza distinzione tra maiuscole e minuscole che corrispondono a ILIKE:


Nella tua soluzione, vengono utilizzati gli indici o dovrei creare un indice su unaccent(name)?
Daniel Serodio

@ErwinBrandstetter In psql 9.1.4, ottengo "le funzioni nell'espressione dell'indice devono essere contrassegnate come IMMUTABILI", poiché la funzione non accento è STABILE, invece di INMUTABILE. Che cosa mi consiglia?
e3matheus

1
@ e3matheus: Sentendomi in colpa per non aver testato la soluzione precedente che ho fornito, ho studiato e aggiornato la mia risposta con una soluzione nuova e migliore (IMHO) per il problema rispetto a quella che fluttua finora.
Erwin Brandstetter

Le regole di confronto non sono utf8_general_cila risposta a questo tipo di problemi?
Med

5
Le tue risposte valgono quanto la documentazione di Postgres: fenomenale!
elettrotipia

6

No, PostgreSQL non supporta le regole di confronto in questo senso

PostgreSQL non supporta regole di confronto del genere (non sensibili all'accento o meno) perché nessun confronto può restituire uguale a meno che le cose non siano uguali a livello binario. Questo perché internamente introdurrebbe molte complessità per cose come un indice hash. Per questo motivo le regole di confronto nel loro senso più stretto influiscono solo sull'ordinamento e non sull'uguaglianza.

Soluzioni alternative

Dizionario di ricerca full-text che non accenti i lessemi.

Per FTS, puoi definire il tuo dizionario usando unaccent,

CREATE EXTENSION unaccent;

CREATE TEXT SEARCH CONFIGURATION mydict ( COPY = simple );
ALTER TEXT SEARCH CONFIGURATION mydict
  ALTER MAPPING FOR hword, hword_part, word
  WITH unaccent, simple;

Che puoi quindi indicizzare con un indice funzionale,

-- Just some sample data...
CREATE TABLE myTable ( myCol )
  AS VALUES ('fóó bar baz'),('qux quz');

-- No index required, but feel free to create one
CREATE INDEX ON myTable
  USING GIST (to_tsvector('mydict', myCol));

Ora puoi interrogarlo in modo molto semplice

SELECT *
FROM myTable
WHERE to_tsvector('mydict', myCol) @@ 'foo & bar'

    mycol    
-------------
 fóó bar baz
(1 row)

Guarda anche

Unaccent di per sé.

Il unaccentmodulo può anche essere utilizzato da solo senza integrazione FTS, per questo controlla la risposta di Erwin


2

Sono abbastanza sicuro che PostgreSQL si basi sul sistema operativo sottostante per le regole di confronto. Essa non supporta la creazione di nuove regole di confronto , e la personalizzazione di regole di confronto . Non sono sicuro di quanto lavoro potrebbe essere per te, però. (Potrebbe essere abbastanza.)


1
Il nuovo supporto per le regole di confronto è attualmente sostanzialmente limitato a wrapper e alias per le impostazioni locali del sistema operativo. È molto semplice. Non c'è supporto per funzioni di filtro, comparatori personalizzati o qualsiasi cosa di cui avresti bisogno per vere regole di confronto personalizzate.
Craig Ringer
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.