Приклад повнотекстового пошуку в Android


88

Мені важко зрозуміти, як використовувати повнотекстовий пошук (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 (і навпаки). Включення тригерів у вашу відповідь не є необхідним для цього базового прикладу, але все ж було б корисно.

3
Добре задокументоване запитання, я противлююся довільному голосуванню, яке ви отримали тут.
Мекап

Відповіді:


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.')");

(Знову ж таки, є кращі способи вставки вставки, ніж з 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 (щоб вона могла оновити індекс) кожного разу, коли ви вносите зміни (ВСТАВИТИ, ВИДАЛИТИ, ОНОВИТИ) до зовнішньої таблиці вмісту. Це може стати громіздким. Якщо ви створюєте лише попередньо заповнену базу даних, ви можете це зробити

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:

додаткові нотатки


1
Насправді, якщо ви використовуєте таблицю fts способом, яким ви вказали (вибираючи з таблиці, що не є fts, де _id міститься в наборі docid, що повертається таблицею fts), ви можете заощадити простір, використовуючи content = "" . Це створить повнотекстовий індекс без дублювання вмісту. Див. Безмісні таблиці FTS4
astyanaxas

Параметр вмісту FTS4 був доданий не раніше, ніж у SQLite 3.7.9 ( sqlite.org/releaselog/3_7_11.html ), що означає, що він недоступний до Android API 16. SQLiteDatabase перейде на спробу використання.
Knuckles

Як отримати відповідність із півслова за допомогою цього запиту?
Хітеш Данідгарія

@HiteshDanidhariya, хіба це не означає часткового збігу слів? Вибачте, вже давно я не працював над цим, але я думав, що це вже зробив.
Сурагч

@suragch Отримав рішення. Довелося додати "*" після searchString та Thanks. Ваша відповідь мені так допомогла. :)
Хітеш Данідгарія

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.