RE:NODE

Fulltextové vyhledávání v PostgreSQL: tsvector, GIN

Text: Tým RE:NODE
17 min čtení


PostgreSQL umí prohledávat text bez Elasticsearch i bez jakéhokoli jiného samostatného systému. Každý dokument převedeš na tsvector (seřazený seznam normalizovaných slov s pozicemi), text z vyhledávacího pole na tsquery, porovnáš je operátorem @@, tsvector zaindexuješ pomocí GIN, aby porovnání nemuselo číst každý řádek, a nalezené řádky seřadíš podle ts_rank. Pro blog, nápovědu, katalog zboží nebo interní wiki s řádově miliony záznamů to stačí, a navíc to žije ve stejných transakcích, ve stejných zálohách a za stejným připojovacím řetězcem jako zbytek tvých dat. Tento návod ho krok za krokem postaví na skutečné tabulce a řekne, kde přestává být tím správným nástrojem.

Vše níže funguje na každé aktuálně podporované verzi PostgreSQL, pokud není uvedeno jinak. Několik funkcí je novějších než ostatní a u nich je to poznamenáno.

Co umí fulltext a LIKE ne#

První, co lidé zkusí, je WHERE body ILIKE '%server%'. Na sto řádcích to funguje a pak to selže třemi způsoby:

  • Nepoužije běžný index. Vzor s divokou kartou na začátku vynutí průchod všemi řádky a přečtení každého bajtu každého textu. Čas roste s velikostí tabulky.
  • Hledá znaky, ne slova. Dotaz run najde prune a brunch, ale nenajde running ani ran.
  • Nezná relevanci. Řádek, který slovo zmiňuje jednou v poznámce pod čarou, dopadne stejně jako řádek, který je celý o něm.

Fulltext řeší všechny tři problémy, protože pracuje s lexémy, ne se znaky. Dokument se rozloží na tokeny, každý token projde slovníkem, který zahodí stop slova (the, and, of) a ostatní zredukuje na kořen (running, runs a ran se nezmění všechny na run stejně - stemmer funguje podle pravidel, takže nepravidelné tvary jako ran obvykle zůstanou stranou - ale running a runs ano), a uloží se výsledek. Dotaz projde stejnou cestou, takže obě strany mají stejnou představu o tom, co je slovo.

Tu proměnu si můžeš prohlédnout přímo:

sql
SELECT to_tsvector('english', 'The quick brown foxes jumped over the lazy dogs');
code
 'brown':3 'dog':9 'fox':4 'jump':5 'lazi':8 'quick':2

Slova jsou seřazená, zkrácená na kmen (foxes se stalo fox, lazy se stalo lazi, což není anglické slovo a být nemusí) a u každého je pozice, na které stálo. the a over zmizela, ale pozice se pro ně přesto započítaly, a právě podle toho později hledání fráze pozná, že dvě slova stála vedle sebe.

Dva typy: tsvector a tsquery#

tsvector je strana dokumentu. tsquery je strana dotazu. Operátor porovnání je @@ a vrací boolean:

sql
SELECT to_tsvector('english', 'PostgreSQL stores documents as lexemes')       @@ to_tsquery('english', 'document & lexeme');

Výsledek je t. Obě strany prošly stemmingem se stejnou konfigurací, takže documents se potkalo s document. Pokud konfigurace smícháš - tsvector postavený s english a dotaz s simple - dostaneš tiché minutí. To je nejčastější důvod, proč vyhledávání „u mě funguje" a v produkci ne. Jak se tomu vyhnout, ukazuje část o jazykových konfiguracích níže.

PostgreSQL nabízí čtyři způsoby, jak postavit tsquery, a rozdíl je hlavně v tom, co se stane, když uživatel do vyhledávacího pole napíše něco divného:

FunkceStyl vstupuKdy ji použít
to_tsqueryOperátory & | ! <-> a :*Dotazy, které píšeš sám
plainto_tsqueryObyčejná slova, vše přes ANDJednoduchá pole bez syntaxe
phraseto_tsqueryObyčejná slova, těsně za sebouPole pro přesnou frázi
websearch_to_tsqueryUvozovky, or, -wordVeřejné vyhledávací pole

to_tsquery je přísná. Předej jí syrový řetězec od uživatele a zatoulaný apostrof nebo nespárovaná závorka vyvolá syntaktickou chybu. Je to správný nástroj, když výraz skládá tvůj kód, a špatný pro vstup od uživatele.

websearch_to_tsquery (PostgreSQL 11 a novější) je ta, kterou patří dát za vyhledávací pole. Na uživatelském vstupu nikdy nevyhodí syntaktickou chybu a rozumí zvyklostem, které lidé znají z webových vyhledávačů:

sql
SELECT websearch_to_tsquery('english', '"dedicated server" -windows or linux');
code
 'dedic' <-> 'server' & !'window' | 'linux'

Fráze v uvozovkách se stane frází (operátor <-> znamená „bezprostředně následuje"), -word se stane negací a or se stane |. Slova bez uvozovek se spojí přes AND.

Hledání podle předpony je vlastnost to_tsquery: to_tsquery('english', 'postgr:*') najde každý lexém začínající na postgr. Je to základ našeptávání při psaní, s jedním upozorněním - předpona neprochází stemmingem tak jako celé slovo, proto si otestuj případy, na kterých ti záleží.

Příklad na tabulce článků#

Vytvoř tabulku a vlož do ní pár řádků. Zastupuje cokoli, v čem chceš hledat:

sql
CREATE TABLE articles (    id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,    title      text NOT NULL,    body       text NOT NULL,    created_at timestamptz NOT NULL DEFAULT now());INSERT INTO articles (title, body) VALUES  ('Backups that restore', 'A backup you have never restored is a hope, not a backup.'),  ('Connection pooling', 'Each connection costs memory. A pooler keeps the count low.'),  ('Choosing an index', 'An index speeds up reads and slows down writes on the table.');

Naivní verze hledání počítá tsvector za běhu:

sql
SELECT id, titleFROM articlesWHERE to_tsvector('english', title || ' ' || body)      @@ websearch_to_tsquery('english', 'restore backup');

Je to správně a je to pomalé, protože PostgreSQL musí při každém dotazu znovu rozebrat a stemovat text každého řádku. Existují dva standardní způsoby, jak toho nechat, a oba končí indexem GIN.

Varianta 1: uložený generovaný sloupec

Od PostgreSQL 12 lze sloupec vypočítat z jiných sloupců a uložit na disk, přičemž se udržuje automaticky:

sql
ALTER TABLE articlesADD COLUMN search tsvectorGENERATED ALWAYS AS (    to_tsvector('english', coalesce(title, '') || ' ' || coalesce(body, ''))) STORED;

coalesce je důležité: spojení hodnoty NULL s čímkoli dává NULL a řádek s prázdným názvem by z vyhledávání úplně zmizel. Výraz musí být neměnný (immutable), proto je název konfigurace napsaný výslovně. Tuto variantu volej jako výchozí. Cena je ta, že tsvector zabírá místo na disku vedle původního textu a přidání sloupce jednou přepíše tabulku.

Na PostgreSQL 11 a starších generované sloupce nejsou; použij sloupec tsvector udržovaný triggerem. PostgreSQL k tomu dodává pomocníka tsvector_update_trigger, popsaného v kapitole manuálu o textovém vyhledávání.

Varianta 2: index nad výrazem, bez dalšího sloupce

Pokud vektor ukládat nechceš, zaindexuj přímo výraz:

sql
CREATE INDEX articles_fts_exprON articles USING gin (to_tsvector('english', title || ' ' || body));

Dotaz pak musí opakovat totožný výraz, aby ho plánovač poznal:

sql
WHERE to_tsvector('english', title || ' ' || body) @@ query

Ušetříš uložený sloupec, ale snadno se to rozejde: změníš výraz na jednom místě a na druhém ne a index se tiše přestane používat. Všimni si také, že index přijímá jen dvouargumentový tvar to_tsvector s výslovně uvedenou konfigurací. Jednoargumentový tvar závisí na nastavení relace default_text_search_config, takže není immutable a PostgreSQL ho odmítne indexovat.

Index GIN#

GIN znamená Generalized Inverted Index. tsvector má na řádek mnoho lexémů, takže užitečná struktura je opačná než tabulka: ke každému lexému seznam řádků, které ho obsahují. Vyhledání backup je pak jediné nahlédnutí, ne průchod tabulkou.

sql
CREATE INDEX articles_search_idx ON articles USING gin (search);

Že se index používá, ověř pomocí EXPLAIN:

sql
EXPLAINSELECT id, title FROM articlesWHERE search @@ websearch_to_tsquery('english', 'backup');

U tabulky o třech řádcích zvolí plánovač sekvenční průchod, ať uděláš cokoli, protože přečíst tři řádky je levnější než sáhnout do indexu. To je správné chování, ne chybějící index. Chceš-li vidět plán s indexem na hračkové tabulce, spusť v relaci SET enable_seqscan = off; a explain zopakuj; hledej Bitmap Index Scan on articles_search_idx. To nastavení nenechávej zapnuté. U tabulky s desítkami tisíc řádků se index zvolí sám.

GIN je správná výchozí volba pro textové vyhledávání. Alternativa, GiST, staví menší index, který se rychleji aktualizuje, ale hůř se v něm hledá a je ztrátový (může vrátit řádky, které se pak znovu ověřují). Rada samotné dokumentace PostgreSQL je GIN pro statická nebo převážně čtená data, a to pokrývá většinu případů. Obecný obraz najdeš v indexech PostgreSQL: typech, pořadí a ceně.

S čím je třeba počítat:

  • Velikost. Index GIN nad textem může tvořit velký díl velikosti samotného textu, někdy víc. Zjistíš to příkazem SELECT pg_size_pretty(pg_relation_size('articles_search_idx'));.
  • Rychlost zápisu. Vložení jednoho dokumentu se dotkne mnoha záznamů indexu, po jednom za každý různý lexém. Aby to bylo mírnější, GIN ukládá nové záznamy do čekacího seznamu a slévá je po dávkách (parametr fastupdate, ve výchozím stavu zapnutý). Velkou tabulku je mnohem rychlejší nejdřív naplnit a index vytvořit až potom.
  • Bloat a údržba. Čekací seznam uklízí autovacuum, takže tabulka s intenzivním zápisem potřebuje, aby autovacuum stíhal. Jak to zkontrolovat, vysvětluje Postgres VACUUM a bloat.

Řazení výsledků pomocí ts_rank#

Booleovská shoda řekne, které řádky vyhovují, ne které ukázat první. ts_rank vrátí pro každou shodu desetinné číslo podle toho, jak často a jak husto se slova dotazu vyskytují:

sql
SELECT id, title,       ts_rank(search, q) AS rankFROM articles,     websearch_to_tsquery('english', 'backup restore') AS qWHERE search @@ qORDER BY rank DESCLIMIT 10;

Když dotaz dáš do FROM jako q, vypočítá se jednou, ne pro každý řádek. Existuje příbuzná funkce ts_rank_cd, která navíc odměňuje slova stojící blízko sebe („cover density"). Je dražší a stojí za zkoušku, když uživatelům záleží na shodě frází.

Dvě omezení je dobré znát předem:

  • Řazení nemůže použít index. Index GIN najde odpovídající řádky; rank se musí spočítat pro každý z nich a pak se seřadí. Pokud běžné slovo odpovídá 400 000 řádkům, řadíš 400 000 řádků. Obvyklé léky jsou zúžit dotaz (víc povinných slov, filtr podle kategorie nebo data) nebo řadit jen množinu kandidátů.
  • Řazení nezohledňuje frekvenci dokumentů. Vestavěné řazení PostgreSQL neví, že jedno slovo je v celém korpusu vzácné a jiné běžné, jak to dělá BM25 ve specializovaných vyhledávačích. Pro většinu aplikací to stačí a je to nejčastější důvod, proč přejít na něco jiného.

Váhy: název má počítat víc než text

Části dokumentu můžeš označit jednou ze čtyř vah, A až D, pomocí setweight. Ve výchozím stavu váhy násobí shodu hodnotou 1.0 pro A, 0.4 pro B, 0.2 pro C a 0.1 pro D. Přestav generovaný sloupec tak, aby názvy měly A a texty B:

sql
ALTER TABLE articles DROP COLUMN search;ALTER TABLE articlesADD COLUMN search tsvectorGENERATED ALWAYS AS (    setweight(to_tsvector('english', coalesce(title, '')), 'A') ||    setweight(to_tsvector('english', coalesce(body, '')),  'B')) STORED;CREATE INDEX articles_search_idx ON articles USING gin (search);

Smazáním sloupce zmizí i starý index, proto se vytváří znovu. Stránka, jejíž název obsahuje hledané slovo, teď předběhne tu, která ho zmiňuje jen letmo. Vlastní váhy můžeš předat jako první argument ts_rank, jako pole v pořadí {D, C, B, A}:

sql
SELECT id, ts_rank('{0.1, 0.2, 0.4, 1.0}', search, q) AS rankFROM articles, websearch_to_tsquery('english', 'backup') AS qWHERE search @@ q;

ts_rank přijímá jako poslední argument i volbu normalizace, bitovou masku. Bez ní dostávají dlouhé dokumenty vyšší skóre prostě proto, že obsahují víc slov. Volba 1 vydělí rank hodnotou 1 + log(délka dokumentu) a 32 převede rank do rozsahu 0 až 1. Lze je kombinovat sčítáním: ts_rank(search, q, 1 | 32).

Zobrazení shody pomocí ts_headline#

Výsledky hledání vypadají mnohem lépe, když ukážeš odpovídající úryvek se zvýrazněnými slovy. Dělá to ts_headline:

sql
SELECT id,       ts_headline('english', body, q,                   'StartSel=<mark>, StopSel=</mark>, MaxWords=25, MinWords=10') AS snippetFROM articles,     websearch_to_tsquery('english', 'backup') AS qWHERE search @@ qORDER BY ts_rank(search, q) DESCLIMIT 10;

Dvě upozornění. ts_headline pracuje s původním textem, ne s tsvector, takže dokument znovu rozebírá; volej ji jen na řádky, které opravdu zobrazíš (těch deset po LIMIT), nikdy na celý výsledek před řazením. A vrací text s tebou zvolenými značkami, takže při stavbě webové stránky si text dokumentu ošetři (escapuj) sám před zvýrazněním: HTML escapování funkce nedělá.

Jazykové konfigurace#

Konfigurace textového vyhledávání je recept, který určuje, jak se text rozkládá na tokeny a které slovníky tokeny zredukují. english je jedna z nich. Seznam těch, které má tvůj server:

bash
$ psql -c '\dF'

nebo z SQL:

sql
SELECT cfgname FROM pg_ts_config ORDER BY cfgname;SHOW default_text_search_config;

Standardní instalace obsahuje konfigurace pro mnoho evropských jazyků (french, german, spanish, russian, italian a další) a konfiguraci simple. Je třeba znát dva druhy mezer:

  • Jazyky bez vestavěné konfigurace. Čeština a gruzínština, abychom jmenovali dvě, ve standardním seznamu nejsou. Bez ní použij simple, která převede slova na malá písmena a nechá je celá, bez stemmingu a bez stop slov. Dává to jen přesnou shodu slov, takže server nenajde servers. Plnější podpora znamená přidat slovník (třeba Ispell nebo Hunspell pro češtinu) a postavit na něm vlastní konfiguraci, což vyžaduje soubory na disku databázového serveru a na spravovaném hostingu to obvykle nejde.
  • Diakritika. café a cafe jsou ve výchozím stavu různé lexémy. Rozšíření unaccent to napraví, pokud ho můžeš vytvořit.

Volba jazyka je skutečné návrhové rozhodnutí a musí sedět na obou stranách. Nejlevnější cesta k souladu je uvádět konfiguraci výslovně v každém volání, nikdy se nespoléhat na výchozí hodnotu relace a mít ji na jednom místě, třeba v generovaném sloupci a v jedné pomocné funkci dotazu.

Dokumenty ve více jazycích

Pokud jsou řádky v různých jazycích, ukládej konfiguraci ke každému řádku. Typ je regconfig:

sql
ALTER TABLE articles ADD COLUMN lang regconfig NOT NULL DEFAULT 'english';-- a generated column that uses the row's own languageALTER TABLE articlesADD COLUMN search tsvector GENERATED ALWAYS AS (    to_tsvector(lang, coalesce(title, '') || ' ' || coalesce(body, ''))) STORED;

To je platné, protože argument regconfig je neměnný. Při dotazu stemuješ hledaná slova jazykem, ve kterém uživatel hledá, a ten bývá znám z rozhraní, ne z obsahu. Stemování dotazu ve špatném jazyce dává stejné tiché minutí jako míchání konfigurací.

Ignorování diakritiky vlastní konfigurací

Vytvoř rozšíření, zkopíruj vestavěnou konfiguraci a vlož unaccent před stemmer:

sql
CREATE EXTENSION IF NOT EXISTS unaccent;CREATE TEXT SEARCH CONFIGURATION english_unaccent (COPY = english);ALTER TEXT SEARCH CONFIGURATION english_unaccent    ALTER MAPPING FOR hword, hword_part, word    WITH unaccent, english_stem;

Teď to_tsvector('english_unaccent', 'Café') a dotaz cafe dají stejný lexém. Pokud je CREATE EXTENSION odmítnuto chybou oprávnění, role, pod kterou jsi připojený, ho instalovat nesmí; na hostované databázi si před návrhem na něm zjisti, která rozšíření tvůj tarif dovoluje. Kdo co smí, vysvětlují role a oprávnění v PostgreSQL.

Proč hledání nic nenajde#

Vestavěné ladicí funkce odpoví na většinu otázek typu „proč to nesedí":

sql
SELECT * FROM ts_debug('english', 'The quick brown foxes');SELECT to_tsvector('english', 'Backups') @@ to_tsquery('english', 'backup');SELECT ts_lexize('english_stem', 'foxes');SELECT * FROM ts_stat('SELECT search FROM articles') ORDER BY ndoc DESC LIMIT 20;

Každá z nich odpovídá na jinou otázku:

  • ts_debug ukáže každý token, které pravidlo parseru ho vytvořilo, který slovník ho zpracoval a jaký lexém vyšel.
  • ts_lexize pošle jedno slovo jedním slovníkem. Prázdný výsledek znamená, že je to v tom slovníku stop slovo.
  • ts_stat vypíše lexémy ve sloupci a kolik mají dokumentů a výskytů. Hodí se k nalezení slov tak běžných, že by se měla brát jako stop slova, a k odhalení nesmyslných tokenů, třeba dlouhých hex řetězců nebo kusů URL, které index nafukují.

Časté příčiny prázdného výsledku, zhruba podle četnosti:

  1. tsvector byl postaven s jednou konfigurací a dotaz s jinou.
  2. Hledané slovo je v tom jazyce stop slovo, takže se dotaz zredukuje na nic. to_tsquery('english', 'the') vytvoří prázdný dotaz s upozorněním a prázdný dotaz nenajde žádné řádky.
  3. Hledání části slova bez :*. postgr není postgres.
  4. Sloupec tsvector je NULL, protože zdrojové sloupce byly NULL a chybělo coalesce.
  5. Sloupec se změnil, ale tsvector se nepřestavěl (ručně udržovaný sloupec bez svého triggeru).

Vyhledávání na malém serveru#

Fulltext je paměťově náročný zvláštním způsobem: dotazy do GIN jsou rychlé, když je index v cache, a znatelně pomalejší, když se čte z disku. Na malé instanci záleží na čísle, které porovnává velikost indexu s RAM, která zbude po zbytku pracovní sady. Databáze na tarifu s 2 GB a vyhledávací index o velikosti 600 MB je pohodlná; index o 3 GB na stejném tarifu se bude z velké části číst z disku. Velikost zkontroluj před přidáním vyhledávacího sloupce i po něm:

sql
SELECT pg_size_pretty(pg_total_relation_size('articles')) AS table_and_indexes,       pg_size_pretty(pg_relation_size('articles_search_idx')) AS gin_index;

Ladění shared_buffers a work_mem je v ladění PostgreSQL pro malé servery. Pro vyhledávání platí, že nejrychlejší výhry jsou indexovat jen sloupce, ve kterých se opravdu hledá, zahodit váhy a sloupce, které nepoužíváš, a dělat dotazy selektivnější, aby se řadilo méně řádků.

Na RE:NODE začínají tarify databáze PostgreSQL od $6 měsíčně na stránce hostingu databází a host, port, uživatel i heslo se vygenerují při vytvoření serveru. Připojíš se z psql nebo z aplikace přesně jako v návodu připojení k PostgreSQL: začínáme a všechny příkazy z tohoto návodu spustíš tak, jak jsou napsané. Vyhledávací sloupec a jeho index GIN jsou obyčejná data tabulky, takže jsou součástí záložních slotů tvého tarifu i dumpu. Než na rozšíření jako unaccent něco postavíš, spusť CREATE EXTENSION na vlastní databázi a zjisti, jestli je povolené; nespoléhej se na tento návod. Index se při obnově dumpu přestaví automaticky, což je další důvod mít vyzkoušený postup obnovy - viz zálohy a obnova databází.

Kdy vyhledávání v PostgreSQL nestačí#

Vestavěné vyhledávání je silná výchozí volba a poctivý návod musí říct, kde končí:

  • Překlepy a přibližná shoda. Fulltext porovnává lexémy přesně. Pro chování „mysleli jste" a toleranci překlepů přidej rozšíření pg_trgm, které indexuje tříznakové fragmenty (také přes GIN) a podporuje řazení podle podobnosti a rychlé ILIKE '%fragment%'. Některé aplikace používají obojí: trigramy pro krátká pole jako jména, fulltext pro dlouhé texty.
  • Ladění relevance. Bez skórování podle frekvence dokumentů a bez zvýhodnění polí nad rámec čtyř vah nelze řazení jemně doladit, jak to dovoluje specializovaný vyhledávač.
  • Fasetové počty a velmi velké korpusy. Počítání shod podle kategorií přes mnoho milionů řádků je pomalé, protože se musí projít každá shoda.
  • Silný vyhledávací provoz soutěžící s aplikací. Vyhledávání a tvoje transakční dotazy sdílejí stejný CPU a paměť. Pokud zátěž z vyhledávání začne škodit zbytku aplikace, je čas je oddělit.

Pokud je otázka, jestli je PostgreSQL vůbec správná databáze pro začátek, porovnávají možnosti jakou databázi zvolit a MySQL vs PostgreSQL. Pokud jsou tvá data většinou polostrukturované dokumenty, lze PostgreSQL JSONB kombinovat s fulltextem: tsvector sestav z payload ->> 'title' a podobných výrazů.

Kontrolní seznam pro novou funkci vyhledávání#

  1. Vyber konfiguraci (english, nebo jinou, která odpovídá tvým datům) a zapiš si ji jednou.
  2. Přidej uložený generovaný sloupec tsvector s setweight u důležitých polí a coalesce u každého pole, které může být NULL.
  3. Vytvoř na tomto sloupci index GIN.
  4. Dotazuj se přes websearch_to_tsquery se stejným názvem konfigurace.
  5. Řaď podle ts_rank a omez výsledek přes LIMIT; ts_headline volej jen na výsledné řádky.
  6. Plán zkontroluj pomocí EXPLAIN na realistickém množství dat, ne na třech řádcích.
  7. Zkontroluj velikost indexu a sleduj autovacuum u tabulek s intenzivním zápisem.

FAQ#

Stačí fulltext v PostgreSQL pro produkci?

Ano, pro vyhledávání v katalogu, dokumentaci, nápovědě a blogu až do několika milionů řádků. Není navržený na toleranci překlepů ani na pokročilé ladění relevance. Mnoho produkčních systémů na něm běží roky a samostatný vyhledávač přidá, až když narazí na jeden z výše popsaných limitů.

Jaký je rozdíl mezi tsvector a tsquery?

tsvector je uložená, zpracovaná podoba dokumentu: seřazené lexémy s pozicemi. tsquery je zpracovaná podoba hledání: lexémy spojené operátory jako AND, OR, NOT a „následuje". Operátor @@ testuje, jestli vektor dotazu vyhovuje.

Proč můj fulltextový dotaz nepoužívá index GIN?

Obvyklé důvody jsou tabulka příliš malá na to, aby se index vyplatil, výraz v dotazu odlišný od výrazu v indexu, tsvector postavený s jinou konfigurací než dotaz, nebo jednoargumentový tvar to_tsvector. Spusť EXPLAIN na tabulce s realistickým počtem řádků a porovnej výrazy znak po znaku.

Použít generovaný sloupec, nebo trigger?

Na PostgreSQL 12 a novějším použij uložený generovaný sloupec: je kratší, nejde obejít zapomenutou cestou v kódu a při obnově tabulky se přestaví sám. Trigger je záloha pro starší verze nebo pro případ, kdy se musí vektor skládat z jiných tabulek.

Funguje fulltext pro češtinu, gruzínštinu nebo ruštinu?

Pro ruštinu je vestavěná konfigurace. Pro češtinu a gruzínštinu ve standardní instalaci není, takže můžeš použít simple pro přesnou shodu slov, nebo nainstalovat slovník a postavit vlastní konfiguraci tam, kde to hosting dovoluje. Zkoušej na skutečných slovech ze svých dat, protože kvalita stemmingu se podle jazyka liší.

Jak udělat, aby hledání ignorovalo diakritiku a velká písmena?

Velikost písmen se ignoruje už teď, protože lexémy se převádějí na malá písmena. Pro diakritiku nainstaluj rozšíření unaccent a postav konfiguraci, která tokeny před stemmingem pošle přes něj, jak je ukázáno výše, a pak ten název konfigurace použij ve sloupci i v dotazu.

Pomohlo vám to?