LIKE się nie skaluje
Przy wyszukiwaniu tekstu w SQLite pierwszym odruchem jest zwykle LIKE '%word%'. Działa w małych tabelach i rozpada się w dużych. Żaden indeks tu nie pomoże: SQLite musi przejrzeć każdy wiersz, zamienić go na małe litery i sprawdzić, czy zawiera podciąg. Granice słów, ranking, zapytania z wieloma słowami i dopasowanie po prefiksie musisz zaimplementować sam.
FTS5 to wbudowana odpowiedź. To typ tabeli wirtualnej, który utrzymuje indeks odwrócony nad kolumnami tekstowymi, rozumie mały język zapytań i ustala ranking wyników algorytmem BM25. Jest domyślnie dostępny w SQLite, nie trzeba instalować żadnego rozszerzenia.
Tworzenie tabeli FTS5
Tabelę FTS5 tworzysz przez CREATE VIRTUAL TABLE ... USING fts5(...), wymieniając kolumny tekstowe, które mają być indeksowane:
Warto zauważyć trzy rzeczy. Kolumny nie mają typów: FTS5 traktuje wszystko jak tekst. Operator MATCH stosuje się do nazwy tabeli (posts MATCH ...), a nie do kolumny. A zapytanie nie rozróżnia wielkości liter i jest tokenizowane, więc 'sqlite' znajduje SQLite w każdym z wierszy.
Język zapytań MATCH
MATCH przyjmuje więcej niż jedno słowo. Ciąg zapytania ma własną, prostą gramatykę:
Co robi każde z nich:
'fts5 AND prefix': oba tokeny muszą wystąpić (w dowolnej kolejności, w dowolnym miejscu wiersza).'"keep fts"': dokładna fraza, w tej kolejności.'trig*': wyszukiwanie po prefiksie, pasuje dotrigger,triggers,trigonometry...'index NOT trigger': zawieraindex, nie zawieratrigger.
Możesz też zawęzić wyszukiwanie do jednej kolumny przez column:term, na przykład 'title:sqlite'. Pełna gramatyka obejmuje nawiasy do grupowania i OR dla alternatyw, czyli to, czego można się spodziewać po wyszukiwarce.
Ranking z BM25
FTS5 domyślnie dołącza do każdego wiersza ukrytą kolumnę rank. To wynik trafności BM25: niższe liczby oznaczają lepsze dopasowanie. Sortuj po niej, żeby najtrafniejsze wyniki były na początku:
Chcesz, żeby niektóre kolumny ważyły więcej niż inne? Wywołaj bm25() z wagami, po jednej na kolumnę w kolejności deklaracji:
Pierwszy post wygrywa, bo sqlite występuje w title (waga 10×), a nie tylko w body (waga 1×). Dobierz wagi tak, jak twoja aplikacja naprawdę chce ustalać ranking.
Synchronizacja indeksu
Najprostsza tabela FTS5 przechowuje własną kopię tekstu. To wystarcza dla danych w stylu logów, do których tylko dopisujesz, ale większość aplikacji ma już prawdziwą tabelę i chce, żeby FTS ją śledziło. Czysty wzorzec to tabela FTS z treścią zewnętrzną i trzy wyzwalacze.
content='articles' mówi FTS5, żeby nie przechowywało samego tekstu, tylko pobierało go w razie potrzeby z tabeli articles. Wyzwalacze odzwierciedlają zapisy w indeksie FTS. Teraz articles jest źródłem prawdy, a articles_fts to tylko struktura wyszukiwania obok niej.
Dziwnie wyglądające INSERT INTO articles_fts(articles_fts, ...) VALUES ('delete', ...) to składnia poleceń FTS5, która każe indeksowi usunąć wiersz.
Fragmenty i wyróżnianie
Wyniki wyszukiwania zwykle potrzebują podglądu z wyróżnionymi dopasowanymi słowami. FTS5 ma do tego dwie funkcje:
highlight(table, column_index, open, close)zwraca pełny tekst kolumny z dopasowanymi tokenami otoczonymi znacznikami.snippet(table, column_index, open, close, ellipsis, token_count)zwraca krótki fragment wokół dopasowania.
Indeksy kolumn liczone są od zera w kolejności deklaracji. To elementy, z których buduje się „dopasowane słowa na żółto”, potrzebne w każdym interfejsie wyszukiwania.
Pułapki, o których warto wiedzieć
Kilka rzeczy, na które ludzie się łapią:
MATCHdziała tylko na tabelach FTS. Nie możesz użyćMATCHna zwykłej kolumnie. Jeśli potrzebujesz wyszukiwania w istniejącej tabeli, użyj opisanego wyżej wzorca z treścią zewnętrzną.- Nie zapomnij sortować po
rank. Bez tego FTS5 zwraca wiersze w kolejności przechowywania, która nie ma nic wspólnego z trafnością. - Tokenizery mają znaczenie. Domyślny tokenizer (
unicode61) dzieli tekst na granicach słów Unicode i zamienia na małe litery. Do sprowadzania słów do rdzenia (runpasuje dorunning) użyj tokenizeraporter:USING fts5(body, tokenize='porter'). - FTS5 nie toleruje literówek. Dopasowuje po prefiksie, a nie w sposób przybliżony. Jeśli potrzebujesz podpowiedzi w stylu „czy chodziło ci o...”, to osobna warstwa nad FTS5.
- Tabele bez treści (
content='') są mniejsze, ale tracą dane. Możesz je przeszukiwać, ale nie odzyskasz z nich oryginalnego tekstu, tylko rowid. Przydają się, gdy tekst przechowujesz gdzie indziej.
Dalej: funkcje okna
FTS5 zajmuje się wyszukiwaniem tekstu. Następna strona omawia inny rodzaj zaawansowanych zapytań: funkcje okna, które pozwalają liczyć sumy narastające, rankingi i analizy w grupach bez zwijania wierszy w agregaty.
Najczęściej zadawane pytania
Czym jest FTS5 w SQLite?
FTS5 to wbudowane rozszerzenie SQLite do wyszukiwania pełnotekstowego. Tworzysz specjalną tabelę wirtualną przez CREATE VIRTUAL TABLE ... USING fts5(...) i odpytujesz ją operatorem MATCH. Przy wstawianiu dzieli tekst na tokeny, przechowuje indeks odwrócony i domyślnie ustala ranking wyników algorytmem BM25.
Czym MATCH różni się od LIKE w SQLite?
LIKE liniowo przeszukuje podciągi i ignoruje granice słów. MATCH korzysta z indeksu odwróconego FTS5, więc jest szybki w dużych tabelach i rozumie tokeny, zapytania po prefiksie (term*), operatory logiczne (AND, OR, NOT) i wyszukiwanie fraz ("exact phrase"). MATCH działa tylko na wirtualnych tabelach FTS.
Jak utrzymać indeks FTS5 zsynchronizowany ze zwykłą tabelą?
Użyj tabeli FTS5 bez treści albo z treścią zewnętrzną, która wskazuje na prawdziwą tabelę, albo utwórz wyzwalacze AFTER INSERT, AFTER UPDATE i AFTER DELETE, które odzwierciedlają zmiany w tabeli FTS. Wzorzec z treścią zewnętrzną (content='posts') pozwala uniknąć przechowywania tekstu dwa razy.
Jak ustalić ranking wyników wyszukiwania pełnotekstowego w SQLite?
FTS5 udostępnia ukrytą kolumnę rank, która zwraca wynik BM25 (im niższy, tym lepiej). Sortuj bezpośrednio po niej: ORDER BY rank. Możesz też wywołać bm25(table), żeby jawnie dostać wynik, albo podać wagi kolumn, np. bm25(posts, 10.0, 1.0), żeby tytuł ważył więcej niż treść.