Android中的全文搜索示例
Posted
技术标签:
【中文标题】Android中的全文搜索示例【英文标题】:Full text search example in Android 【发布时间】:2015-07-01 03:42:53 【问题描述】:我很难理解如何在 android 中使用全文搜索 (FTS)。我读过SQLite documentation on the FTS3 and FTS4 extensions。我知道it's possible to do on Android。但是,我很难找到任何我能理解的例子。
基本数据库模型
一个 SQLite 数据库表(名为 example_table
)有 4 列。但是,只有一列(名为text_column
)需要为全文搜索建立索引。 text_column
的每一行都包含长度从 0 到 1000 个字不等的文本。总行数大于 10,000。
text_column
执行 FTS 查询?
补充说明:
由于只需要为一列建立索引,因此仅使用 FTS 表(并删除example_table
)将是 inefficient for non-FTS queries。
对于如此大的表,不希望在 FTS 表中存储 text_column
的重复条目。 This post 建议使用 external content table。
外部内容表使用 FTS4,但 FTS4 是 not supported before Android API 11。答案可以假设 API >= 11,但评论支持较低版本的选项会很有帮助。
更改原始表中的数据不会自动更新 FTS 表(反之亦然)。对于这个基本示例,您的答案中不需要包含 triggers,但仍然会有所帮助。
【问题讨论】:
有据可查的问题,我反对你在这里得到的任意反对票。 【参考方案1】:最基本的答案
我使用下面的普通 sql,以便一切都尽可能清晰易读。在您的项目中,您可以使用 Android 便捷方法。下面使用的db
对象是SQLiteDatabase 的一个实例。
Create FTS Table
db.execSQL("CREATE VIRTUAL TABLE fts_table USING fts3 ( col_1, col_2, text_column )");
这可以放在扩展 SQLiteOpenHelper
类的 onCreate()
方法中。
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.')");
使用SQLiteDatabase#insert 或prepared statements 比使用execSQL
更好。
Query FTS Table
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 表将包含索引,但不包含常规表中的实际数据。相反,它将有一个指向常规表内容的链接。这称为external content table。
创建表格
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 来执行此操作。 API 版本 11 之前的 Android 不支持 FTS4。您可以 (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_example_table
上进行 FTS 查询,您将得不到任何结果。原因是更改一张表不会自动更改另一张表。您必须手动更新 FTS 表:
db.execSQL("INSERT INTO fts_example_table (docid, text_column) SELECT _id, text_column FROM example_table");
(docid
类似于常规表的rowid
。)每次进行更改(INSERT、DELETE、 UPDATE)到外部内容表。这会变得很麻烦。如果你只是制作一个预填充的数据库,你可以这样做
db.execSQL("INSERT INTO fts_example_table(fts_example_table) VALUES('rebuild')");
这将重建整个表。不过,这可能会很慢,所以这不是您在每次小改动后都想做的事情。您将在完成外部内容表上的所有插入后执行此操作。如果确实需要自动保持数据库同步,可以使用triggers。 Go here 并向下滚动一点以查找路线。
查询数据库
String[] selectionArgs = searchString ;
Cursor cursor = db.rawQuery("SELECT * FROM fts_example_table WHERE fts_example_table MATCH ?", selectionArgs);
这和以前一样,只是这次你只能访问text_column
(和docid
)。如果您需要从外部内容表中的其他列获取数据怎么办?由于 FTS 表的 docid
与外部内容表的 rowid
(在本例中为 _id
)匹配,因此您可以使用连接。 (感谢this answer 的帮助。)
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 虚拟表的其他方法:
SQLite FTS3 and FTS4 Extensions(SQLite 文档) Storing and Searching for Data(Android 文档)附加说明
SQLite FTS 查询中的集合运算符(AND、OR、NOT)有Standard Query Syntax 和Enhanced Query Syntax。不幸的是,Android 显然不支持增强型查询语法(参见here、here、here 和here)。这意味着混合 AND 和 OR 变得困难(似乎需要使用UNION
或检查PRAGMA compile_options
)。非常不幸。如果该区域有更新,请添加评论。
【讨论】:
事实上,如果您以您指定的方式使用 fts 表(从非 fts 表中选择,其中 _id 包含在 fts 表匹配返回的 docid 集中),您可以使用 content="" 节省空间。这将创建全文索引而不复制内容。见Contentless FTS4 Tables FTS4 内容选项的添加不早于 SQLite 3.7.9 (sqlite.org/releaselog/3_7_11.html),这意味着它在 Android API 16 之前不可用。SQLiteDatabase 将在尝试使用时抛出。 如何通过此查询获得半字匹配? @HiteshDanidhariya,这不是进行部分单词匹配吗?抱歉,我已经有一段时间没有做这个了,但我认为它已经做到了。 @suragch 得到了解决方案。必须在 searchString 和Thanks 之后添加“*”。您的回答对我帮助很大。 :)【参考方案2】:在使用 content from 重建 fts 表时不要忘记。
我使用更新、插入、删除触发器来执行此操作
【讨论】:
INSERT INTO foo_fts VALUES("rebuild")
以上是关于Android中的全文搜索示例的主要内容,如果未能解决你的问题,请参考以下文章