Полнотекстовый поиск в Android

Мне сложно понять, как использовать полнотекстовый поиск (FTS) с Android. Я прочитал документацию SQLite на расширениях FTS3 и FTS4. И я знаю это можно сделать на Android. Однако мне трудно найти примеры, которые я могу понять.

Базовая модель базы данных

Таблица базы данных SQLite (с именем example_table) имеет 4 столбца. Однако есть только один столбец (с именем text_column), который нужно индексировать для полнотекстового поиска. Каждая строка text_column содержит текст, изменяющийся по длине от 0 до 1000 слов. Общее количество строк больше 10000.

  • Как настроить таблицу и/или виртуальную таблицу FTS?
  • Как бы вы выполняли запрос FTS на text_column?

Дополнительные примечания:

  • Поскольку нужно индексировать только один столбец, использование таблицы FTS (и отбрасывание example_table) будет неэффективным для не-FTS-запросов.
  • Для такой большой таблицы сохранение повторяющихся записей text_column в таблице FTS было бы нежелательным. Этот пост предлагает использовать таблицу внешнего контента.
  • В таблицах внешнего контента используется FTS4, но FTS4 не поддерживается до Android API 11. Ответ может предполагать API >= 11, но комментирование вариантов поддержки более низких версий было бы полезно.
  • Изменение данных в исходной таблице автоматически не обновляет таблицу FTS (и наоборот). Включая триггеры в вашем ответе не нужны для этого базового примера, но были бы полезны, тем не менее.

Ответ 1

Самый основной ответ

Я использую простой sql ниже, чтобы все было максимально понятным и понятным. В вашем проекте вы можете использовать удобные методы Android. Объект db, используемый ниже, представляет собой экземпляр SQLiteDatabase.

Создать таблицу FTS

db.execSQL("CREATE VIRTUAL TABLE fts_table USING fts3 ( col_1, col_2, text_column )");

Это может пойти в методе onCreate() вашего расширенного класса SQLiteOpenHelper.

Заполнить таблицу FTS

db.execSQL("INSERT INTO fts_table VALUES ('3', 'apple', 'Hello. How are you?')");
db.execSQL("INSERT INTO fts_table VALUES ('24', 'car', 'Fine. Thank you.')");
db.execSQL("INSERT INTO fts_table VALUES ('13', 'book', 'This is an example.')");

Лучше использовать SQLiteDatabase # insert или подготовленные инструкции, чем execSQL.

Таблица запросов FTS

String[] selectionArgs = { searchString };
Cursor cursor = db.rawQuery("SELECT * FROM fts_table WHERE fts_table MATCH ?", selectionArgs);

Вы также можете использовать метод SQLiteDatabase # query. Обратите внимание на ключевое слово MATCH.

Полный ответ

У виртуальной таблицы FTS выше проблемы с ней. Каждый столбец индексируется, но это пустая трата пространства и ресурсов, если некоторые столбцы не нужно индексировать. Единственный столбец, который нуждается в индексе FTS, вероятно, это text_column.

Для решения этой проблемы мы будем использовать комбинацию регулярной таблицы и виртуальной таблицы FTS. Таблица FTS будет содержать индекс, но ни одна из фактических данных из обычной таблицы. Вместо этого он будет иметь ссылку на содержимое обычной таблицы. Это называется внешней таблицей .

enter image description here

Создать таблицы

db.execSQL("CREATE TABLE example_table (_id INTEGER PRIMARY KEY, col_1 INTEGER, col_2 TEXT, text_column TEXT)");
db.execSQL("CREATE VIRTUAL TABLE fts_example_table USING fts4 (content='example_table', text_column)");

Обратите внимание, что для этого нужно использовать FTS4, а не FTS3. FTS4 не поддерживается в Android до версии API версии 11. Вы можете либо (1) предоставить только функции поиска API >= 11, либо (2) использовать таблицу FTS3 (но это означает, что база данных будет больше, поскольку существует полный текстовый столбец в обеих базах данных).

Заполнение таблиц

db.execSQL("INSERT INTO example_table (col_1, col_2, text_column) VALUES ('3', 'apple', 'Hello. How are you?')");
db.execSQL("INSERT INTO example_table (col_1, col_2, text_column) VALUES ('24', 'car', 'Fine. Thank you.')");
db.execSQL("INSERT INTO example_table (col_1, col_2, text_column) VALUES ('13', 'book', 'This is an example.')");

(Опять же, есть более эффективные способы вставки, чем с execSQL. Я просто использую его для его удобочитаемости.)

Если вы попытались выполнить запрос FTS теперь на fts_example_table, вы не получите никаких результатов. Причина в том, что изменение одной таблицы автоматически не меняет другую таблицу. Вы должны вручную обновить таблицу FTS:

db.execSQL("INSERT INTO fts_example_table (docid, text_column) SELECT _id, text_column FROM example_table");

(docid похож на rowid для обычной таблицы.) Вы должны обязательно обновлять таблицу FTS (чтобы она могла обновлять индекс) каждый раз, когда вы вносили изменения (INSERT, DELETE, UPDATE) во внешнюю таблицу содержимого. Это может стать громоздким. Если вы делаете только предварительно заполненную базу данных, вы можете сделать

db.execSQL("INSERT INTO fts_example_table(fts_example_table) VALUES('rebuild')");

который перестроит всю таблицу. Это может быть медленным, но это не то, что вы хотите сделать после каждого небольшого изменения. Вы сделали бы это после завершения всех вставок во внешней таблице содержимого. Если вам необходимо автоматически синхронизировать базы данных, вы можете использовать triggers. Перейдите сюда и немного прокрутите список, чтобы найти маршруты.

Запросить базы данных

String[] selectionArgs = { searchString };
Cursor cursor = db.rawQuery("SELECT * FROM fts_example_table WHERE fts_example_table MATCH ?", selectionArgs);

Это то же самое, что и раньше, за исключением того, что на этот раз у вас есть доступ только к text_columndocid). Что делать, если вам нужно получить данные из других столбцов во внешней таблице контента? Поскольку таблица docid таблицы FTS соответствует таблице rowid (и в этом случае _id) таблицы внешнего контента, вы можете использовать соединение. (Спасибо этот ответ за помощь в этом.)

String sql = "SELECT * FROM example_table WHERE _id IN " +
        "(SELECT docid FROM fts_example_table WHERE fts_example_table MATCH ?)";
String[] selectionArgs = { searchString };
Cursor cursor = db.rawQuery(sql, selectionArgs);

Дополнительная литература

Внимательно изучите эти документы, чтобы увидеть другие способы использования виртуальных таблиц FTS:

Дополнительные примечания

Ответ 2

Не забывайте при использовании содержимого для перестройки таблицы fts.

Я делаю это с триггером при обновлении, вставке, удалении