PostgreSQL prend-il en charge les classements «insensibles aux accents»?

98

Dans Microsoft SQL Server, il est possible de spécifier un classement «insensible aux accents» (pour une base de données, une table ou une colonne), ce qui signifie qu'il est possible pour une requête comme

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

pour rechercher une ligne avec un Joaonom.

Je sais qu'il est possible de dépouiller les accents de chaînes dans PostgreSQL en utilisant la unaccent_string fonction contrib, mais je me demande si PostgreSQL prend en charge ces « de insensibles classements accent » de sorte que le SELECTdessus fonctionnerait.

Daniel Serodio
la source
Voir cette réponse pour créer un dictionnaire FTS avec unaccent: stackoverflow.com/a/50595181/124486
Evan Carroll
Voulez-vous des recherches sensibles à la casse ou insensibles à la casse?
Evan Carroll

Réponses:

204

Utilisez le module unaccent pour cela - qui est complètement différent de ce à quoi vous créez un lien.

unaccent est un dictionnaire de recherche de texte qui supprime les accents (signes diacritiques) des lexèmes.

Installer une fois par base de données avec:

CREATE EXTENSION unaccent;

Si vous obtenez une erreur comme:

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

Installez le package contrib sur votre serveur de base de données comme indiqué dans cette réponse connexe:

Entre autres choses, il fournit la fonction que unaccent()vous pouvez utiliser avec votre exemple (là où cela LIKEne semble pas nécessaire).

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

Indice

Pour utiliser un index pour ce type de requête, créez un index sur l'expression . Cependant , Postgres n'accepte que les IMMUTABLEfonctions pour les index. Si une fonction peut renvoyer un résultat différent pour la même entrée, l'index peut être interrompu en silence.

unaccent()seulement STABLEpasIMMUTABLE

Malheureusement, unaccent()c'est seulement STABLEnon IMMUTABLE. Selon ce fil sur pgsql-bugs , cela est dû à trois raisons:

  1. Cela dépend du comportement d'un dictionnaire.
  2. Il n'y a pas de connexion câblée à ce dictionnaire.
  3. Cela dépend donc aussi du courant search_path, qui peut changer facilement.

Certains tutoriels sur le Web demandent simplement de modifier la volatilité de la fonction IMMUTABLE. Cette méthode de force brute peut casser sous certaines conditions.

D'autres suggèrent une simple IMMUTABLEfonction wrapper (comme je l'ai fait moi-même dans le passé).

Il y a un débat en cours sur l'opportunité de créer la variante avec deux paramètres IMMUTABLE qui déclarent explicitement le dictionnaire utilisé. Lisez ici ou ici .

Une autre alternative serait ce module avec une fonction IMMUTABLE unaccent()de Musicbrainz , fournie sur Github. Je ne l'ai pas testé moi-même. Je pense avoir trouvé une meilleure idée :

Meilleur pour le moment

Cette approche est plus efficace que d'autres solutions flottantes et plus sûre .
Créez une IMMUTABLEfonction wrapper SQL exécutant le formulaire à deux paramètres avec une fonction et un dictionnaire qualifiés de schéma câblés.

Étant donné que l'imbrication d'une fonction non immuable désactiverait l'intégration de fonction, basez-la sur une copie de la fonction C, (fausse) également déclarée IMMUTABLE. Son seul but est d'être utilisé dans l'encapsuleur de fonctions SQL. Non destiné à être utilisé seul.

La sophistication est nécessaire car il n'y a aucun moyen de câbler le dictionnaire dans la déclaration de la fonction C. (Nécessiterait de pirater le code C lui-même.) La fonction SQL wrapper fait cela et permet à la fois l'insertion de fonctions et les index d'expression.

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

Abandonnez les PARALLEL SAFEdeux fonctions pour Postgres 9.5 ou plus.

publicétant le schéma dans lequel vous avez installé l'extension ( publicc'est la valeur par défaut).

La déclaration de type explicite ( regdictionary) se défend contre les attaques hypothétiques avec des variantes surchargées de la fonction par des utilisateurs malveillants.

Auparavant, je préconisais une fonction wrapper basée sur la STABLEfonction unaccent()fournie avec le module unaccent. Cette fonction désactivée en ligne . Cette version s'exécute dix fois plus vite que la simple fonction wrapper que j'avais ici plus tôt.
Et c'était déjà deux fois plus rapide que la première version qui a ajouté SET search_path = public, pg_tempà la fonction - jusqu'à ce que je découvre que le dictionnaire peut également être qualifié de schéma. Encore (Postgres 12) pas trop évident d'après la documentation.

Si vous n'avez pas les privilèges nécessaires pour créer des fonctions C, vous êtes de retour à la deuxième meilleure implémentation: Un IMMUTABLEwrapper de fonction autour de la STABLE unaccent()fonction fournie par le module:

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;

Enfin, l' index d'expression pour accélérer les requêtes :

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

N'oubliez pas de recréer les index impliquant cette fonction après toute modification de la fonction ou du dictionnaire, comme une mise à niveau majeure sur place qui ne recréerait pas les index. Les versions majeures récentes avaient toutes des mises à jour pour le unaccentmodule.

Adaptez les requêtes pour qu'elles correspondent à l'index (afin que le planificateur de requêtes l'utilise):

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

Vous n'avez pas besoin de la fonction dans la bonne expression. Là, vous pouvez également fournir des chaînes non accentuées comme 'Joao'directement.

La fonction plus rapide ne se traduit pas par des requêtes beaucoup plus rapides utilisant l' index d'expression . Cela fonctionne sur des valeurs pré-calculées et est déjà très rapide. Mais la maintenance d'index et les requêtes n'utilisant pas l'avantage d'index.

La sécurité des programmes clients a été renforcée avec Postgres 10.3 / 9.6.8, etc. Vous devez qualifier le schéma de la fonction et du nom du dictionnaire comme démontré lorsqu'ils sont utilisés dans des index. Voir:

Ligatures

Dans Postgres 9.5 ou des ligatures plus anciennes comme 'Œ' ou 'ß' doivent être développées manuellement (si vous en avez besoin), car unaccent()remplace toujours une seule lettre:

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

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

Vous allez adorer cette mise à jour pour unaccent dans Postgres 9.6 :

Extension contrib/unaccentdu unaccent.rulesfichier standard pour gérer tous les signes diacritiques connus sous Unicode, et développer correctement les ligatures (Thomas Munro, Léonard Benedetti)

Je souligne le mien. Maintenant nous obtenons:

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

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

Correspondance de motif

Pour LIKEou ILIKEavec des modèles arbitraires, combinez cela avec le module pg_trgmde PostgreSQL 9.1 ou version ultérieure. Créez un trigramme GIN (généralement préférable) ou un index d'expression GIST. Exemple pour GIN:

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

Peut être utilisé pour des requêtes telles que:

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

Les index GIN et GIST sont plus chers à maintenir que les simples btree:

Il existe des solutions plus simples pour les motifs uniquement ancrés à gauche. En savoir plus sur la correspondance de modèles et les performances:

pg_trgmfournit également des opérateurs%<-> utiles pour «similarité» ( ) et «distance» ( ) .

Les index trigrammes prennent également en charge les expressions régulières simples avec ~et al. et correspondance de modèle insensible à la casse avec ILIKE:

Erwin Brandstetter
la source
Dans votre solution, des index sont-ils utilisés ou devrais-je créer un index sur unaccent(name)?
Daniel Serodio
@ErwinBrandstetter Dans psql 9.1.4, j'obtiens "les fonctions dans l'expression d'index doivent être marquées IMMUTABLE", car la fonction non accentuée est STABLE, au lieu de INMUTABLE. Que recommandez-vous?
e3matheus
1
@ e3matheus: Se sentant coupable de ne pas avoir testé la solution précédente que j'ai fournie, j'ai étudié et mis à jour ma réponse avec une solution nouvelle et meilleure (IMHO) pour le problème que ce qui circule jusqu'à présent.
Erwin Brandstetter
Le classement n'est-il pas la utf8_general_ciréponse à ce genre de problèmes?
Med
5
Vos réponses sont aussi bonnes que la documentation Postgres: phénoménale!
electrotype
6

Non, PostgreSQL ne prend pas en charge les classements dans ce sens

PostgreSQL ne prend pas en charge les classements comme celui-ci (insensible aux accents ou non) car aucune comparaison ne peut renvoyer égal à moins que les choses soient égales en binaire. En effet, en interne, cela introduirait beaucoup de complexités pour des choses comme un index de hachage. Pour cette raison, les classements dans leur sens le plus strict n'affectent que l'ordre et non l'égalité.

Solutions de contournement

Dictionnaire de recherche en texte intégral qui désaccentue les lexèmes.

Pour FTS, vous pouvez définir votre propre dictionnaire en utilisant 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 vous pouvez ensuite indexer avec un index fonctionnel,

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

Vous pouvez maintenant l'interroger très simplement

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

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

Voir également

Unaccent en soi.

Le unaccentmodule peut également être utilisé seul sans intégration FTS, pour cela, consultez la réponse d'Erwin

Evan Carroll
la source
2

Je suis à peu près sûr que PostgreSQL s'appuie sur le système d'exploitation sous-jacent pour le classement. Il prend en charge la création de nouveaux classements et la personnalisation des classements . Je ne sais pas trop combien de travail cela pourrait vous apporter. (Cela pourrait être beaucoup.)

Mike Sherrill 'Rappel de chat'
la source
1
La prise en charge du nouveau classement est actuellement essentiellement limitée aux wrappers et aux alias pour les paramètres régionaux du système d'exploitation. C'est très basique. Il n'y a pas de prise en charge des fonctions de filtrage, des comparateurs personnalisés ou de tout ce dont vous auriez besoin pour de véritables classements personnalisés.
Craig Ringer