【问题标题】:Querying a Full Text Search Table in Android Room Database在 Android 房间数据库中查询全文搜索表
【发布时间】:2021-08-09 13:45:16
【问题描述】:

我正在尝试使用 Android 的 Room 库 fts4 在我的应用中创建搜索功能。 由于某种原因,查询总是返回空结果。 而且我似乎无法弄清楚问题所在。 下面是我的 DAO 和实体数据类。 我在这里做错了吗?

@Dao
interface HymnDao {
    @Query("SELECT * FROM hymns_table")
    suspend fun getAllHymns(): List<HymnEntity>

    @Query("SELECT * FROM hymns_table WHERE :id = _id ")
    suspend fun getHymn(id: Int): HymnEntity

    @Query(
        """SELECT hymns_table.* 
                 FROM hymns_fts 
                 JOIN hymns_table ON (hymns_fts.rowid = _id )
                 WHERE hymns_fts MATCH :query """
    )
    suspend fun search(query: String): List<HymnEntity>
}

@Entity(tableName = "hymns_table")
data class HymnEntity(
    @PrimaryKey
    @ColumnInfo(name = "_id")
    val id: Int,
    val title: String,
    val author: String,
    val lyrics: String,
)

@Entity(tableName = "hymns_fts")
@Fts4(contentEntity = HymnEntity::class)
data class HymnFts(
    val title: String,
    val lyrics: String
)

【问题讨论】:

  • WHERE hymns_fts MATCH :query -- 这指定了一个表,而不是一个列。你确定这是有效的语法吗?
  • 我认为它应该与给定的查询匹配。
  • 至少这是我从谷歌谈话“房子里的房间”中了解到的。如果我错了,请纠正我。
  • @CommonsWare 如果指定了表,则将返回所有出现,无论列如何。请参阅sqlite.org/fts3.html#simple_fts_queries,例如它包括SELECT * FROM docs WHERE docs MATCH 'sqlite';

标签: android android-sqlite full-text-search android-room


【解决方案1】:

我认为您的问题是 FTS 实体必须具有 FTS 表所具有的 rowid 列的 val/var。只有在使用@ColumnInfo(name = "rowid") 时,val/var 才能以其他方式命名。 rowid 还必须使用@PrimaryKey 进行注释。

  • 根据SQLite FTS3 and FTS4 Extensions > 除了用户命名的列(如果没有指定模块参数作为 CREATE VIRTUAL TABLE 语句的一部分,则为“内容”列),每个 FTS 表都有一个“ rowid”列。

  • Android Developers - FTS4 > FTS 实体表总是有一个名为 rowid 的列,它等同于 INTEGER PRIMARY KEY 索引。因此,一个 FTS 实体只能有一个使用 PrimaryKey 注释的字段,它必须命名为 rowid 并且必须是 INTEGER 亲和性。该字段可以选择在类中省略,但仍可在查询中使用。

例如:-

@Entity(tableName = "hymns_fts")
@Fts4(contentEntity = HymnEntity::class)
data class HymnFts(
    @PrimaryKey
    @ColumnInfo(name = "rowid") // not required in this case but doesn't hurt
    val rowid: Long, //<< Required for FTS
    val title: String,
    val lyrics: String
)

工作示例 使用上面的代码和你的代码(为我的怪癖稍微修改)然后是下面的(为了简洁和方便在主线程上运行)然后:-

class MainActivity : AppCompatActivity() {

    lateinit var db: TheDatabase
    lateinit var dao: HymnDao
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        db = TheDatabase.getInstance(this)
        dao = db.getHymnDao()

        dao.insert(
            HymnEntity(title = "All things bright and beautiful", lyrics ="All things bright and beautiful,\n" +
                "All creatures great and small,\n" +
                "All things wise and wonderful:\n" +
                "The Lord God made them all.\n" +
                "\n" +
                "Each little flower that opens,\n" +
                "Each little bird that sings,\n" +
                "He made their glowing colors,\n" +
                "He made their tiny wings.\n")
        )
        dao.insert(HymnEntity( title = "Onward Christian Soldiers", lyrics = "Onward, Christian soldiers, marching as to war,\n" +
                "With the cross of Jesus going on before.\n" +
                "Christ, the royal Master, leads against the foe;\n" +
                "Forward into battle see His banners go!"))
        for(hymn: HymnEntity in dao.search("small")) {
            Log.d("HYMNINFO","Hymn is ${hymn.title}")
        }
    }
}

当运行结果时:-

D/HYMNINFO: Hymn is All things bright and beautiful

【讨论】:

  • 感谢您抽出宝贵时间 Mike。我会试一试并给您反馈。
  • 事实证明,这是因为我使用的是预填充的数据库。但是当我使用addCallback 方法添加回调时,我收到此错误SQL logic error or missing database (Sqlite code 1), (OS error - 2:No such file or directory。即使在清除应用数据并卸载应用之后。
  • @EyramMichael 听起来/看起来像是数据库文件的副本失败,因为数据库文件夹(在 data/data/package_name/ 中)不存在。也许使用应该正确处理副本的createFromAssetcreateFromFile。见developer.android.com/reference/androidx/room/…
  • @EyramMichael 此外,预填充的数据库是否包括触发器? Room 期望触发器(即 Room 构建)将 HymnEntity 表中的删除、更新和插入应用到 Hymns_fts 表。如果您编译并查看生成的 java,您可以看到 room 创建触发器作为 createAllTables 方法的一部分。如果没有这些等价物,那么 FTS 虚拟表中将没有任何内容。
  • 原始数据库不包含 Triggers ,room 为我在database_implfile 中创建了触发器。
【解决方案2】:

附加

考虑到评论:-

事实证明,这是因为我使用的是预填充的数据库。

这是一个基于上述答案的示例,但具有合适的预填充数据库。

合适的,是根据 Room 期望创建的,它本身基于实体。

  • 注意,由于原始答案用于提供另一个答案,因此数据库包含其他表和索引。

创建一个合适的数据库相对容易,就像你用实体和@Database(指适当的实体)编译项目(CTRL + F9),然后Room生成java(Android View显示了这一点)。与@Database 类名称相同并以_Impl 为后缀的文件有一个名为createAllTables 的方法,该方法可以很容易地在任何SQLite 工具中使用(假设该工具支持FTS)。

创建合适的预填充数据库

  1. 在 Android Studio 中找到生成的 java 文件 TheDatabase_Impl 和其中的createAllTables 方法:-

  1. 使用 SQLite 工具基本上是从生成的 java 复制 SQL,例如

:-

CREATE TABLE IF NOT EXISTS `hymns_table` (`_id` INTEGER, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `lyrics` TEXT NOT NULL, PRIMARY KEY(`_id`));
CREATE VIRTUAL TABLE IF NOT EXISTS `hymns_fts` USING FTS4(`title` TEXT NOT NULL, `lyrics` TEXT NOT NULL, content=`hymns_table`);
CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_hymns_fts_BEFORE_UPDATE BEFORE UPDATE ON `hymns_table` BEGIN DELETE FROM `hymns_fts` WHERE `docid`=OLD.`rowid`; END;
CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_hymns_fts_BEFORE_DELETE BEFORE DELETE ON `hymns_table` BEGIN DELETE FROM `hymns_fts` WHERE `docid`=OLD.`rowid`; END;
CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_hymns_fts_AFTER_UPDATE AFTER UPDATE ON `hymns_table` BEGIN INSERT INTO `hymns_fts`(`docid`, `title`, `lyrics`) VALUES (NEW.`rowid`, NEW.`title`, NEW.`lyrics`); END;
CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_hymns_fts_AFTER_INSERT AFTER INSERT ON `hymns_table` BEGIN INSERT INTO `hymns_fts`(`docid`, `title`, `lyrics`) VALUES (NEW.`rowid`, NEW.`title`, NEW.`lyrics`); END;

CREATE TABLE IF NOT EXISTS `application_table` (`id` INTEGER, `name` TEXT NOT NULL, PRIMARY KEY(`id`));
CREATE UNIQUE INDEX IF NOT EXISTS `index_application_table_name` ON `application_table` (`name`);
CREATE TABLE IF NOT EXISTS `brand_table` (`id` INTEGER, `path` TEXT NOT NULL, `code` TEXT NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`id`));
CREATE TABLE IF NOT EXISTS `Model` (`id` INTEGER, `path` TEXT NOT NULL, `code` TEXT NOT NULL, `value` TEXT NOT NULL, `brandCreatorId` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`brandCreatorId`) REFERENCES `brand_table`(`id`) ON UPDATE CASCADE ON DELETE CASCADE );
CREATE INDEX IF NOT EXISTS `index_Model_brandCreatorId` ON `Model` (`brandCreatorId`);
CREATE TABLE IF NOT EXISTS `ApplicationBrandCrossRef` (`appId` INTEGER NOT NULL, `brandId` INTEGER NOT NULL, PRIMARY KEY(`appId`, `brandId`), FOREIGN KEY(`appId`) REFERENCES `application_table`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`brandId`) REFERENCES `brand_table`(`id`) ON UPDATE CASCADE ON DELETE CASCADE );
CREATE INDEX IF NOT EXISTS `index_ApplicationBrandCrossRef_brandId` ON `ApplicationBrandCrossRef` (`brandId`);
  • 注意不要包含创建room_master_table 或将行插入表中的SQL。
  1. 在 SQLite 工具中填充数据库(为此示例插入了 2 行),例如

:-

INSERT INTO `hymns_table` (title,author,lyrics) VALUES
    ('All things bright and beautiful','Fred','All things bright and beautiful,\nAll creatures great and small,\nAll things wise and wonderful:\nThe Lord God made them all.\nEach little flower that opens,\nEach little bird that sings,\nHe made their glowing colors,\nHe made their tiny wings.\n'),
    ('Onward Christian Soldiers','Mary','Onward, Christian soldiers, marching as to war,\nWith the cross of Jesus going on before.\nChrist, the royal Master, leads against the foe;\nForward into battle see His banners go!\nblah the great')
;
  • 2 行添加了All things bright and beautifulOnward Christian Soldiers(后者有额外的一行blah the great,所以两者都有一个共同的词)
  1. 保存/关闭数据库,再次打开并保存以确保已保存。

  2. 在项目中创建 assets 文件夹并将数据库文件(如果存在 -wal 和 -shm 文件(如果数据库已关闭,则不应存在))复制到 assets 文件夹中。

  • 在示例中,文件名为 soanswers.db,因为这是我使用的连接。

例如:-

  1. 修改 Room.databaseBuilder 调用以包含 `.createFromAsset("the_filename_copied_into_the_assets_folder") 方法调用。

例如

        instance = Room.databaseBuilder(context, TheDatabase::class.java,"hymn.db")
                .createFromAsset("soanswers.db") //<<<<<< ADDED
                .allowMainThreadQueries()
                .build()
  1. 现在应该一切正常了。

在上一个答案的示例中,在执行上述步骤后,活动中使用的代码更改为:-

    db = TheDatabase.getInstance(this)
    dao = db.getHymnDao()

    for(hymn: HymnEntity in dao.search("small")) {
        Log.d("HYMNINFOR1","Hymn is ${hymn.title}")
    }
    for(hymn: HymnEntity in dao.search("on")) {
        Log.d("HYMNINFOR2","Hymn is ${hymn.title}")
    }
    for(hymn: HymnEntity in dao.search("great")) {
        Log.d("HYMNINFOR3","Hymn is ${hymn.title}")
    }

结果输出到日志:-

2021-08-11 11:08:44.691 D/HYMNINFOR1: Hymn is All things bright and beautiful

2021-08-11 11:08:44.693 D/HYMNINFOR2: Hymn is Onward Christian Soldiers

2021-08-11 11:08:44.694 D/HYMNINFOR3: Hymn is All things bright and beautiful
2021-08-11 11:08:44.694 D/HYMNINFOR3: Hymn is Onward Christian Soldiers

即查询的前两次调用找到唯一的赞美诗匹配,第三次匹配两首赞美诗(因此为什么将伟大的 wass 添加到 Onward Christian Soldiers)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-27
    • 2020-09-08
    相关资源
    最近更新 更多