具有多个非强制性过滤器的 Room DAO
Room DAO with multiple non-obligatory filters
在我的应用程序中,用户可以在每个组合中使用多个过滤器来过滤他们的数据(仅应用一个、多个或 none)。
在此之前,我只有一个过滤器,所以每次应用它时,我都在切换 DAO 方法。
现在我有 6 个过滤器,所以有几十种组合,因此不可能为每种组合创建一个方法。我也不能对我的数据库进行太多修改,因为它已经可供用户使用。
我当前的代码如下所示:
@Query("SELECT id, name, date FROM UserData")
fun getAll(): DataSource.Factory<Int, UserItem> //no filters
@Query("SELECT id, name, date FROM UserData WHERE name LIKE '%' || :search || '%'")
fun getAllFiltered(query: String): DataSource.Factory<Int, UserItem> //one filter applied
有没有办法修改查询,以便所有过滤器组合都有一个方法?
更新:
这是我的数据class,我想过滤哪些实例:
@Entity(tableName = "UserItem")
data class UserItem(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
val id: Long? = null,
@ColumnInfo(name = "created_at")
val createdAt: Date,
@ColumnInfo(name = "is_uploaded")
val isUploaded: Boolean,
@ColumnInfo(name = "name")
val name: String,
@ColumnInfo(name = "item_sum")
val sum: Int = 0,
@ColumnInfo(name = "tags")
val tags: List<String> = listOf(),
)
我想filter/check数值和布尔属性的相等性,检查列表属性是否包含指定的字符串。基本上,我希望能够过滤我能过滤的一切。如果不可能,我至少对一些过滤器感到满意。
这取决于您是否接受稍微复杂的查询,但我可能会这样做。创建这样的方法:
@Query("""
SELECT id, name, date FROM UserData WHERE
(:nameQuery IS NULL OR name LIKE '%' || :nameQuery || '%') AND
(:isUploaded IS NULL OR is_uploaded = :isUploaded) AND
(:sum IS NULL OR item_sum = sum)
""")
fun getAllFiltered(nameQuery: String?, isUploaded: Boolean?, sum: Int?
): DataSource.Factory<Int, UserItem>
如果特定字段没有筛选器,现在只需将 null 作为参数传递。
我不知道您是如何将 List<> 存储在数据库中的,但也许您可以像搜索字符串一样对该字段进行搜索(例如名称字段)
如果您想进一步提高搜索速度,可以为文本字段设置 FTS4 table,然后将 table 和 运行 字符串过滤器与 Match 连接起来和我在这里使用的其他过滤器。 (如果您需要在 FTS4 中搜索特殊字符,您必须为 table 设置分词器)
您可以使用Timb but there is a lot of null checks (complex where clauses affect performance of query), I would use RawQuery which is used for dynamic query. although it lacks the syntax highlighting that @Query
provides. This Link提供的示例中提到的复杂查询。如果您使用 LiveData
,请为重要的实体添加 observedEntities
属性 或 @RawQuery
注释。
根据@CommonsWare 的评论,我尝试使用 RawQuery 来实现我想要的。
首先想到的是创建过滤器数据 class 将来会保留
所有的过滤器。删除一个或添加多个真的很容易。
data class Filters(
val query: String? = null,
val isUploaded: Boolean? = null,
// all the other filters
)
将构建查询的函数和 return 来自数据库的结果:
fun getAllFiltered(filters: Filters): DataSource.Factory<Int, UserItem> {
val conditions = mutableListOf<Pair<String, Any?>>()
with(filters) {
query?.let { conditions.add("name LIKE '%' || ? || '%'" to it) }
isUploaded?.let { conditions.add("is_uploaded = ${it.toInt()}" to null) }
// "subqueries" to filter specific field
}
if (conditions.isEmpty())
return getAll()
val conditionsMerged = conditions.joinToString(separator = " AND ") { it.first }
val bindArgs = conditions.mapNotNull { it.second }
val query = SimpleSQLiteQuery(
"SELECT id, name, date FROM UserData WHERE $conditionsMerged",
bindArgs.toTypedArray()
)
return getAllFiltered(query)
}
@RawQuery(observedEntities = [UserItem::class])
fun getAllFiltered(query: SupportSQLiteQuery): DataSource.Factory<Int, UserItem>
private fun Boolean.toInt() = if (this) 1 else 0
我不知道它会如何执行,因为必须在运行时构建查询(但从我所做的一些测试中,我没有注意到太多的性能损失),但优点是它是添加其他筛选器或删除现有筛选器非常容易,如果仅应用多个筛选器中的一个,创建的查询非常简单。
在我的应用程序中,用户可以在每个组合中使用多个过滤器来过滤他们的数据(仅应用一个、多个或 none)。
在此之前,我只有一个过滤器,所以每次应用它时,我都在切换 DAO 方法。 现在我有 6 个过滤器,所以有几十种组合,因此不可能为每种组合创建一个方法。我也不能对我的数据库进行太多修改,因为它已经可供用户使用。
我当前的代码如下所示:
@Query("SELECT id, name, date FROM UserData")
fun getAll(): DataSource.Factory<Int, UserItem> //no filters
@Query("SELECT id, name, date FROM UserData WHERE name LIKE '%' || :search || '%'")
fun getAllFiltered(query: String): DataSource.Factory<Int, UserItem> //one filter applied
有没有办法修改查询,以便所有过滤器组合都有一个方法?
更新:
这是我的数据class,我想过滤哪些实例:
@Entity(tableName = "UserItem")
data class UserItem(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
val id: Long? = null,
@ColumnInfo(name = "created_at")
val createdAt: Date,
@ColumnInfo(name = "is_uploaded")
val isUploaded: Boolean,
@ColumnInfo(name = "name")
val name: String,
@ColumnInfo(name = "item_sum")
val sum: Int = 0,
@ColumnInfo(name = "tags")
val tags: List<String> = listOf(),
)
我想filter/check数值和布尔属性的相等性,检查列表属性是否包含指定的字符串。基本上,我希望能够过滤我能过滤的一切。如果不可能,我至少对一些过滤器感到满意。
这取决于您是否接受稍微复杂的查询,但我可能会这样做。创建这样的方法:
@Query("""
SELECT id, name, date FROM UserData WHERE
(:nameQuery IS NULL OR name LIKE '%' || :nameQuery || '%') AND
(:isUploaded IS NULL OR is_uploaded = :isUploaded) AND
(:sum IS NULL OR item_sum = sum)
""")
fun getAllFiltered(nameQuery: String?, isUploaded: Boolean?, sum: Int?
): DataSource.Factory<Int, UserItem>
如果特定字段没有筛选器,现在只需将 null 作为参数传递。
我不知道您是如何将 List<> 存储在数据库中的,但也许您可以像搜索字符串一样对该字段进行搜索(例如名称字段)
如果您想进一步提高搜索速度,可以为文本字段设置 FTS4 table,然后将 table 和 运行 字符串过滤器与 Match 连接起来和我在这里使用的其他过滤器。 (如果您需要在 FTS4 中搜索特殊字符,您必须为 table 设置分词器)
您可以使用Timb @Query
provides. This Link提供的示例中提到的复杂查询。如果您使用 LiveData
,请为重要的实体添加 observedEntities
属性 或 @RawQuery
注释。
根据@CommonsWare 的评论,我尝试使用 RawQuery 来实现我想要的。
首先想到的是创建过滤器数据 class 将来会保留 所有的过滤器。删除一个或添加多个真的很容易。
data class Filters(
val query: String? = null,
val isUploaded: Boolean? = null,
// all the other filters
)
将构建查询的函数和 return 来自数据库的结果:
fun getAllFiltered(filters: Filters): DataSource.Factory<Int, UserItem> {
val conditions = mutableListOf<Pair<String, Any?>>()
with(filters) {
query?.let { conditions.add("name LIKE '%' || ? || '%'" to it) }
isUploaded?.let { conditions.add("is_uploaded = ${it.toInt()}" to null) }
// "subqueries" to filter specific field
}
if (conditions.isEmpty())
return getAll()
val conditionsMerged = conditions.joinToString(separator = " AND ") { it.first }
val bindArgs = conditions.mapNotNull { it.second }
val query = SimpleSQLiteQuery(
"SELECT id, name, date FROM UserData WHERE $conditionsMerged",
bindArgs.toTypedArray()
)
return getAllFiltered(query)
}
@RawQuery(observedEntities = [UserItem::class])
fun getAllFiltered(query: SupportSQLiteQuery): DataSource.Factory<Int, UserItem>
private fun Boolean.toInt() = if (this) 1 else 0
我不知道它会如何执行,因为必须在运行时构建查询(但从我所做的一些测试中,我没有注意到太多的性能损失),但优点是它是添加其他筛选器或删除现有筛选器非常容易,如果仅应用多个筛选器中的一个,创建的查询非常简单。