web-dev-qa-db-fra.com

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

Dans Microsoft SQL Server, il est possible de spécifier un classement "insensible à l'accent" (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 trouver une ligne avec un nom Joao.

Je sais qu'il est possible de supprimer les accents des chaînes dans PostgreSQL en utilisant la fonction naccent_string contrib, mais je me demande si PostgreSQL supporte ces classements "insensibles aux accents" pour que le SELECT ci-dessus fonctionne .

81
Daniel Serodio

Utilisez le module non accentué pour cela - qui est complètement différent de ce que vous liez.

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

Installez une fois par base de données avec:

CREATE EXTENSION unaccent;

Si vous obtenez une erreur comme:

ERREUR: impossible d'ouvrir le fichier de contrôle d'extension "/usr/share/postgresql/9.x/extension/unaccent.control": aucun fichier ou répertoire de ce type

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 unaccent() que vous pouvez utiliser avec votre exemple (où LIKE ne 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 fonctions IMMUTABLE pour les index. Si une fonction peut renvoyer un résultat différent pour la même entrée, l'index peut se rompre en silence.

unaccent() uniquement STABLE pas IMMUTABLE

Malheureusement, unaccent() n'est que STABLE, pas 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 search_path Actuel, qui peut changer facilement.

Certains tutoriels sur le Web indiquent de modifier simplement la volatilité de la fonction en IMMUTABLE. Cette méthode de force brute peut se briser dans certaines conditions.

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

Il y a un débat en cours pour savoir si faire la variante avec deux paramètresIMMUTABLE qui déclare 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 que j'ai trouvé un meilleure idée:

Meilleur pour l'instant

Je propose une approche qui est au moins aussi efficace que d'autres solutions flottantes, mais plus sûre : créer une fonction wrapper avec la forme à deux paramètres et "hard" -wire "le schéma de la fonction et du dictionnaire:

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;

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

Auparavant, j'avais ajouté SET search_path = public, pg_temp À la fonction - jusqu'à ce que je découvre que le dictionnaire pouvait également être qualifié de schéma ce qui n'est actuellement pas documenté (p. 10) . Cette version est un peu plus courte et environ deux fois plus rapide dans mes tests sur pg 9.5 et pg 10.

La version mise à jour ne permet toujours pas fonction inlining parce que les fonctions déclarées IMMUTABLE peuvent ne pas appeler de fonctions non immuables dans le corps pour permettre cela. Peu importe pour les performances alors que nous utilisons un index d'expression sur cette fonction IMMUTABLE:

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

La sécurité des programmes clients a été renforcée avec Postgres 10.3/9.6.8, etc. Vous avez besoin pour la fonction qualificative de schéma et le dictionnaire, comme illustré dans les index. Voir:

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

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

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

Ligatures

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

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

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

Vous allez adorer cette mise à jour non accentuée dans Postgres 9.6 :

Étendez le fichier contrib/unaccent Standard de unaccent.rules Pour gérer tous les signes diacritiques connus d'Unicode et développez correctement les ligatures (Thomas Munro , Léonard Benedetti)

Accentuation sur moi. Maintenant, nous obtenons:

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

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

Correspondance de motifs

Pour LIKE ou ILIKE avec des modèles arbitraires, combinez cela avec le module pg_trgm dans 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 comme:

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

Les index GIN et Gist sont plus chers à entretenir que btree ordinaire:

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

pg_trgm Fournit également des informations utiles opérateurs pour "similitude" (%) Et "distance" (<->) .

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

174
Erwin Brandstetter

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

PostgreSQL ne prend pas en charge les classements comme celui-ci (insensibles à l'accent ou non) car aucune comparaison ne peut retourner égale à moins que les choses soient binaires égales. En effet, en interne, cela introduirait beaucoup de complexités pour des choses comme un index de hachage. Pour cette raison, les classements au sens strict n'affectent que l'ordre et non l'égalité.

Solutions de contournement

Dictionnaire de recherche en texte intégral qui supprime 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

Non accentué par lui-même.

Le module unaccent peut également être utilisé seul sans intégration FTS, pour cette vérification réponse d'Erwin

3
Evan Carroll

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