it-swarm.com.de

PostgreSQL-Volltextsuche in vielen Spalten

Ich benötige einen Rat bei der Suche nach Datensätzen anhand der angegebenen Zeichenfolge.

Suchzeichenfolgen können Werte aus diesen Spalten enthalten. Die Werte in dieser Zeichenfolge müssen nicht unbedingt identisch sein und in der richtigen Reihenfolge angegeben werden. Außerdem fehlen möglicherweise die Werte einiger Spalten in dieser Zeichenfolge.

Beispiel für eine Suchzeichenfolge:

22 Karntner Wien

Und ich bekomme zum Beispiel ein Ergebnis mit Top 5 ähnlichen Rekorden.

Ich denke, ich sollte die Volltextsuche verwenden, aber ich habe keine Erfahrungen damit. Können Sie mir sagen, wie ich vorgehen soll?

6
Denis Stephanov

Ich schlage diesen Ausdruck für Abfrage und Index vor:

SELECT * FROM tbl
WHERE  to_tsvector('simple', f_concat_ws(' ', country, city, street, house_nr, postcode))
    @@ plainto_tsquery('simple', '22 Kärntner Wien');

Beachten Sie die benutzerdefinierte Funktion f_concat_ws() oben. Das liegt daran, dass concat_ws() nur STABLE ist, nicht IMMUTABLE. Sie müssen es erstellen zuerst:

CREATE OR REPLACE FUNCTION f_concat_ws(text, VARIADIC text[])
  RETURNS text LANGUAGE sql IMMUTABLE AS 'SELECT array_to_string($2, $1)';

Es kann als Drop-In-Ersatz für concat_ws() verwendet werden, außer dass nur tatsächliche Textdaten als Eingabe akzeptiert werden (wodurch wir IMMUTABLE ohne Betrug erstellen können effektiv). Detaillierte Erklärung (lesen Sie es!):

Über VARIADIC:

Für viele Spalten ist dies kürzer und schneller. Sie könnten verzichten darauf, aber dann wird die Syntax ziemlich ausführlich (siehe Joanolos Antwort ).

Der dazu passende Index:

CREATE INDEX tbl_adr_fts_idx ON tbl USING GIN (
       to_tsvector('simple', f_concat_ws(' ', country, city, street, house_nr, postcode)));

Sie haben es mit internationalen Adressdaten zu tun, also verwenden Sie nicht die english Textsuchkonfiguration. Stemming macht für Namen wenig Sinn und die meisten Ihrer Beispieldaten sind zunächst nicht einmal Englisch. Verwenden Sie stattdessen die Konfiguration simple. Sie benötigen das Formular mit zwei Parametern - siehe unten.

Verketten Sie die Zeichenfolgen und rufen Sie die teurere Funktion to_tsvector() einmal auf. Verwenden Sie concat_ws(), um mit möglichen NULL-Werten elegant umzugehen. Insgesamt billiger und auch kürzer.

Wie ich bereits kommentiert habe, bietet die Volltextsuche nur eingeschränkte Unterstützung für Fuzzy-Matching, aber es gibt die oft übersehene Funktion des Präfix-Matchings:

Also, wenn Sie nicht sicher sind, ob es 'Kärntner' oder 'Kärnten' ist und ob es 'Straße' ist , 'strasse' oder 'Strabe' (wie in Ihren fehlerhaften Beispieldaten), aber Sie wissen, dass das zweite Wort dem ersten folgt, Sie könnten:

... @@ to_tsquery('simple', '22 & Kärnt:* <-> Stra:* & Wien')

<-> Ist der Suchoperator für Phrasen und erfordert Postgres 9.6 .

Und wenn Sie auch diakritische Zeichen ignorieren möchten ( 'ä' <> 'a'), fügen Sie unaccent() zur Mischung hinzu . Sie können es als separate Funktion verwenden oder als Wörterbuch hinzufügen = zu Ihrer Textsuchkonfiguration. Sie müssen zuerst die Erweiterung installieren ...

Übersicht über die Mustervergleichsoption in typischen Postgres-Installationen:

Joanolo hat bereits einige grundlegende Informationen zu FTS und den Link zum Handbuch für weitere Informationen bereitgestellt.

Adressierung Ihres Kommentars

Ich versuche, diesen Index hinzuzufügen, erhalte aber eine Fehlermeldung:

ERROR: functions in index expression must be marked IMMUTABLE

Es gibt zwei Varianten der Funktion to_tsvector() - siehe "Funktionsüberladung ". Der 1. nimmt nur text, der 2. nimmt regconfig und text. Überzeugen Sie sich selbst:

SELECT proname, provolatile, proargtypes[0]::regtype, proargtypes[1]::regtype
FROM   pg_proc
WHERE  proname = 'to_tsvector';

Nur die zweite ist IMMUTABLE und kann direkt in einem Indexausdruck verwendet werden. 'simple' im obigen Beispiel ist eine Textsuchkonfiguration (regconfig).

Noch wichtiger , mein Versehen: concat_ws() (das ich in meiner ersten Version hatte) ist nur STABLE, nicht IMMUTABLE . Ich habe oben die notwendigen Schritte hinzugefügt.

Verbunden:

7

Stellen wir uns vor, dies ist Ihre Tabelle und einige Daten:

CREATE TABLE t
(
    country text,
    city text,
    street text,
    house_number text,
    post_code text
) ;

INSERT INTO t
VALUES
   ('Österreich', 'Vienna', 'HauptStrasse', '123', '12345'),   
   ('France', 'Paris', 'Rue du Midi', '12A', '01234'),   
   ('España', 'Barcelona', 'Passeig de Gràcia', '32', '08001'),   
   ('United Kingdom', 'London', 'Oxford Street', '20', 'W1D 1AS'),
   ('Nederland', 'Amsterdam', 'Leidsekruisstraat', '6-8', '1017 RH') ;

[HINWEIS: Überprüfen Sie dies unter http://rextester.com/DOJN8533]

Die Möglichkeit, mit PostgreSQL eine Volltextsuche für mehrere Spalten durchzuführen (vorausgesetzt, 'Englisch' ist der Name Ihrer FTS-Konfiguration), besteht in der Verwendung einer Abfrage wie:

SELECT
    *
FROM
    t
WHERE
    (
        to_tsvector('english', coalesce(country, ''))      || 
        to_tsvector('english', coalesce(city, ''))         || 
        to_tsvector('english', coalesce(street, ''))       || 
        to_tsvector('english', coalesce(house_number, '')) ||
        to_tsvector('english', coalesce(post_code, '')) 
    ) @@ plainto_tsquery('english', 'Amsterdam') ;

Das wo clasuse bedeutet:

 (this tsvector = document) @@ /* matches */  (this tsquery = query)

Ein tsvector ist ein spezieller Datentyp, der von PostgreSQL zum Speichern transformierter Daten (z. B. in Kleinbuchstaben; mit herausgenommenen Kommas, mit identifizierten und aufgelisteten Wörtern usw.) über einen Text verwendet wird. Ein tsquery ist eine Möglichkeit, nachzufragen Eigenschaften eines Dokuments (zum Beispiel enthält dieses _und_ das).

Der Operator || Kombiniert tsvectors (sagen wir, er addiert sie ").

Wenn Sie die Dinge beschleunigen möchten, sollten Sie einen Funktionsindex haben, der wie folgt definiert ist:

CREATE INDEX ts_idx 
    ON t USING Gist ( 
    (
        to_tsvector('english', coalesce(country, '')) || 
        to_tsvector('english', coalesce(city, '')) || 
        to_tsvector('english', coalesce(street, '')) || 
        to_tsvector('english', coalesce(house_number, '')) ||
        to_tsvector('english', coalesce(post_code, ''))
    ) 
) ;

Sie müssen die Dokumentation zu Volltextsuche sorgfältig prüfen. Es ist ein bisschen einschüchternd, weil es viele Möglichkeiten gibt, aber es lohnt sich, die Zeit zu verbringen.

Um Ergebnisse zu sortieren, wenn es viele gibt, sollten Sie die Funktion ts_rank Auf ORDER BY Verwenden und dann begrenzen.

3
joanolo