¿PostgreSQL admite intercalaciones "insensibles al acento"?

98

En Microsoft SQL Server, es posible especificar una intercalación "insensible al acento" (para una base de datos, tabla o columna), lo que significa que es posible para una consulta como

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

para encontrar una fila con un Joaonombre.

Sé que es posible quitar los acentos de las cadenas en PostgreSQL usando la función contrib unaccent_string , pero me pregunto si PostgreSQL admite estas intercalaciones "insensibles al acento" para que lo SELECTanterior funcione.

Daniel Serodio
fuente
Vea esta respuesta para crear un diccionario FTS con unaccent: stackoverflow.com/a/50595181/124486
Evan Carroll
¿Quieres búsquedas con distinción entre mayúsculas y minúsculas?
Evan Carroll

Respuestas:

204

Use el módulo de unccent para eso, que es completamente diferente de lo que está vinculando.

unaccent es un diccionario de búsqueda de texto que elimina los acentos (signos diacríticos) de los lexemas.

Instale una vez por base de datos con:

CREATE EXTENSION unaccent;

Si recibe un error como:

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

Instale el paquete contrib en su servidor de base de datos como se indica en esta respuesta relacionada:

Entre otras cosas, proporciona la función unaccent()que puede usar con su ejemplo (donde LIKEparece que no es necesaria).

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

Índice

Para usar un índice para ese tipo de consulta, cree un índice en la expresión . Sin embargo , Postgres solo acepta IMMUTABLEfunciones para índices. Si una función puede devolver un resultado diferente para la misma entrada, el índice podría romperse silenciosamente.

unaccent()solo que STABLEnoIMMUTABLE

Desafortunadamente, unaccent()es solo STABLE, no IMMUTABLE. Según este hilo sobre pgsql-bugs , esto se debe a tres razones:

  1. Depende del comportamiento de un diccionario.
  2. No hay una conexión cableada a este diccionario.
  3. Por tanto, también depende de la corriente search_path, que puede cambiar fácilmente.

Algunos tutoriales en la web indican simplemente modificar la volatilidad de la función IMMUTABLE. Este método de fuerza bruta puede romperse en determinadas condiciones.

Otros sugieren una función de envoltura simpleIMMUTABLE (como hice yo mismo en el pasado).

Hay un debate en curso sobre si hacer la variante con dos parámetros IMMUTABLE que declare explícitamente el diccionario utilizado. Leer aquí o aquí .

Otra alternativa sería este módulo con función INMUTABLE unaccent()de Musicbrainz , proporcionado en Github. No lo he probado yo mismo. Creo que se me ocurrió una idea mejor :

Mejor por ahora

Este enfoque es más eficiente que otras soluciones flotantes y más seguro .
Cree una IMMUTABLEfunción contenedora de SQL ejecutando el formulario de dos parámetros con una función y un diccionario calificados por esquema cableado.

Dado que anidar una función no inmutable deshabilitaría la inserción de funciones, basarlo en una copia de la función C, (falsa) declarada IMMUTABLEtambién. Su único propósito es ser utilizado en el contenedor de funciones SQL. No está diseñado para usarse solo.

La sofisticación es necesaria ya que no hay forma de cablear el diccionario en la declaración de la función C. (Requeriría piratear el propio código C). La función de envoltura SQL hace eso y permite tanto la función de inserción como los índices de expresión.

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$;

Elimine PARALLEL SAFEambas funciones para Postgres 9.5 o versiones anteriores.

publicsiendo el esquema donde instaló la extensión ( publices el predeterminado).

La declaración de tipo explícita ( regdictionary) defiende contra ataques hipotéticos con variantes sobrecargadas de la función por parte de usuarios malintencionados.

Anteriormente, abogué por una función de envoltura basada en la STABLEfunción unaccent()enviada con el módulo unaccent. Esa función deshabilitada en línea . Esta versión se ejecuta diez veces más rápido que la simple función de contenedor que tenía aquí antes.
Y eso ya era dos veces más rápido que la primera versión que se agregó SET search_path = public, pg_tempa la función, hasta que descubrí que el diccionario también puede ser calificado por esquema. Aún así (Postgres 12) no es demasiado obvio por la documentación.

Si no tiene los privilegios necesarios para crear funciones en C, está de vuelta a la segunda mejor implementación: una IMMUTABLEfunción que envuelve la STABLE unaccent()función proporcionada por el módulo:

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;

Por último, el índice de expresión para realizar consultas rápidas :

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

Recuerde volver a crear índices que incluyan esta función después de cualquier cambio en la función o el diccionario, como una actualización de versión importante en el lugar que no recrearía índices. Todas las versiones principales recientes tenían actualizaciones para el unaccentmódulo.

Adapte las consultas para que coincidan con el índice (para que el planificador de consultas las utilice):

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

No necesita la función en la expresión correcta. Allí también puede suministrar cadenas sin acento como 'Joao'directamente.

La función más rápida no se traduce en consultas mucho más rápidas utilizando el índice de expresión . Eso opera con valores precalculados y ya es muy rápido. Pero el mantenimiento del índice y las consultas no utilizan el beneficio del índice.

La seguridad de los programas cliente se ha reforzado con Postgres 10.3 / 9.6.8, etc. Necesita calificar el esquema de la función y el nombre del diccionario como se demuestra cuando se usa en cualquier índice. Ver:

Ligaduras

En Postgres 9.5 o anteriores, las ligaduras como 'Œ' o 'ß' deben expandirse manualmente (si es necesario), ya que unaccent()siempre sustituye una sola letra:

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

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

Le encantará esta actualización a unaccent en Postgres 9.6 :

Extienda contrib/unaccentel unaccent.rulesarchivo estándar para manejar todos los diacríticos conocidos por Unicode y expanda las ligaduras correctamente (Thomas Munro, Léonard Benedetti)

El énfasis audaz es mío. Ahora obtenemos:

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

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

La coincidencia de patrones

Para LIKEo ILIKEcon patrones arbitrarios, combine esto con el módulo pg_trgmen PostgreSQL 9.1 o posterior. Cree un trigrama GIN (normalmente preferible) o un índice de expresión GIST. Ejemplo de GIN:

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

Se puede utilizar para consultas como:

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

Los índices GIN y GIST son más costosos de mantener que los btree simples:

Hay soluciones más simples para patrones anclados a la izquierda. Más sobre la coincidencia de patrones y el rendimiento:

pg_trgmtambién proporciona operadores%<-> útiles para "similitud" ( ) y "distancia" ( ) .

Los índices de trigram también admiten expresiones regulares simples con ~et al. y coincidencia de patrones sin distinción entre mayúsculas y minúsculas con ILIKE:

Erwin Brandstetter
fuente
En su solución, ¿se utilizan índices o debería crear un índice unaccent(name)?
Daniel Serodio
@ErwinBrandstetter En psql 9.1.4, obtengo "las funciones en la expresión de índice deben marcarse como INMUTABLE", debido a que la función inacentuada es ESTABLE, en lugar de INMUTABLE. ¿Que recomiendas?
e3matheus
1
@ e3matheus: Sintiéndome culpable por no haber probado la solución anterior que proporcioné, investigué y actualicé mi respuesta con una solución nueva y mejor (en mi humilde opinión) para el problema que la que está flotando hasta ahora.
Erwin Brandstetter
¿No es la colación utf8_general_cila respuesta para este tipo de problemas?
Med.
5
Sus respuestas son tan buenas como la documentación de Postgres: ¡fenomenal!
electrotipo
6

No, PostgreSQL no admite intercalaciones en ese sentido

PostgreSQL no admite intercalaciones como esa (insensible al acento o no) porque ninguna comparación puede devolver igual a menos que las cosas sean iguales en binario. Esto se debe a que internamente introduciría muchas complejidades para cosas como un índice hash. Por esta razón, las colaciones en su sentido más estricto solo afectan el orden y no la igualdad.

Soluciones alternativas

Diccionario de búsqueda de texto completo que no utiliza lexemas.

Para FTS, puede definir su propio diccionario 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;

Que luego puede indexar con un índice funcional,

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

Ahora puede consultarlo de manera muy simple

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

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

Ver también

Sin acento por sí mismo.

El unaccentmódulo también se puede usar solo sin integración FTS, para eso, consulte la respuesta de Erwin

Evan Carroll
fuente
2

Estoy bastante seguro de que PostgreSQL se basa en el sistema operativo subyacente para la intercalación. Que no admite la creación de nuevas colaciones , y la personalización de las intercalaciones . Sin embargo, no estoy seguro de cuánto trabajo podría ser para ti. (Podría ser bastante).

Mike Sherrill 'Cat Recall'
fuente
1
El nuevo soporte de intercalación está actualmente básicamente limitado a envoltorios y alias para las configuraciones regionales del sistema operativo. Es muy básico. No hay soporte para funciones de filtro, comparadores personalizados ni nada de lo que necesitaría para verdaderas intercalaciones personalizadas.
Craig Ringer