Ajoutons une nouvelle colonne de type tsvector
:
alter table sites add column tsvector tsvector;
Maintenant, nous allons créer un déclencheur qui permettra de recueillir lexèmes, les organiser et de mettre à notre tsvector. Nous allons utiliser 4 groupes (A, B, C, D) - ceci est une fonction spéciale de tsvector qui vous permet de distinguer les lexèmes plus tard, au moment de la recherche (voir les exemples dans le manuel https://www.postgresql.org/docs/current/static/textsearch-controls.html) malheureusement cette fonction ne prend en charge que 4 groupes développeurs réservé seulement 2 bits pour cela, mais nous avons la chance ici, nous avons besoin de seulement 4 groupes):
create or replace function t_sites_tsvector() returns trigger as $$
declare
dic regconfig;
part_a text;
part_b text;
part_c text;
part_d text;
begin
dic := 'simple'; -- change if you need more advanced word processing (stemming, etc)
part_a := coalesce(new.doc->>'identification', '') || ' ' || coalesce(new.doc->>'title', '') || ' ' || coalesce(new.doc->>'address', '');
select into part_b string_agg(coalesce(a, ''), ' ') || ' ' || string_agg(coalesce(b, ''), ' ')
from (
select
jsonb_array_elements((new.doc->'buildings'))->>'identification',
jsonb_array_elements((new.doc->'buildings'))->>'name'
) _(a, b);
select into part_c string_agg(coalesce(c, ''), ' ')
from (
select jsonb_array_elements(b)->>'identification' from (
select jsonb_array_elements((new.doc->'buildings'))->'deposits'
) _(b)
) __(c);
select into part_d string_agg(coalesce(d, ''), ' ')
from (
select jsonb_array_elements(c)->>'sample_id'
from (
select jsonb_array_elements(b)->'audits' from (
select jsonb_array_elements((new.doc->'buildings'))->'deposits'
) _(b)
) __(c)
) ___(d);
new.tsvector := setweight(to_tsvector(dic, part_a), 'A')
|| setweight(to_tsvector(dic, part_b), 'B')
|| setweight(to_tsvector(dic, part_c), 'C')
|| setweight(to_tsvector(dic, part_d), 'D')
;
return new;
end;
$$ language plpgsql immutable;
create trigger t_sites_tsvector
before insert or update on sites for each row execute procedure t_sites_tsvector();
^^ - faire défiler, cet extrait est plus grand qu'il n'y paraît (en particulier vous avez Mac OS w/o ...) scrollbars
Créons index GIN pour speedup requêtes de recherche (logique si vous avez plusieurs lignes - disons, plus de centaines ou des milliers):
create index i_sites_fulltext on sites using gin(tsvector);
Et maintenant, nous insérer quelque chose à vérifier:
insert into sites select 1, '{
"_id": "123",
"type": "Site",
"identification": "Custom ID",
"title": "SITE 1",
"address": "UK, London, Mr Tom''s street, 2",
"buildings": [
{
"uuid": "12312",
"identification": "Custom ID",
"name": "BUILDING 1",
"deposits": [
{
"uuid": "12312",
"identification": "Custom ID",
"audits": [
{
"uuid": "12312",
"sample_id": "SAMPLE ID"
}
]
}
]
}
]
}'::jsonb;
Vérifiez avec select * from sites;
- vous devez voir que tsvector
colonne est remplie avec des données.
Maintenant, nous allons l'interroger:
select * from sites where tsvector @@ to_tsquery('simple', 'sample');
- il doit retourner notre dossier. Dans ce cas, nous cherchons 'sample'
mot et nous ne nous soucions pas du groupe dans lequel il sera trouvé.
Changeons et essayer de ne rechercher que dans le groupe A (« SITE (identification, titre, adresse) » comme vous l'avez décrit):
select * from sites where tsvector @@ to_tsquery('simple', 'sample:A');
- cela doit retourner rien parce que mot 'sample'
se trouve seulement dans le groupe D ("AUDIT (sample_id)"). En effet:
select * from sites where tsvector @@ to_tsquery('simple', 'sample:D');
- nous reviendrons à nouveau notre record. Notez que vous devez utiliser to_tsquery(..)
, pas plainto_tsquery(..)
pour pouvoir adresser 4 groupes. Vous devez donc nettoyer vous-même votre entrée (évitez d'utiliser ou de supprimer des caractères spéciaux tels que &
et |
car ils ont une signification particulière dans les valeurs tsquery
).
Et les bonnes nouvelles est que vous pouvez combiner différents groupes dans une seule requête, comme ceci:
select * from sites where tsvector @@ to_tsquery('simple', 'sample:D & london:A');
L'autre chemin à parcourir (par exemple si vous devez travailler avec plus de 4 groupes) est d'avoir plusieurs tsvectors, chacun assis dans une colonne séparée, les construisent en utilisant une seule requête, créent un index (vous pouvez créer un index unique sur plusieurs colonnes tsvector
) et interrogent des colonnes séparées. C'est similaire à ce que j'ai expliqué ci-dessus, mais peut-être moins efficace.
Espérons que cela aide.
Le premier coup serait de "dénormaliser" le stockage: sacrifiez un peu d'espace de stockage par souci de concision. Extraire les données nécessaires dans des champs séparés: site, construction, dépôt, audit, contenant la contanténation pure des champs nécessaires, c'est-à-dire 'building.identification || ';' || building.title || ';' || building.address' etc (cela peut être fait en utilisant les fonctions de postgres comme valeurs par défaut, ou basé sur un trigger, si vos données sont modifiées). Ensuite, créez des index GIN sur ces champs -> puis construisez vos requêtes de texte intégral correspondantes sur ces champs –
Merci @IlyaDyoshin. J'aime ton idée - j'essaierai de l'expérimenter. – rusllonrails
ou vous pouvez attendre jusqu'à la version 10.0 - où json/jsonb FTS sera citoyen de première classe https://www.postgresql.org/docs/10/static/release-10.html –