Unterstützt PostgreSQL "akzentunempfindliche" Kollatierungen?


98

In Microsoft SQL Server ist es möglich, eine "akzentunempfindliche" Kollatierung (für eine Datenbank, Tabelle oder Spalte) anzugeben, was bedeutet, dass dies für eine Abfrage wie möglich möglich ist

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

eine Reihe mit a finden Joao Namen zu finden.

Ich weiß, dass es möglich ist, Akzente von Zeichenfolgen in PostgreSQL mit der Contrib-Funktion unaccent_string zu entfernen, aber ich frage mich, ob PostgreSQL diese "akzentunempfindlichen" Kollatierungen unterstützt, damit die SELECToben genannten funktionieren.


Siehe diese Antwort zum Erstellen eines FTS-Wörterbuchs mit nicht akzentuiertem: stackoverflow.com/a/50595181/124486
Evan Carroll

Möchten Sie zwischen Groß- und Kleinschreibung unterscheiden oder zwischen Groß- und Kleinschreibung unterscheiden?
Evan Carroll

Antworten:


204

Verwenden Sie dazu das Modul ohne Akzent - das völlig anders ist als das, auf das Sie verlinken.

unaccent ist ein Textsuchwörterbuch, das Akzente (diakritische Zeichen) aus Lexemen entfernt.

Einmal pro Datenbank installieren mit:

CREATE EXTENSION unaccent;

Wenn Sie eine Fehlermeldung erhalten wie:

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

Installieren Sie das Contrib-Paket auf Ihrem Datenbankserver, wie in dieser Antwort beschrieben:

Unter anderem bietet es die Funktion, die unaccent()Sie für Ihr Beispiel verwenden können (wo dies LIKEnicht benötigt wird).

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

Index

Um einen Index für diese Art von Abfrage zu verwenden, erstellen Sie einen Index für den Ausdruck . Postgres akzeptiert jedoch nur IMMUTABLEFunktionen für Indizes. Wenn eine Funktion für dieselbe Eingabe ein anderes Ergebnis zurückgeben kann, kann der Index stillschweigend unterbrochen werden.

unaccent()nur STABLEnichtIMMUTABLE

Leider unaccent()ist nur STABLEnicht IMMUTABLE. Laut diesem Thread zu pgsql-Bugs hat dies drei Gründe:

  1. Dies hängt vom Verhalten eines Wörterbuchs ab.
  2. Es besteht keine fest verdrahtete Verbindung zu diesem Wörterbuch.
  3. Es kommt also auch auf den Strom an search_path, der sich leicht ändern kann.

Einige Tutorials im Web weisen an, nur die Funktionsvolatilität auf zu ändernIMMUTABLE . Diese Brute-Force-Methode kann unter bestimmten Bedingungen brechen.

Andere schlagen eine einfache IMMUTABLEWrapper-Funktion vor (wie ich es selbst in der Vergangenheit getan habe).

Es gibt eine anhaltende Debatte darüber, ob die Variante mit zwei Parametern erstellt werden soll, IMMUTABLE die das verwendete Wörterbuch explizit deklarieren. Lesen Sie hier oder hier .

Eine andere Alternative wäre dieses Modul mit einer IMMUTABLE- unaccent()Funktion von Musicbrainz , die auf Github bereitgestellt wird. Habe es nicht selbst getestet. Ich denke, ich habe eine bessere Idee :

Am besten für jetzt

Dieser Ansatz ist effizienter als andere Lösungen und sicherer .
Erstellen Sie eine IMMUTABLESQL-Wrapper-Funktion, die das Zwei-Parameter-Formular mit einer fest verdrahteten schemaqualifizierten Funktion und einem Wörterbuch ausführt.

Da das Verschachteln einer nicht unveränderlichen Funktion das Inlining von Funktionen deaktivieren würde, basieren Sie auf einer Kopie der ebenfalls deklarierten (gefälschten) C-Funktion IMMUTABLE. Der einzige Zweck besteht darin, im SQL-Funktions-Wrapper verwendet zu werden. Nicht für den alleinigen Gebrauch gedacht.

Die Raffinesse ist erforderlich, da das Wörterbuch in der Deklaration der C-Funktion nicht fest verdrahtet werden kann. (Müsste den C-Code selbst hacken.) Die SQL-Wrapper-Funktion erledigt dies und ermöglicht sowohl Inlining- als auch Ausdrucksindizes.

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

Löschen Sie PARALLEL SAFEbeide Funktionen für Postgres 9.5 oder älter.

publicDies ist das Schema, in dem Sie die Erweiterung installiert haben (dies publicist die Standardeinstellung).

Die explizite Typdeklaration ( regdictionary) schützt vor hypothetischen Angriffen mit überladenen Varianten der Funktion durch böswillige Benutzer.

Früher habe ich mich für eine Wrapper - Funktion auf der Grundlage der STABLEFunktion unaccent()mit dem unaccent Modul ausgeliefert. Diese deaktivierte Funktion Inlining . Diese Version wird zehnmal schneller ausgeführt als die einfache Wrapper-Funktion, die ich zuvor hier hatte.
Und das war schon doppelt so schnell wie die erste Version, die SET search_path = public, pg_tempdie Funktion erweitert hat - bis ich herausfand, dass das Wörterbuch auch schemaqualifiziert werden kann. Dennoch (Postgres 12) aus der Dokumentation nicht allzu offensichtlich.

Wenn Sie nicht über die erforderlichen Berechtigungen zum Erstellen von C-Funktionen verfügen, kehren Sie zur zweitbesten Implementierung zurück: Ein IMMUTABLEFunktionsumbruch um die STABLE unaccent()vom Modul bereitgestellte Funktion:

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;

Schließlich der Ausdrucksindex , um Abfragen schnell zu machen :

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

Denken Sie daran , nach jeder Änderung der Funktion oder des Wörterbuchs Indizes neu zu erstellen, die diese Funktion betreffen, z. B. ein direktes Upgrade der Hauptversion, bei dem keine Indizes neu erstellt werden. Die letzten Hauptversionen hatten alle Updates für das unaccentModul.

Passen Sie Abfragen an den Index an (damit der Abfrageplaner ihn verwendet):

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

Sie brauchen die Funktion nicht im richtigen Ausdruck. Dort können Sie auch Zeichenfolgen ohne Akzent wie 'Joao'direkt angeben .

Die schnellere Funktion übersetzt nicht in viel schnellere Abfragen unter Verwendung des Ausdrucksindex . Das arbeitet mit vorberechneten Werten und ist schon sehr schnell. Aber Indexpflege und Abfragen, die den Indexvorteil nicht nutzen.

Die Sicherheit für Client-Programme wurde mit Postgres 10.3 / 9.6.8 usw. verschärft. Sie müssen die Funktion und den Wörterbuchnamen für das Schema qualifizieren, wie bei der Verwendung in Indizes gezeigt. Sehen:

Ligaturen

In Postgres 9.5 oder älteren müssen Ligaturen wie 'Œ' oder 'ß' manuell erweitert werden (falls erforderlich), da unaccent()immer ein einzelner Buchstabe ersetzt wird:

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

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

Sie werden dieses Update lieben, um in Postgres 9.6 nicht zu akzentuieren :

Erweitern Sie contrib/unaccentdie Standarddatei unaccent.rules, um alle Unicode bekannten Diakritika zu verarbeiten, und erweitern Sie die Ligaturen korrekt (Thomas Munro, Léonard Benedetti).

Meine kühne Betonung. Jetzt bekommen wir:

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

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

Mustervergleich

Kombinieren Sie dies für LIKEoder ILIKEmit beliebigen Mustern mit dem Modul pg_trgmin PostgreSQL 9.1 oder höher. Erstellen Sie einen Trigramm-GIN- (normalerweise vorzuziehen) oder GIST-Ausdrucksindex. Beispiel für GIN:

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

Kann für Abfragen wie verwendet werden:

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

Die Pflege von GIN- und GIST-Indizes ist teurer als die von Btree:

Es gibt einfachere Lösungen für nur links verankerte Muster. Weitere Informationen zu Mustervergleich und Leistung:

pg_trgmbietet auch nützliche Operatoren für "Ähnlichkeit" ( %) und "Entfernung" ( <->) .

Trigram-Indizes unterstützen auch einfache reguläre Ausdrücke mit ~et al. und Muster, bei denen die Groß- und Kleinschreibung nicht berücksichtigt wird ILIKE:


Werden in Ihrer Lösung Indizes verwendet oder muss ich einen Index für erstellen unaccent(name)?
Daniel Serodio

@ErwinBrandstetter In psql 9.1.4 erhalte ich "Funktionen im Indexausdruck müssen als IMMUTABLE markiert sein", da die nicht akzentuierte Funktion STABLE anstelle von INMUTABLE ist. Was empfehlen Sie?
e3matheus

1
@ e3matheus: Ich fühlte mich schuldig, weil ich die vorherige Lösung, die ich bereitgestellt hatte, nicht getestet hatte. Ich untersuchte und aktualisierte meine Antwort mit einer neuen und besseren (IMHO) Lösung für das Problem als die bisherige.
Erwin Brandstetter

Ist die Zusammenstellung nicht utf8_general_cidie Antwort auf diese Art von Problemen?
Med

5
Ihre Antworten sind so gut wie die Postgres-Dokumentation: phänomenal!
Elektrotyp

6

Nein, PostgreSQL unterstützt keine Kollatierungen in diesem Sinne

PostgreSQL unterstützt solche Kollatierungen nicht (akzentunempfindlich oder nicht), da kein Vergleich gleich zurückgeben kann, es sei denn, die Dinge sind binär gleich. Dies liegt daran, dass es intern viele Komplexitäten für Dinge wie einen Hash-Index mit sich bringen würde. Aus diesem Grund wirken sich Kollatierungen im engeren Sinne nur auf die Reihenfolge und nicht auf die Gleichheit aus.

Problemumgehungen

Volltextsuchwörterbuch, das Lexeme nicht akzentuiert.

Für FTS können Sie Ihr eigenes Wörterbuch definieren mit 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;

Was Sie dann mit einem Funktionsindex indizieren können,

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

Sie können es jetzt ganz einfach abfragen

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

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

Siehe auch

An sich nicht akzentuiert.

Das unaccentModul kann auch ohne FTS-Integration alleine verwendet werden. Überprüfen Sie dazu Erwins Antwort


2

Ich bin mir ziemlich sicher, dass PostgreSQL für die Sortierung auf dem zugrunde liegenden Betriebssystem basiert. Es wird unterstützt neue Sortierungen zu schaffen , und Sortierungen Customizing . Ich bin mir jedoch nicht sicher, wie viel Arbeit das für Sie sein könnte. (Könnte ziemlich viel sein.)


1
Die Unterstützung für neue Sortierungen ist derzeit im Wesentlichen auf Wrapper und Aliase für Betriebssystem-Gebietsschemas beschränkt. Es ist sehr einfach. Es gibt keine Unterstützung für Filterfunktionen, benutzerdefinierte Komparatoren oder alles, was Sie für echte benutzerdefinierte Kollatierungen benötigen.
Craig Ringer
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.