我很难理解如何在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。

  • 您将如何设置表和/或FTS虚拟表?
  • 您将如何对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不是必需的,但是仍然很有帮助。
  • 最佳答案

    最基本的答案

    我在下面使用普通的sql,以便所有内容都尽可能清晰易读。在您的项目中,您可以使用Android便捷方法。下面使用的db对象是SQLiteDatabase的实例。

    Create FTS Table

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

    这可以在扩展的onCreate()类的SQLiteOpenHelper方法中进行。

    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#insertprepared statementsexecSQL更好。

    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来执行此操作。 Android在API版本11之前不支持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.')");
    

    (再次,插入比插入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)时,必须确保更新FTS表(以便它可以更新索引)。 。这可能会很麻烦。如果您只创建一个预先填充的数据库,则可以执行
    db.execSQL("INSERT INTO fts_example_table(fts_example_table) VALUES('rebuild')");
    

    这将重建整个表。不过,这可能会很慢,因此您不需要每次更改后都要做。您需要在完成外部内容表上的所有插入之后执行此操作。如果确实需要自动保持数据库同步,则可以使用triggersGo 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 SyntaxEnhanced Query Syntax。不幸的是,Android显然不支持增强查询语法(请参见herehereherehere)。这意味着将AND和OR混合变得很困难(要求使用 UNION 或检查 PRAGMA compile_options 似乎很困难)。非常不幸。如果此区域有更新,请添加评论。
  • 关于android - Android中的全文本搜索示例,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/29815248/

    10-11 22:27