在房间查询中获取可变参数大小

Get vararg parameter size in Room Query

假设我有一个数据库,其中包含两个主要实体(歌曲和标签)以及它们之间的多对多关系。使用 Room,我想通过名称查询具有一系列标签(所有标签)的歌曲。

因此,在交叉引用 table (SongTagCrossRef) 中给出此示例数据:

Song Tag
song1 tag1
song1 tag2
song1 tag3
song2 tag2
song3 tag2
song3 tag3

如果我输入 tag1 和 tag2,我希望查询 return 只有 song1,因为它是唯一与两者相关的歌曲。

我在相应的道中想出了这个@Query

@Query("""
    SELECT s.* FROM Song s
    JOIN SongTagCrossRef st ON s.song_id = st.song_id
    JOIN Tag t ON st.tag_id = t.tag_id
    WHERE t.name IN (:tagNames)
    GROUP BY s.song_id
    HAVING COUNT(*) = (SELECT COUNT(*) FROM Tag WHERE name IN (:tagNames))
""")
fun getSongsWithAllOfTheTagsByName(vararg tagNames: String): List<SongEntity>

由于我无法访问@Query中的tagNames.size,我不得不使用子查询来人为地获取它。这个子查询不应该太重,但以某种方式访问​​ tagNames.size.

总是更好

在阅读 的答案后,我一直在尝试创建一个 @RawQuery 并从一个只需要 tagNames 的函数中调用它,大致如下:

@RawQuery
fun getSongsWithAllOfTheTagsByName(query: SupportSQLiteQuery): List<SongEntity>

fun getSongsWithAllOfTheTagsByName(vararg tagNames: String): List<SongEntity> {
    val query = SimpleSQLiteQuery("""
        SELECT s.* FROM Song s
        JOIN SongTagCrossRef st ON s.song_id = st.song_id
        JOIN Tag t ON st.tag_id = t.tag_id
        WHERE t.name IN (?)
        GROUP BY s.song_id
        HAVING COUNT(*) = ?
    """, arrayOf(tagNames, tagNames.size))
    return getSongsWithAllOfTheTagsByName(query)
}

(仅将 tagNames 转换为它实际可以吞咽的东西)

但我放弃了这种方法,因为我不想公开接受查询的函数。

是否有更简单、更优雅的方式来编写此查询?

我终于做到了,所以我想分享一下我的发现。它实际上不是很简单,但它确实有效。

通过 SQLite 文档,我发现了 JSON1 extension and more specifically the json_array() and json_array_length() 函数。

但是,要使用此扩展名,必须使用 CommonsWare points out in and Hooman summarises , Requery's standalone library,通过 RequerySQLiteOpenHelperFactory

总结:

build.gradle 文件

dependencies {
    ...
    implementation 'com.github.requery:sqlite-android:3.36.0'
    ...
}

房间数据库class

Room.databaseBuilder(...)
    ...
    .openHelperFactory(RequerySQLiteOpenHelperFactory())
    ...
    .build()

道接口

@Query("""
    SELECT s.* FROM Song s
    JOIN SongTagCrossRef st ON s.song_id = st.song_id
    JOIN Tag t ON st.tag_id = t.tag_id
    WHERE t.name IN (:tagNames)
    GROUP BY s.song_id
    HAVING COUNT(*) = JSON_ARRAY_LENGTH(JSON_ARRAY(:tagNames))
""")
fun getSongsWithAllOfTheTagsByName(vararg tagNames: String): List<SongEntity>