Пример полнотекстового поиска в Android

87

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

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

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

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

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

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

Ответы:

117

Самый простой ответ

Я использую простой 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 # . Обратите внимание на MATCHключевое слово.

Более полный ответ

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

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

введите описание изображения здесь

Создайте таблицы

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 до версии 11 API. Вы можете либо (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.')");

(Опять же, есть лучшие способы вставки do, чем с 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')");

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

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

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:

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

Suragch
источник
1
Фактически, если вы используете таблицу fts указанным вами способом (выбирая из таблицы non-fts, где _id содержится в наборе docid, возвращенном совпадением таблицы fts), вы можете сэкономить место, используя content = "" . Это создаст полнотекстовый индекс без дублирования содержимого. См. Таблицы Contentless FTS4
astyanaxas
Опция содержимого FTS4 была добавлена ​​не ранее, чем в SQLite 3.7.9 ( sqlite.org/releaselog/3_7_11.html ), что означает, что она недоступна до Android API 16. SQLiteDatabase выдает ошибку при попытке использования.
Knuckles
Как мне получить совпадение половинного слова с помощью этого запроса?
Hitesh Danidhariya
@HiteshDanidhariya, разве это не частичное сопоставление слов? Извините, я давно не работал над этим, но я думал, что он уже сделал это.
Suragch
@suragch Получил решение. Пришлось добавить "*" после searchString и "Спасибо". Ваш ответ мне очень помог. :)
Hitesh Danidhariya
3

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

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

Джеймс Киплинг
источник
INSERT INTO foo_fts VALUES("rebuild")
Джеймс Киплинг