Android 中的全文搜索示例

我很难理解如何在 Android 中使用全文搜索(FTS)。我读过 关于 FTS3和 FTS4扩展的 SQLite 文档。我知道 在安卓系统上是可行的。然而,我很难找到任何我能理解的例子。

基本数据库模型

SQLite 数据库表(名为 example_table)有4列。但是,只有一列(名为 text_column)需要为全文搜索建立索引。text_column的每一行都包含长度从0到1000个单词不等的文本。行的总数大于10,000。

  • 如何设置表和/或 FTS 虚拟表?
  • 如何在 text_column上执行 FTS 查询?

附加说明:

  • 因为只需要索引一个列,所以只有使用 FTS 表(并删除 example_table)才是 非 FTS 查询效率低下
  • 对于如此大的表,在 FTS 表中存储重复的 text_column条目是不可取的。这个帖子建议使用 外部内容表外部内容表
  • 外部内容表使用 FTS4,但 FTS4是 在 Android API 11之前不支持。答案可以假设 API > = 11,但是注释支持较低版本的选项会很有帮助。
  • 更改原始表中的数据不会自动更新 FTS 表(反之亦然)。在你的答案中包含 触发器对于这个基本的例子来说是不必要的,但是仍然是有帮助的。
32536 次浏览

Most Basic Answer

I'm using the plain sql below so that everything is as clear and readable as possible. In your project you can use the Android convenience methods. The db object used below is an instance of SQLiteDatabase.

Create FTS Table

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

This could go in the onCreate() method of your extended SQLiteOpenHelper class.

Populate FTS Table

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

It would be better to use SQLiteDatabase#insert or prepared statements than execSQL.

Query FTS Table

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

You could also use the SQLiteDatabase#query method. Note the MATCH keyword.

Fuller Answer

The virtual FTS table above has a problem with it. Every column is indexed, but this is a waste of space and resources if some columns don't need to be indexed. The only column that needs an FTS index is probably the text_column.

To solve this problem we will use a combination of a regular table and a virtual FTS table. The FTS table will contain the index but none of the actual data from the regular table. Instead it will have a link to the content of the regular table. This is called an external content table.

enter image description here

Create the Tables

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

Notice that we have to use FTS4 to do this rather than FTS3. FTS4 is not supported in Android before API version 11. You could either (1) only provide search functionality for API >= 11, or (2) use an FTS3 table (but this means the database will be larger because the full text column exists in both databases).

Populate the Tables

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

(Again, there are better ways in do inserts than with execSQL. I am just using it for its readability.)

If you tried to do an FTS query now on fts_example_table you would get no results. The reason is that changing one table does not automatically change the other table. You have to manually update the FTS table:

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

(The docid is like the rowid for a regular table.) You have to make sure to update the FTS table (so that it can update the index) every time you make a change (INSERT, DELETE, UPDATE) to the external content table. This can get cumbersome. If you are only making a prepopulated database, you can do

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

which will rebuild the whole table. This can be slow, though, so it is not something you want to do after every little change. You would do it after finishing all the inserts on the external content table. If you do need to keep the databases in sync automatically, you can use triggers. Go here and scroll down a little to find directions.

Query the Databases

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

This is the same as before, except this time you only have access to text_column (and docid). What if you need to get data from other columns in the external content table? Since the docid of the FTS table matches the rowid (and in this case _id) of the external content table, you can use a join. (Thanks to this answer for help with that.)

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);

Further Reading

Go through these documents carefully to see other ways of using FTS virtual tables:

Additional Notes

Don't forget when using content from to rebuild the fts table.

I do this with a trigger on update, insert, delete