Материал основан на разборе sqlite.org. Ниже — главное и практические шаги, которые можно быстро применить в работе.
- Что такое FTS5: модуль полнотекстового поиска в SQLite
- Компиляция и подключение FTS5 в SQLite
- 2.1. Сборка FTS5 как части SQLite
- 2.2. Сборка загружаемого расширения
- Синтаксис полнотекстовых запросов FTS5 в SQLite
- 3.1. Строки FTS5
- 3.2. Фразы FTS5
- 3.3. Запросы с префиксом FTS5
- 3.4. Запросы FTS5 с начальным токеном
- 3.5. Запросы NEAR в FTS5
- 3.6. Фильтры столбцов FTS5
- 3.7. Булевы операторы FTS5
- Создание виртуальной таблицы FTS5: синтаксис и параметры
- 4.1. Параметр столбца UNINDEXED
- 4.2. Префиксные индексы
- 4.3. Токенизаторы
- 4.3.1. Токенизатор Unicode61
- 4.3.2. Токенизатор Ascii
- 4.3.3. Токенизатор Porter
- 4.3.4. Токенизатор Trigram
- 4.4. Внешнее содержимое и таблицы без содержимого
- 4.4.1. Таблицы без содержимого (Contentless Tables)
- 4.4.2. Таблицы без содержимого с поддержкой удаления (Contentless-Delete Tables)
- 4.4.3. Таблицы с внешним содержимым (External Content Tables)
- 4.4.4. Подводные камни таблиц с внешним содержимым (External Content Table Pitfalls)
Что такое FTS5: модуль полнотекстового поиска в SQLite
FTS5 — это модуль виртуальных таблиц в SQLite, который обеспечивает приложению возможности полнотекстового поиска.
Чтобы использовать FTS5, пользователь создаёт виртуальную таблицу FTS5 с одним или несколькими столбцами. Например:
CREATE VIRTUAL TABLE email USING fts5(sender, title, body);
Добавление типов, ограничений или ключей PRIMARY KEY в оператор CREATE VIRTUAL TABLE для таблицы FTS5 невозможно. После создания таблицы FTS5 она может заполняться стандартными операциями INSERT, UPDATE и DELETE, как и любая другая таблица. Такая таблица, не имеющая объявления PRIMARY KEY, имеет неявное поле INTEGER PRIMARY KEY именуемое rowid.
FTS5 поддерживает различные параметры в операторе CREATE VIRTUAL TABLE, позволяющие настраивать характеристики таблицы. Эти параметры могут изменять способы извлечения терминов из документов и запросов, создавать дополнительные индексы для повышения скорости поиска по префиксу и использовать таблицу FTS5 в качестве индекса для стороннего содержимого.
После заполнения существуют три способа выполнить полнотекстовый запрос к содержимому таблицы FTS5:
- использование оператора MATCH в предложении WHERE оператора SELECT,
- использование оператора равенства («=») в предложении WHERE оператора SELECT,
- использование синтаксиса табличной функции (table-valued function syntax)
При использовании операторов MATCH или = имя таблицы FTS5 указывается слева от оператора MATCH (без фильтрации по столбцам), а текстовое значение искомого термина — справа. При работе с табличной функцией искомый термин передается как первый аргумент.
-- Запрос всех строк, содержащих хотя бы одно вхождение термина
-- "fts5" (в любом столбце). Следующие три запроса эквивалентны.
SELECT * FROM email WHERE email MATCH 'fts5';
SELECT * FROM email WHERE email = 'fts5';
SELECT * FROM email('fts5');
По умолчанию полнотекстовый поиск в FTS5 не чувствителен к регистру. Пример выше, как и любые SQL-запросы без ORDER BY, возвращает результаты в случайном порядке. Чтобы отсортировать результаты по их релевантности, можно добавить условие ORDER BY.
-- Запрос всех строк, содержащих хотя бы одно вхождение термина
-- "fts5" (в любом столбце). Результаты возвращаются от лучшего к худшему.
SELECT * FROM email WHERE email MATCH 'fts5' ORDER BY rank;
Приложение может использовать вспомогательные функции FTS5 для получения подробной информации о совпадениях, включая выделение совпадений с помощью HTML-тегов. Эти функции работают как скалярные функции SQLite, в которых имя таблицы FTS5 передается первым аргументом.
-- Запрос строк, соответствующих "fts5". Возвращает копию столбца "body"
-- каждой строки с совпадениями, окружёнными тегами <b></b>.
SELECT highlight(email, 2, '<b>', '</b>') FROM email('fts5');
Описание доступных вспомогательных функций и информация о настройке специального столбца «rank» представлены далее. Пользовательские функции могут быть реализованы на C и зарегистрированы в FTS5 аналогично пользовательским SQL-функциям в ядре SQLite.
Помимо поиска всех строк, содержащих термин, FTS5 позволяет пользователю искать строки, которые содержат:
- любые термины, начинающиеся с указанного префикса,
- «фразы» — последовательности терминов или префиксных терминов, которые должны присутствовать в документе, чтобы он соответствовал запросу,
- наборы терминов, префиксных терминов или фраз, которые встречаются в пределах заданной близости друг от друга (они называются «NEAR-запросами»),
- булевы комбинации любого из вышеперечисленного
Расширенные поиски запрашиваются путем предоставления более сложной строки FTS5 в качестве текста справа от оператора MATCH (или в синтаксисе табличной функции). Полный синтаксис запросов описан ниже.
Компиляция и подключение FTS5 в SQLite
2.1. Сборка FTS5 как части SQLite
С 14 октября 2015 года FTS5 включён в стандартное дерево исходного кода SQLite. При использовании стандартных сборок кода FTS5 можно активировать с параметром --enable-fts5 в скрипте конфигурации. На текущий момент FTS5 отключён по умолчанию, но это может измениться.
Или, если sqlite3.c компилируется с использованием какой-либо другой системы сборки, путём обеспечения определения символа препроцессора SQLITE_ENABLE_FTS5
2.2. Сборка загружаемого расширения
В качестве альтернативы FTS5 может быть собран как загружаемое расширение
Канонический исходный код FTS5 состоит из серии файлов *.c и других файлов в директории ext/fts5 дерева исходного кода SQLite. Процесс сборки сводит их к двум файлам — fts5.c и fts5.h — которые могут использоваться для сборки загружаемого расширения SQLite
Получите последний код SQLite из fossil, создайте Makefile как описано в разделе How To Compile SQLite, затем соберите цель fts5.c, которая также создаёт fts5.h:
$ wget -c https://sqlite.org/src/tarball/SQLite-trunk.tgz?uuid=trunk -O SQLite-trunk.tgz
....
$ tar -xzf SQLite-trunk.tgz
$ cd SQLite-trunk
$ ./configure && make fts5.c
... lots of output ...
$ ls fts5.[ch]
fts5.c fts5.h
Код в fts5.c затем может быть скомпилирован в загружаемое расширение или статически скомпонован с приложением, как описано в разделе Compiling Loadable Extensions. Определены две точки входа, обе из которых делают одно и то же:
sqlite3_fts_initsqlite3_fts5_init
Другой файл, fts5.h, не требуется для компиляции расширения FTS5. Он используется приложениями, которые реализуют пользовательские токенизаторы FTS5 или вспомогательные функции
Синтаксис полнотекстовых запросов FTS5 в SQLite
Следующий блок содержит сводку синтаксиса запросов FTS в форме BNF (Backus–Naur Form). Далее следует подробное объяснение
3.1. Строки FTS5
Внутри выражения FTS строка может быть задана одним из двух способов:
Путём заключения её в двойные кавычки ("). Внутри строки любые встроенные символы двойных кавычек могут быть экранированы в стиле SQL — путём добавления второго символа двойной кавычки
Как «голое слово» FTS5 (FTS5 bareword), которое не является словами "AND", "OR" или "NOT" (с учётом регистра). Голое слово FTS5 — это строка из одного или нескольких последовательных символов, каждый из которых является одним из следующих:
- символы вне диапазона ASCII (то есть кодовые точки Unicode больше 127),
- один из 52 символов ASCII верхнего и нижнего регистра,
- один из 10 десятичных цифровых символов ASCII,
- символ подчёркивания (кодовая точка Unicode 95),
- символ-заменитель (кодовая точка Unicode 26)
3.2. Фразы FTS5
Каждая строка в запросе fts5 разбирается («токенизируется») токенизатором, и из неё извлекается список из нуля или более токенов, или термов. Например, токенизатор по умолчанию токенизирует строку "alpha beta gamma" в три отдельных токена — "alpha", "beta" и "gamma" — именно в таком порядке
Запросы FTS состоят из фраз. Фраза — это упорядоченный список из одного или нескольких токенов. Токены из каждой строки в запросе образуют одну фразу. Две фразы могут быть объединены в одну большую фразу с помощью оператора +. Например, предполагая, что используемый модуль токенизатора токенизирует входную строку "one.two.three" в три отдельных токена, следующие четыре запроса задают одну и ту же фразу:
... MATCH '"one two three"'
... MATCH 'one + two + three'
... MATCH '"one two" + three'
... MATCH 'one.two.three'
Фраза соответствует документу, если документ содержит хотя бы одну подпоследовательность токенов, совпадающую с последовательностью токенов, составляющих фразу
3.3. Запросы с префиксом FTS5
Если за строкой внутри выражения FTS следует символ *, то последний токен, извлечённый из строки, помечается как префиксный токен. Как и следует ожидать, префиксный токен соответствует любому токену документа, для которого он является префиксом. Например, первые два запроса в следующем блоке будут соответствовать любому документу, содержащему токен "one", за которым непосредственно следует токен "two", а затем любой токен, начинающийся с "thr":
... MATCH '"one two thr" * '
... MATCH 'one + two + thr*'
... MATCH '"one two thr*"' -- Может работать не так, как ожидается!
Последний запрос в приведённом выше блоке может работать не так, как ожидается. Поскольку символ * находится внутри двойных кавычек, он будет передан токенизатору, который, скорее всего, отбросит его (или, возможно, в зависимости от конкретного используемого токенизатора, включит его как часть последнего токена) вместо того, чтобы распознать его как специальный символ FTS
3.4. Запросы FTS5 с начальным токеном
Если символ ^ появляется непосредственно перед фразой, которая не является частью запроса NEAR, то эта фраза соответствует документу только в том случае, если она начинается с первого токена в столбце. Синтаксис ^ может быть объединён с фильтром столбца, но не может быть вставлен в середину фразы
3.5. Запросы NEAR в FTS5
Две или более фраз могут быть сгруппированы в группу NEAR. Группа NEAR задаётся токеном "NEAR" (с учётом регистра), за которым следует открывающая скобка, затем две или более фразы, разделённых пробелами, опционально за которыми следует запятая и числовой параметр N, а затем закрывающая скобка. Например:
... MATCH 'NEAR("one two" "three four", 10)'
... MATCH 'NEAR("one two" thr* + four)'
Если параметр N не указан, он по умолчанию равен 10. Группа NEAR соответствует документу, если документ содержит хотя бы одну группу токенов, которая:
- содержит хотя бы один экземпляр каждой фразы, и
- для которой количество токенов между концом первой фразы и началом последней фразы в группе меньше или равно N
3.6. Фильтры столбцов FTS5
Одна фраза или группа NEAR может быть ограничена соответствием тексту в указанном столбце таблицы FTS путём добавления перед ней имени столбца, за которым следует символ двоеточия. Или ограничена набором столбцов путём добавления перед ней разделённого пробелами списка имён столбцов, заключённого в фигурные скобки (curly brackets), за которым следует символ двоеточия.
Имена столбцов могут быть указаны в любой из двух форм, описанных выше для строк. В отличие от строк, являющихся частью фраз, имена столбцов не передаются модулю токенизатора. Имена столбцов нечувствительны к регистру обычным для имён столбцов SQLite образом — эквивалентность верхнего/нижнего регистра понимается только для символов в диапазоне ASCII
... MATCH 'colname : NEAR("one two" "three four", 10)'
... MATCH '"colname" : one + two + three'
... MATCH '{col1 col2} : NEAR("one two" "three four", 10)'
... MATCH '{col2 col1 col3} : one + two + three'
Если перед спецификацией фильтра столбца стоит символ -, то она интерпретируется как список столбцов, по которым не нужно выполнять поиск. Например:
-- Поиск совпадений во всех столбцах, кроме "colname"
... MATCH '- colname : NEAR("one two" "three four", 10)'
-- Поиск совпадений во всех столбцах, кроме "col1", "col2" и "col3"
... MATCH '- {col2 col1 col3} : one + two + three'
Спецификации фильтров столбцов могут также применяться к произвольным выражениям, заключённым в скобки. В этом случае фильтр столбца применяется ко всем фразам внутри выражения. Вложенные операции фильтрации столбцов могут только дополнительно сужать подмножество совпадающих столбцов, они не могут использоваться для повторного включения отфильтрованных столбцов. Например:
-- Следующие запросы эквивалентны:
... MATCH '{a b} : ( {b c} : "hello" AND "world" )'
... MATCH '(b : "hello") AND ({a b} : "world")'
Наконец, фильтр столбца для одного столбца может быть задан путём использования имени столбца в качестве левой части оператора MATCH (вместо обычного имени таблицы)
3.7. Булевы операторы FTS5
Фразы и группы NEAR могут быть объединены в выражения с помощью булевых операторов. В порядке приоритета, от наивысшего (наиболее тесная группировка) до наименьшего (наиболее свободная группировка), операторы следующие:
- неявный AND (implicit AND)
- NOT
- AND
- OR
Скобки могут использоваться для группировки выражений с целью изменения приоритета операторов обычным образом. Например:
-- Поскольку NOT группирует теснее, чем OR, любой из следующих запросов может
-- использоваться для поиска всех документов, содержащих токен "two", но не
-- "three", или содержащих токен "one".
... MATCH 'one OR two NOT three'
... MATCH 'one OR (two NOT three)'
-- Соответствует документам, содержащим хотя бы один экземпляр "one"
-- или "two", но не содержащим ни одного экземпляра токена "three".
... MATCH '(one OR two) NOT three'
Фразы и группы NEAR также могут быть соединены неявными операторами AND. Для простоты они не показаны в грамматике BNF выше. По существу, любая последовательность фраз или групп NEAR (включая те, которые ограничены соответствием указанным столбцам), разделённых только пробелами, обрабатывается так, как если бы между каждой парой фраз или групп NEAR был неявный оператор AND.
Неявные операторы AND никогда не вставляются после или перед выражением, заключённым в скобки. Неявные операторы AND группируют теснее, чем все остальные операторы, включая NOT
Создание виртуальной таблицы FTS5: синтаксис и параметры
Каждый аргумент, указанный в составе оператора CREATE VIRTUAL TABLE ... USING fts5 ..., является либо объявлением столбца, либо параметром конфигурации. Объявление столбца состоит из одного или нескольких разделённых пробелами FTS5-слов (barewords) или строковых литералов, заключённых в кавычки любым способом, допустимым в SQLite
Первая строка или bareword в объявлении столбца является именем столбца. Попытка назвать столбец таблицы fts5 именем «rowid» или «rank», а также присвоить столбцу то же имя, что используется самой таблицей, является ошибкой
Каждая последующая строка или bareword в объявлении столбца является параметром столбца, изменяющим поведение этого столбца. Параметры столбцов не зависят от регистра. В отличие от ядра SQLite, FTS5 считает нераспознанные параметры столбцов ошибками. В настоящее время единственным распознаваемым параметром является «UNINDEXED» (см. ниже)
Параметр конфигурации состоит из FTS5-слова (bareword) — имени параметра — за которым следует символ «=», а затем значение параметра. Значение параметра задаётся либо одним FTS5-словом (bareword), либо строковым литералом, также заключённым в кавычки любым способом, допустимым в ядре SQLite. Например:
CREATE VIRTUAL TABLE mail USING fts5(sender, title, body, tokenize = 'porter ascii');
В настоящее время доступны следующие параметры конфигурации:
- параметр «tokenize» — для настройки пользовательского токенизатора,
- параметр «prefix» — для добавления префиксных индексов к таблице FTS5,
- параметр «content» — для превращения таблицы FTS5 во внешнюю контентную таблицу или таблицу без контента,
- параметр «content_rowid» — для задания поля rowid внешней контентной таблицы,
- параметр «columnsize» — для настройки того, сохраняется ли размер в токенах каждого значения в таблице FTS5 отдельно в базе данных,
- параметр «detail» — может использоваться для уменьшения размера FTS-индекса на диске путём исключения из него части информации
4.1. Параметр столбца UNINDEXED
Содержимое столбцов, помеченных параметром столбца UNINDEXED, не добавляется в FTS-индекс. Это означает, что для целей запросов MATCH и вспомогательных функций FTS5 столбец не содержит сопоставляемых токенов
Например, чтобы избежать добавления содержимого поля «uuid» в FTS-индекс, достаточно пометить этот столбец как UNINDEXED при создании таблицы
4.2. Префиксные индексы
По умолчанию FTS5 поддерживает единственный индекс, фиксирующий расположение каждого вхождения токена в наборе документов. Это означает, что запросы по полным токенам выполняются быстро, поскольку требуют единственного поиска, однако запросы по префиксному токену могут выполняться медленно, поскольку требуют сканирования диапазона.
Например, запрос по префиксному токену «abc*» требует сканирования диапазона всех токенов, больших или равных «abc» и меньших «abd»
Префиксный индекс — это отдельный индекс, фиксирующий расположение всех вхождений префиксных токенов определённой длины в символах, используемый для ускорения запросов по префиксным токенам. Например, для оптимизации запроса по префиксному токену «abc*» требуется префиксный индекс трёхсимвольных префиксов
Чтобы добавить префиксные индексы к таблице FTS5, параметр «prefix» устанавливается либо в одно положительное целое число, либо в текстовое значение, содержащее разделённый пробелами список из одного или нескольких положительных целых чисел. Для каждого указанного целого числа создаётся префиксный индекс. Если в составе одного оператора CREATE VIRTUAL TABLE указано более одного параметра «prefix», применяются все они
4.3. Токенизаторы
Параметр «tokenize» оператора CREATE VIRTUAL TABLE используется для настройки конкретного токенизатора, применяемого таблицей FTS5. Аргумент параметра должен быть либо FTS5-словом (bareword), либо текстовым литералом SQL. Текст аргумента сам по себе трактуется как разделённая пробелами последовательность из одного или нескольких FTS5-слов (barewords) или текстовых литералов SQL.
Первый из них является именем используемого токенизатора. Второй и последующие элементы списка, если они присутствуют, являются аргументами, передаваемыми реализации токенизатора
В отличие от значений параметров и имён столбцов, текстовые литералы SQL, предназначенные для токенизаторов, должны заключаться в одинарные кавычки. Например:
-- Все следующие варианты эквивалентны
CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = 'porter ascii');
CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = "porter ascii");
CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = "'porter' 'ascii'");
CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = '''porter'' ''ascii''');
-- Но это завершится ошибкой:
CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = '"porter" "ascii"');
-- Это тоже завершится ошибкой:
CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = 'porter' 'ascii');
FTS5 включает четыре встроенных модуля токенизатора:
- токенизатор unicode61, основанный на стандарте Unicode 6.1 — токенизатор по умолчанию,
- токенизатор ascii, который считает все символы за пределами диапазона кодовых точек ASCII (0–127) символами токенов,
- токенизатор porter, реализующий алгоритм стемминга Портера (Porter stemming algorithm),
- токенизатор trigram, который рассматривает каждую непрерывную последовательность из трёх символов как токен, позволяя FTS5 поддерживать более общее сопоставление подстрок
Также возможно создание пользовательских токенизаторов для FTS5
4.3.1. Токенизатор Unicode61
Токенизатор unicode61 классифицирует все символы Unicode как «разделители» или «символы токенов». По умолчанию все пробельные символы и знаки препинания, определённые стандартом Unicode 6.1, считаются разделителями, а все остальные символы — символами токенов.
Более конкретно, все символы Unicode, отнесённые к общей категории, начинающейся с «L» или «N» (буквы и цифры соответственно), или к категории «Co» («другое, частное использование»), считаются токенами. Все остальные символы являются разделителями
Каждая непрерывная последовательность из одного или нескольких символов токенов считается токеном. Токенизатор не чувствителен к регистру в соответствии с правилами, определёнными стандартом Unicode 6.1
По умолчанию диакритические знаки удаляются из всех символов латинского письма. Это означает, например, что «A», «a», «À», «à», «Â» и «â» считаются эквивалентными
Любые аргументы, следующие за «unicode61» в спецификации токена, трактуются как список чередующихся имён и значений параметров. Unicode61 поддерживает параметры remove_diacritics, tokenchars, separators и categories. Например:
-- Создать таблицу FTS5, которая не удаляет диакритические знаки из символов
-- латинского письма и считает дефисы и символы подчёркивания частью токенов.
CREATE VIRTUAL TABLE ft USING fts5(a, b, tokenize = "unicode61 remove_diacritics 0 tokenchars '-_'"
);
или:
-- Создать таблицу FTS5, которая, помимо классов символов токенов по умолчанию,
-- считает символы класса "Mn" символами токенов.
CREATE VIRTUAL TABLE ft USING fts5(a, b, tokenize = "unicode61 categories 'L* N* Co Mn'"
);
Токенизатор fts5 unicode61 побайтово совместим с токенизатором unicode61 из fts3/4
4.3.2. Токенизатор Ascii
Токенизатор ascii аналогичен токенизатору unicode61, за исключением следующего:
- все не-ASCII-символы (с кодовыми точками больше 127) всегда считаются символами токенов; если какие-либо не-ASCII-символы указаны в составе параметра separators, они игнорируются,
- приведение к нижнему регистру выполняется только для ASCII-символов; таким образом, «A» и «a» считаются эквивалентными, тогда как «Ã» и «ã» различаются,
- параметр remove_diacritics не поддерживается
4.3.3. Токенизатор Porter
Токенизатор porter является обёрточным токенизатором (wrapper tokenizer). Он принимает вывод некоторого другого токенизатора и применяет алгоритм стемминга Портера к каждому токену перед его возвратом в FTS5. Это позволяет поисковым запросам, например «correction», находить похожие слова, такие как «corrected» или «correcting».
Алгоритм стеммера Портера предназначен для использования только с терминами английского языка — его применение с другими языками может как улучшить, так и не улучшить качество поиска
По умолчанию токенизатор porter работает как обёртка вокруг токенизатора по умолчанию (unicode61). Или, если к параметру «tokenize» после «porter» добавлены один или несколько дополнительных аргументов, они трактуются как спецификация базового токенизатора, используемого стеммером Портера
4.3.4. Токенизатор Trigram
Токенизатор trigram расширяет FTS5 для поддержки сопоставления подстрок в общем случае вместо обычного сопоставления токенов. При использовании токенизатора trigram токен запроса или фразы может соответствовать любой последовательности символов в строке, а не только полному токену. Например:
CREATE VIRTUAL TABLE tri USING fts5(a, tokenize="trigram");
INSERT INTO tri VALUES('abcdefghij KLMNOPQRST uvwxyz');
-- Все следующие запросы соответствуют единственной строке в таблице
SELECT * FROM tri('cdefg');
SELECT * FROM tri('cdefg AND pqr');
SELECT * FROM tri('"hij klm" NOT stuv');
Токенизатор trigram поддерживает параметр case_sensitive:
-- Чувствительный к регистру индекс trigram
CREATE VIRTUAL TABLE tri USING fts5(a, tokenize="trigram case_sensitive 1");
Если параметр remove_diacritics не установлен, таблицы FTS5, использующие токенизатор trigram, также поддерживают индексированное сопоставление с шаблонами GLOB и LIKE. Например:
SELECT * FROM tri WHERE a LIKE '%cdefg%';
SELECT * FROM tri WHERE a GLOB '*ij klm*xyz';
Если токенизатор FTS5 trigram создан с параметром case_sensitive, установленным в 1, он может индексировать только запросы GLOB, но не LIKE
Важные примечания по токенизатору trigram:
- подстроки, состоящие менее чем из 3 символов Unicode, не соответствуют ни одной строке при использовании в полнотекстовом запросе; если шаблон LIKE или GLOB не содержит хотя бы одной последовательности символов Unicode, не являющихся подстановочными знаками, FTS5 переходит к линейному сканированию всей таблицы,
- если таблица FTS5 создана с указанным параметром
detail=noneилиdetail=column, полнотекстовые запросы не могут содержать токены длиннее 3 символов Unicode; сопоставление с шаблонами LIKE и GLOB может работать несколько медленнее, но по-прежнему функционирует, - индекс не может использоваться для оптимизации шаблонов LIKE, если оператор LIKE содержит предложение ESCAPE
4.4. Внешнее содержимое и таблицы без содержимого
Обычно при вставке строки в таблицу FTS5, помимо построения индекса, FTS5 сохраняет копию исходного содержимого строки. Когда пользователь или реализация вспомогательной функции запрашивает значения столбцов из таблицы FTS5, эти значения считываются из той приватной копии содержимого. Параметр "content" может использоваться для создания таблицы FTS5, которая хранит только записи полнотекстового индекса FTS.
Поскольку сами значения столбцов обычно значительно больше, чем соответствующие записи полнотекстового индекса, это позволяет существенно сэкономить место в базе данных
Существует два способа использования параметра "content":
- установить его в пустую строку, чтобы создать таблицу FTS5 без содержимого (contentless table); в этом случае FTS5 предполагает, что исходные значения столбцов недоступны ему при обработке запросов; полнотекстовые запросы и некоторые вспомогательные функции по-прежнему могут использоваться, однако из таблицы нельзя считать никакие значения столбцов, кроме rowid,
- установить его в имя объекта базы данных (таблицы, виртуальной таблицы или представления), к которому FTS5 может обращаться в любое время для получения значений столбцов; такая таблица называется таблицей с «внешним содержимым» (external content table); в этом случае доступна вся функциональность FTS5, однако пользователь несёт ответственность за то, чтобы содержимое полнотекстового индекса было согласовано с указанным объектом базы данных
4.4.1. Таблицы без содержимого (Contentless Tables)
Таблица FTS5 без содержимого создаётся путём установки параметра "content" в пустую строку. Например:
CREATE VIRTUAL TABLE ft USING fts5(a, b, c, content='');
Таблицы FTS5 без содержимого не поддерживают операторы UPDATE или DELETE, а также операторы INSERT, которые не передают ненулевое значение для поля rowid. Таблицы без содержимого не поддерживают обработку конфликтов REPLACE. Операторы REPLACE и INSERT OR REPLACE обрабатываются как обычные операторы INSERT. Строки можно удалять из таблицы без содержимого с помощью команды удаления FTS5 (FTS5 delete command)
Попытка считать любое значение столбца, кроме rowid, из таблицы FTS5 без содержимого возвращает значение SQL NULL
4.4.2. Таблицы без содержимого с поддержкой удаления (Contentless-Delete Tables)
Начиная с версии 3.43.0, также доступны таблицы без содержимого с поддержкой удаления (contentless-delete tables). Такая таблица создаётся путём установки параметра content в пустую строку и одновременной установки параметра contentless_delete в значение 1. Например:
CREATE VIRTUAL TABLE ft USING fts5(a, b, c, content='', contentless_delete=1);
Таблица без содержимого с поддержкой удаления отличается от обычной таблицы без содержимого следующим:
- таблицы contentless-delete поддерживают операторы DELETE и "INSERT OR REPLACE INTO",
- таблицы contentless-delete поддерживают операторы UPDATE, но только если новые значения предоставляются для всех пользовательских столбцов таблицы fts5,
- таблицы contentless-delete не поддерживают команду удаления FTS5 (FTS5 delete command)
-- Поддерживаемый оператор UPDATE:
UPDATE ft SET a=?, b=?, c=? WHERE rowid=?;
-- Этот UPDATE не поддерживается, так как не передаёт новое значение
-- для столбца "c".
UPDATE ft SET a=?, b=? WHERE rowid=?;
Если обратная совместимость не требуется, в новом коде следует предпочитать таблицы contentless-delete таблицам без содержимого
4.4.3. Таблицы с внешним содержимым (External Content Tables)
Таблица FTS5 с внешним содержимым создаётся путём установки параметра content в имя таблицы, виртуальной таблицы или представления (далее — «таблица содержимого», content table) в той же базе данных. Всякий раз, когда FTS5 требуются значения столбцов, он обращается к таблице содержимого следующим образом, подставляя rowid строки, для которой нужны значения, в SQL-переменную:
SELECT <content_rowid>, <cols> FROM <content> WHERE <content_rowid> = ?;
В приведённом выше запросе <content> заменяется именем таблицы содержимого. По умолчанию <content_rowid> заменяется буквальным текстом "rowid". Или, если параметр "content_rowid" задан в операторе CREATE VIRTUAL TABLE, — значением этого параметра. <cols> заменяется разделённым запятыми списком имён столбцов таблицы FTS5. Например:
-- Если схема базы данных такова:
CREATE TABLE t1 (a, b, c, d INTEGER PRIMARY KEY);
CREATE VIRTUAL TABLE ft USING fts5(a, c, content=t1, content_rowid=d);
-- FTS5 может выполнять запросы вида:
SELECT d, a, c FROM t1 WHERE d = ?;
Таблица содержимого также может запрашиваться следующим образом:
SELECT <content_rowid>, <cols> FROM <content> ORDER BY <content_rowid> ASC;
SELECT <content_rowid>, <cols> FROM <content> ORDER BY <content_rowid> DESC;
По-прежнему ответственность пользователя — обеспечивать актуальность содержимого внешней таблицы содержимого FTS5 в соответствии с таблицей содержимого. Один из способов сделать это — использовать триггеры. Например:
-- Создаём таблицу и внешнюю таблицу содержимого fts5 для её индексирования.
CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
CREATE VIRTUAL TABLE fts_idx USING fts5(b, c, content='t1', content_rowid='a');
-- Триггеры для поддержания актуальности FTS-индекса.
CREATE TRIGGER t1_ai AFTER INSERT ON t1 BEGIN INSERT INTO fts_idx(rowid, b, c) VALUES (new.a, new.b, new.c);
END;
CREATE TRIGGER t1_ad AFTER DELETE ON t1 BEGIN INSERT INTO fts_idx(fts_idx, rowid, b, c) VALUES('delete', old.a, old.b, old.c);
END;
CREATE TRIGGER t1_au AFTER UPDATE ON t1 BEGIN INSERT INTO fts_idx(fts_idx, rowid, b, c) VALUES('delete', old.a, old.b, old.c); INSERT INTO fts_idx(rowid, b, c) VALUES (new.a, new.b, new.c);
END;
Как и таблицы без содержимого, таблицы с внешним содержимым не поддерживают обработку конфликтов REPLACE. Любые операции, указывающие обработку конфликтов REPLACE, обрабатываются с использованием ABORT
4.4.4. Подводные камни таблиц с внешним содержимым (External Content Table Pitfalls)
Ответственность пользователя — обеспечивать согласованность внешней таблицы содержимого FTS5 (той, у которой задан непустой параметр content=) с самой таблицей содержимого (таблицей, указанной в параметре content=). Если допустить их рассогласование, результаты запросов к таблице FTS5 могут стать неинтуитивными и выглядеть противоречивыми
В подобных ситуациях кажущиеся противоречивыми результаты запросов к внешней таблице содержимого FTS5 можно понять следующим образом:
- если запрос не использует полнотекстовый индекс — не содержит оператора MATCH или эквивалентного синтаксиса табличной функции — то запрос фактически передаётся напрямую к таблице внешнего содержимого; в этом случае содержимое FTS-индекса не влияет на результаты запроса,
- если запрос использует полнотекстовый индекс, то модуль FTS5 запрашивает у него набор значений rowid, соответствующих документам, которые удовлетворяют запросу; для каждого такого rowid он затем выполняет запрос к таблице содержимого для получения необходимых значений столбцов
Например, если база данных создана с помощью следующего скрипта:
-- Создаём и заполняем таблицу.
CREATE TABLE t1(a INTEGER PRIMARY KEY, t TEXT);
INSERT INTO t1 VALUES(1, 'all that glitters');
INSERT INTO t1 VALUES(2, 'is not gold');
-- Создаём внешнюю таблицу содержимого FTS5
CREATE VIRTUAL TABLE ft USING fts5(t, content='t1', content_rowid='a');
то таблица содержимого содержит две строки, но FTS-индекс не содержит соответствующих им записей. В этом случае следующие запросы вернут противоречивые результаты:
-- Возвращает 2 строки. Поскольку запрос не использует FTS-индекс, он
-- фактически выполняется напрямую против таблицы 't1' и возвращает обе строки.
SELECT * FROM ft;
-- Возвращает 0 строк. Этот запрос использует FTS-индекс, который в данный
-- момент не содержит записей. Поэтому возвращает 0 строк.
SELECT rowid, t FROM ft('gold')
Либо, если база данных была создана и заполнена следующим образом:
-- Создаём и заполняем таблицу.
CREATE TABLE t1(a INTEGER PRIMARY KEY, t TEXT);
-- Создаём внешнюю таблицу содержимого FTS5
CREATE VIRTUAL TABLE ft USING fts5(t, content='t1', content_rowid='a');
INSERT INTO ft(rowid, t) VALUES(1, 'all that glitters');
INSERT INTO ft(rowid, t) VALUES(2, 'is not gold');
то таблица содержимого пуста, но FTS-индекс содержит записи для 6 различных токенов. В этом случае следующие запросы вернут противоречивые результаты:
-- Возвращает 0 строк. Поскольку запрос не использует FTS-индекс, он
-- передаётся напрямую к таблице 't1', которая не содержит данных.
SELECT * FROM ft;
-- Возвращает 1 строку. Поле "rowid" возвращённой строки равно 2, а
-- поле "t" установлено в NULL, потому что при обращении к внешней таблице
-- содержимого "t1" за данными строки с a=2 данные не были найдены.
SELECT rowid, t FROM ft('gold')
Как описано в предыдущем разделе, триггеры на таблице содержимого — хороший способ обеспечить согласованность внешней таблицы содержимого FTS5. Однако триггеры срабатывают только при вставке, обновлении или удалении строк в таблице содержимого. Это означает, что если база данных создана следующим образом:
— Создаём триггеры для поддержания актуальности таблицы FTS5 END; — <аналогичные триггеры для update + delete>
то таблица содержимого и внешняя таблица содержимого FTS5 рассогласованы, поскольку создание триггеров не копирует существующие строки из таблицы содержимого в FTS-индекс. Триггеры способны обеспечить лишь то, что изменения, внесённые в таблицу содержимого после их создания, будут отражены в FTS-индексе
В этой и любой другой ситуации, когда FTS-индекс и его таблица содержимого стали рассогласованными, можно использовать команду `rebuild`, чтобы полностью отбросить содержимое FTS-индекса и перестроить его на основе текущего содержимого таблицы содержимого
Таблицы без содержимого поддерживают операторы UPDATE и DELETE, хотя их следует использовать с осторожностью. При выполнении DELETE против таблицы без содержимого для каждой затронутой строки текущие значения извлекаются путём запроса к таблице содержимого. Полученные значения затем передаются настроенному токенизатору таблицы, который извлекает из них список токенов, которые необходимо удалить из FTS-индекса. Если полученные значения не совпадают с набором записей в FTS-индексе для данной строки, некоторые токены могут остаться в FTS-индексе, что впоследствии приведёт к противоречивым результатам запросов
На практике это означает, что для удаления строки как из таблицы содержимого, так и из внешней таблицы содержимого FTS5, сначала необходимо обновить таблицу FTS5 (чтобы строка таблицы содержимого была ещё доступна ей)
В FTS5 оператор UPDATE реализован как DELETE с последующим INSERT новых значений. Из этого следует, что при обновлении строки как в таблице содержимого, так и во внешней таблице содержимого FTS5, сначала необходимо обновить таблицу FTS5 (опять же, чтобы строка таблицы содержимого была доступна ей)
## 4.5. Параметр columnsize
Обычно FTS5 поддерживает специальную вспомогательную таблицу внутри базы данных, которая хранит размер каждого значения столбца в токенах, вставленных в основную таблицу FTS5, в отдельной таблице. Эта вспомогательная таблица используется функцией API xColumnSize, которая, в свою очередь, используется встроенной функцией ранжирования bm25 (и, вероятно, будет полезна другим функциям ранжирования)
Чтобы сэкономить место, эту вспомогательную таблицу можно не создавать, установив параметр columnsize в ноль. Например:
— Таблица без значений xColumnSize(), хранимых на диске:
— Три эквивалентных способа создания таблицы, которая хранит — значения xColumnSize() на диске:
Установка параметра columnsize в любое значение, отличное от 0 или 1, является ошибкой
Если таблица FTS5 настроена с `columnsize=0`, но не является таблицей без содержимого, функция API xColumnSize всё равно работает, но выполняется значительно медленнее. В этом случае вместо того, чтобы читать возвращаемое значение непосредственно из базы данных, она читает само текстовое значение и подсчитывает токены в нём по требованию
Или, если таблица также является таблицей без содержимого, применяются следующие правила:
- функция API xColumnSize всегда возвращает -1; нет способа определить количество токенов в значении, хранящемся в таблице FTS5 без содержимого, настроенной с `columnsize=0`,
- каждая вставляемая строка должна сопровождаться явно указанным значением rowid; если таблица без содержимого настроена с `columnsize=0`, попытка вставить значение NULL в rowid является ошибкой SQLITE_MISMATCH,
- все запросы к таблице должны быть полнотекстовыми запросами; иными словами, они должны использовать оператор MATCH или = с именем столбца таблицы в качестве левого операнда, либо использовать синтаксис табличной функции; любой запрос, не являющийся полнотекстовым, приводит к ошибке
Имя таблицы, в которой хранятся значения xColumnSize (если не указано `columnsize=0`), — `<name>_docsize`, где `<name>` — имя самой таблицы FTS5. Инструмент sqlite3_analyzer может быть использован для существующей базы данных, чтобы определить, сколько места можно сэкономить, пересоздав таблицу FTS5 с `columnsize=0`
## 4.6. Параметр detail
Для каждого термина в документе FTS-индекс, поддерживаемый FTS5, хранит rowid документа, номер столбца, содержащего термин, и смещение термина внутри значения столбца. Параметр «detail» может использоваться для исключения части этой информации. Это уменьшает пространство, занимаемое индексом в файле базы данных, но также снижает возможности и эффективность системы
Параметр detail может быть установлен в «full» (значение по умолчанию), «column» или «none». Например:
— Следующие две строки эквивалентны (поскольку значение по умолчанию — для "detail" — "full").
Если параметр detail установлен в column, то для каждого термина FTS-индекс записывает только rowid и номер столбца, опуская информацию о смещении термина. Это приводит к следующим ограничениям:
- запросы NEAR недоступны,
- фразовые запросы недоступны,
- при условии, что таблица не является также таблицей без содержимого, функции xInstCount, xInst, xPhraseFirst и xPhraseNext работают медленнее обычного; это происходит потому, что вместо чтения необходимых данных непосредственно из FTS-индекса им приходится загружать и токенизировать текст документа по требованию,
- если таблица также является таблицей без содержимого, API xInstCount, xInst, xPhraseFirst и xPhraseNext ведут себя так, как если бы текущая строка не содержала никаких совпадений фраз (то есть xInstCount() возвращает 0)
Если параметр detail установлен в none, то для каждого термина FTS-индекс записывает только rowid. Информация о столбце и смещении не сохраняется. В дополнение к ограничениям, перечисленным выше для режима `detail=column`, это накладывает следующие дополнительные ограничения:
- запросы с фильтром по столбцу недоступны,
- при условии, что таблица не является также таблицей без содержимого, функции xPhraseFirstColumn и xPhraseNextColumn работают медленнее обычного,
- если таблица также является таблицей без содержимого, API xPhraseFirstColumn и xPhraseNextColumn ведут себя так, как если бы текущая строка не содержала никаких совпадений фраз (то есть xPhraseFirstColumn() устанавливает итератор в состояние EOF)
В одном тесте, в котором индексировался большой набор электронных писем (1636 МиБ на диске), FTS-индекс занимал 743 МиБ на диске при `detail=full`, 340 МиБ при `detail=column` и 134 МиБ при `detail=none`
## 4.7. Параметр tokendata
Этот параметр полезен только приложениям, реализующим пользовательские токенизаторы (custom tokenizers). Обычно токенизаторы могут возвращать токены, состоящие из любой последовательности байтов, включая байты 0x00. Однако если таблица задаёт параметр `tokendata=1`, то fts5 игнорирует первый байт 0x00 и любые завершающие данные в токене при сопоставлении. Он по-прежнему хранит весь токен в том виде, в котором он возвращён токенизатором, но остаток игнорируется ядром fts5
Полная версия токена, включая любой байт 0x00 и завершающие данные, доступна пользовательским вспомогательным функциям через API xQueryToken и xInstToken
Это может быть полезно для функций ранжирования. Пользовательский токенизатор может добавлять дополнительные данные к некоторым токенам документа, позволяя функции ранжирования придавать больший вес совпадениям некоторых токенов (например, тех, что находятся в заголовках документа)
Комбинация пользовательского токенизатора и пользовательской вспомогательной функции может использоваться для реализации асимметричного поиска (asymmetric search). Токенизатор мог бы (например) для каждого токена документа возвращать нормализованную по регистру и немаркированную версию токена, за которой следует байт 0x00, а за ним — полный текст токена из документа. При запросе fts5 предоставлял бы результаты так, как если бы все символы в запросе были нормализованы по регистру и не имели маркировки. Пользовательская вспомогательная функция могла бы затем использоваться в предложении WHERE запроса для фильтрации строк, не соответствующих условиям на основе вторичных или третичных маркировок в документе или терминах запроса
## 4.8. Параметр Locale
Этот параметр полезен только для приложений, реализующих пользовательские токенизаторы. Если таблица fts5 создана с указанием параметра `locale=1`, то SQL-функция `fts5_locale()` может использоваться для связывания значения локали (например, "th_TH" или "en_US") со строками, передаваемыми в FTS5. FTS5 сам по себе не использует значения локали, но делает их доступными для реализации токенизатора каждый раз, когда строка токенизируется. Токенизатор может затем корректировать своё поведение в зависимости от локали
— Следующий оператор создаёт таблицу fts5 с поддержкой локали. — Параметр "tokenizer=…" ниже должен быть заменён реальной спецификацией — токенизатора, поддерживающего локали.
— Этот оператор вставляет строку в таблицу. Значение, вставляемое в — столбец "a", использует локаль "th_TH", значение, записываемое в столбец "b", — использует локаль токенизатора по умолчанию. );
— Локаль "en_US" используется для токенизации поисковых терминов в — следующем запросе. );
Попытка передать строку `fts5_locale()` в таблицу fts5, которая не была создана с параметром `locale=1`, является ошибкой
Когда строка `fts5_locale()` хранится в обычной таблице с содержимым (то есть не в таблице без содержимого или таблице с внешним содержимым), прикреплённая локаль сохраняется вместе с ней. Если строка снова токенизируется FTS5, например потому что её строка удаляется или в рамках вычисления вспомогательной функции, прикреплённая локаль снова передаётся реализации токенизатора
Для поддержки локалей внешняя таблица с содержимым FTS5 может использовать SQL-представление, возвращающее значения `fts5_locale()` в качестве таблицы содержимого. Например:
— Каждая строка этой таблицы содержит строку и её локаль.
— Представление для объединения строки и локали из таблицы t1.
— Таблица FTS5 для чтения строк с поддержкой локали из представления v1.
Если значение `fts5_locale()` записывается в столбец UNINDEXED таблицы fts5, значение локали отбрасывается, а строка сохраняется сама по себе
Функция `fts5_get_locale()` может использоваться для получения локали значения, хранящегося в таблице FTS5
## 4.9. Параметр Contentless-Unindexed
Как правило, столбцы UNINDEXED, принадлежащие таблицам без содержимого, не очень полезны. Значения, записанные в них, не индексируются и не сохраняются, а чтение из такого столбца UNINDEXED всегда возвращает NULL. Однако если параметр `contentless_unindexed=1` указан для таблицы без содержимого, то значения столбцов UNINDEXED сохраняются постоянно, даже несмотря на то что значения, записанные в другие столбцы, не сохраняются
— Создать таблицу без содержимого с параметром contentless_unindexed=1. — Из строки, записанной в неё, значение 'one' будет проиндексировано и затем — отброшено, а значение "1" будет сохранено, но не проиндексировано.
— Этот запрос возвращает 1 строку с 2 столбцами — (NULL, 1). Чтение из — столбца "a" всегда возвращает NULL, так как таблица не имеет содержимого. — Но чтение из "b" возвращает значение, так как таблица использует contentless_unindexed=1.
Указание `contentless_unindexed=1` для таблицы fts5, которая не является таблицей без содержимого или таблицей без содержимого с удалением, является ошибкой
## 5. Вспомогательные функции
Вспомогательные функции похожи на скалярные SQL-функции, за исключением того, что они могут использоваться только в полнотекстовых запросах (тех, которые используют оператор MATCH, или LIKE/GLOB с токенизатором триграмм) к таблице FTS5. Их результаты вычисляются не только на основе переданных им аргументов, но и на основе текущего совпадения и совпавшей строки. Например, вспомогательная функция может возвращать числовое значение, указывающее точность совпадения (см. функцию bm25()), или фрагмент текста из совпавшей строки, содержащий один или несколько экземпляров поисковых терминов
Я нахожу вспомогательные функции одним из наиболее практичных инструментов FTS5: они позволяют не просто найти строку, но и сразу получить контекст совпадения — подсветку, сниппет или числовой ранг — без дополнительных запросов
FTS5 включает три встроенных вспомогательных функции:
- `bm25()` — возвращает числовое значение, отражающее точность совпадения текущей строки с запросом; строки с меньшим значением bm25() считаются более релевантными,
- `highlight()` — возвращает копию указанного столбца текущей строки, в которой каждое вхождение поискового термина заключено между двумя указанными строками,
- `snippet()` — выбирает фрагмент текста из указанного столбца и возвращает его с выделенными вхождениями поисковых терминов
## 6. Типичные ошибки при работе с FTS5
Работая с FTS5 на практике, я замечаю несколько ошибок, которые встречаются особенно часто
**Рассогласование внешней таблицы содержимого.** Самая распространённая проблема — создать внешнюю таблицу содержимого и забыть добавить триггеры. В результате FTS-индекс и таблица содержимого расходятся, и запросы с MATCH возвращают неожиданные результаты. Решение: всегда добавлять триггеры AFTER INSERT, AFTER DELETE и AFTER UPDATE сразу при создании таблицы, либо использовать команду `rebuild` для полного пересоздания индекса
**Неправильное использование символа `*` внутри кавычек.** Запрос `MATCH '"one two thr*"'` не работает как префиксный поиск, потому что `*` оказывается внутри двойных кавычек и передаётся токенизатору. Правильная форма — `MATCH 'one + two + thr*'` или `MATCH '"one two thr" *'`
**Попытка использовать NEAR или фразовые запросы с `detail=column` или `detail=none`.** Если таблица создана с уменьшенным уровнем детализации индекса, запросы NEAR и фразовые запросы недоступны. Это нужно учитывать при проектировании схемы: экономия места на диске достигается ценой ограничения функциональности
**Использование `columnsize=0` с таблицей без содержимого без явного rowid.** Если таблица без содержимого настроена с `columnsize=0`, вставка строки без явного rowid завершается ошибкой SQLITE_MISMATCH. Всегда указывайте rowid явно в таких таблицах
**Запросы с подстроками короче 3 символов при использовании токенизатора trigram.** Токенизатор trigram не может сопоставить подстроки короче 3 символов Unicode. Если шаблон LIKE или GLOB не содержит хотя бы одной трёхсимвольной последовательности, FTS5 переходит к полному линейному сканированию таблицы
## 7. Оценка результатов и выбор конфигурации
При выборе конфигурации FTS5 стоит ориентироваться на несколько практических критериев
**Выбор токенизатора.** Для большинства задач с текстом на английском языке достаточно токенизатора по умолчанию unicode61. Если нужен морфологический поиск (чтобы запрос «running» находил «run» и «runs»), используйте porter. Для поиска по произвольным подстрокам — trigram, но с пониманием, что индекс будет значительно больше
**Выбор параметра detail.** Если приложение не использует фразовые запросы и NEAR, а размер базы данных критичен, `detail=column` или `detail=none` позволяют существенно сократить объём индекса. В тесте с набором электронных писем объёмом 1636 МиБ переход с `detail=full` (743 МиБ) на `detail=none` (134 МиБ) давал пятикратное сокращение размера индекса
**Выбор между contentless и external content.** Таблица без содержимого подходит, когда исходные данные хранятся вне SQLite и не нужно читать значения столбцов из FTS-таблицы. Таблица с внешним содержимым подходит, когда данные уже есть в SQLite и нужна полная функциональность FTS5 без дублирования данных. Если обратная совместимость не требуется, предпочтительнее использовать contentless-delete таблицы вместо обычных contentless
**Использование префиксных индексов.** Если приложение активно использует запросы по префиксу (например, автодополнение), добавление параметра `prefix` с нужными длинами значительно ускоряет такие запросы. Без префиксного индекса каждый префиксный запрос требует сканирования диапазона
## Ответы на эти вопросы могут быть для вас полезными
**Можно ли использовать FTS5 без компиляции SQLite из исходников?**
Да. Начиная с версии SQLite 3.9.0, FTS5 включён в амальгамацию SQLite и включён по умолчанию в скрипте configure амальгамации. Если вы используете готовую сборку SQLite, FTS5, скорее всего, уже доступен. Также FTS5 можно собрать как отдельное загружаемое расширение из файлов `fts5.c` и `fts5.h`
**Чем таблица contentless-delete отличается от обычной таблицы без содержимого?**
Обычная таблица без содержимого не поддерживает операторы DELETE и UPDATE. Таблица contentless-delete (доступна с версии 3.43.0) поддерживает DELETE и "INSERT OR REPLACE INTO", а также UPDATE при условии, что новые значения предоставляются для всех пользовательских столбцов. При этом таблица contentless-delete не поддерживает команду удаления FTS5 (FTS5 delete command), которую поддерживает обычная таблица без содержимого.
**Почему запрос с MATCH возвращает 0 строк, хотя данные в таблице есть?**
Скорее всего, FTS-индекс и таблица содержимого рассогласованы. Это типичная ситуация для таблиц с внешним содержимым: если данные были вставлены в таблицу содержимого до создания FTS-таблицы или триггеров, FTS-индекс пуст. Используйте команду `rebuild` для полного пересоздания индекса на основе текущего содержимого таблицы.
**Как работает сортировка по релевантности в FTS5?**
Добавьте `ORDER BY rank` к запросу с MATCH. Столбец rank вычисляется с помощью встроенной функции ранжирования bm25() — строки с меньшим значением bm25() считаются более релевантными и возвращаются первыми. Функцию ранжирования можно заменить на пользовательскую через настройку специального столбца rank.
**Можно ли искать по подстрокам, а не только по полным токенам?**
Да, для этого используется токенизатор trigram. При его использовании токен запроса может



