web-dev-qa-db-fra.com

Recherche plein texte PostgreSQL sur plusieurs colonnes

J'ai besoin d'un conseil pour rechercher un enregistrement en fonction de la chaîne spécifiée.

Les chaînes de recherche peuvent contenir des valeurs de ces colonnes. Les valeurs de cette chaîne ne doivent pas être strictement identiques, dans le bon ordre, et les valeurs de certaines colonnes de cette chaîne peuvent également être manquantes.

Exemple de chaîne de recherche:

22 Karntner Wien

Et j'obtiens par exemple le résultat avec les 5 meilleurs enregistrements similaires.

Je pense que je devrais utiliser la recherche en texte intégral, mais je n'ai aucune expérience avec cela. Pouvez-vous me dire comment procéder?

6
Denis Stephanov

Je suggère cette expression pour la requête et l'index:

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

Notez la fonction personnalisée f_concat_ws() ci-dessus. C'est parce que concat_ws() n'est que STABLE pas IMMUTABLE. Vous devez le créer d'abord:

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

Il peut être utilisé comme remplacement direct pour concat_ws() , sauf qu'il n'accepte que les données de texte réelles en entrée (ce qui nous permet de les faire IMMUTABLE sans tricher , effectivement). Explication détaillée (lisez-la!):

À propos de VARIADIC:

Pour beaucoup colonnes, c'est plus court et plus rapide. Vous pourriez vous en passer mais la syntaxe devient plutôt verbeuse (voir réponse de joanolo ).

L'index correspondant pour aller avec ceci:

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

Vous traitez avec des données d'adresse internationales, alors faites pas utilisez la configuration de recherche de texte english. La racine a peu de sens pour names et la plupart de vos exemples de données ne sont même pas en anglais pour commencer. Utilisez plutôt la configuration simple. Vous avez besoin du formulaire avec deux paramètres - voir ci-dessous.

Concatène les chaînes et appelle la fonction la plus chère to_tsvector() une fois. Utilisez concat_ws() pour gérer avec élégance les éventuelles valeurs NULL. Moins cher dans l'ensemble et aussi plus court.

Comme je l'ai commenté, la recherche en texte intégral a un support limité pour la correspondance floue, mais il y a la fonctionnalité souvent négligée de la correspondance de préfixe:

Donc, si vous ne savez pas si c'est 'Kärntner' ou 'Kärnten', et si c'est 'Straße' , 'strasse' ou 'Strabe' (comme dans vos données d'exemple de buggy) mais vous savez que le deuxième Word suit le premier, vous pourriez:

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

<-> Est l'opérateur de recherche de phrases et nécessite Postgres 9.6 .

Et si vous voulez également ignorer les signes diacritiques ( 'ä' <> 'a'), ajoutez unaccent() au mix . Vous pouvez l'utiliser séparément fonction ou vous pouvez l'ajouter comme dictionnaire dans votre configuration de recherche de texte. Vous devez d'abord installer l'extension ...

Aperçu de l'option de correspondance de motifs dans les installations Postgres typiques:

Joanolo a déjà fourni quelques informations de base sur FTS et le lien vers le manuel pour en savoir plus.

Adresser votre commentaire

J'essaye d'ajouter cet index mais il me donne une erreur:

ERROR: functions in index expression must be marked IMMUTABLE

Il existe deux variantes de la fonction to_tsvector() - voir "surcharge de fonction ". Le 1er prend seulement text, le 2e prend regconfig et text. Voir par vous-même:

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

Seul le second est IMMUTABLE et peut être utilisé directement dans une expression d'index. 'simple' dans l'exemple ci-dessus est une configuration de recherche de texte (regconfig).

Plus important encore , ma surveillance: concat_ws() (que j'avais dans ma première version) est seulement STABLE, pas IMMUTABLE. J'ai ajouté les étapes nécessaires ci-dessus.

En relation:

7
Erwin Brandstetter

Imaginons que ce soit votre table et quelques données:

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

[REMARQUE: vérifiez-le à http://rextester.com/DOJN8533]

La façon d'effectuer une recherche en texte intégral sur plusieurs colonnes à l'aide de PostgreSQL (en supposant que "anglais" est le nom de votre configuration FTS), consiste à utiliser une requête comme:

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

Le où clasuse signifie:

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

Un tsvector est un type de données spécial utilisé par PostgreSQL pour stocker des données transformées (par exemple, toutes en minuscules; avec des virgules supprimées, avec des mots identifiés et répertoriés, etc.) concernant un texte. Un tsquery est un moyen de demander les caractéristiques d'un document (par exemple contenant ce _et_ cela).

Le || L'opérateur combine les tsvecteurs (disons qu'il "les additionne").

Si vous voulez accélérer les choses, vous devez avoir un index fonctionnel, défini comme:

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

Vous devez vérifier attentivement la documentation sur Recherche plein texte . C'est un peu intimidant, car il y a beaucoup de possibilités, mais cela vaut la peine de passer du temps.

Pour trier les résultats lorsqu'ils sont nombreux, vous devez utiliser he ts_rank fonction à ORDER BY puis limitez.

3
joanolo