В моем приложении пользователь может фильтровать свои данные, используя несколько фильтров в каждой комбинации (применять только один, несколько или ни одного).

До этого у меня был только один фильтр, поэтому каждый раз, когда он применялся, я переключал метод 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

Есть ли способ изменить запрос так, чтобы был один метод для всех комбинаций фильтров?

Обновление:

Это мой класс данных, экземпляры которого я хотел бы отфильтровать:

@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(),
)

Я хотел бы отфильтровать / проверить равенство числовых и логических свойств, проверить, содержат ли свойства списка указанную строку. По сути, я хотел бы иметь возможность фильтровать все, что могу. Если это невозможно, меня устроят хоть какие-то фильтры.

7
baltekg 25 Фев 2021 в 13:32

3 ответа

Лучший ответ

Основываясь на комментарии @CommonsWare, я попытался использовать RawQuery для достижения того, чего хотел.

Сначала нужно было создать класс данных Filters, который в будущем будет содержать все фильтры. Убрать один или добавить еще очень легко.

data class Filters(
    val query: String? = null,
    val isUploaded: Boolean? = null,
    // all the other filters
)

Функция, которая построит запрос и вернет результат из базы данных:

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

Я не знаю, как это будет работать, потому что запросы должны быть построены во время выполнения (но из некоторых тестов, которые я провел, я не заметил значительной потери производительности), но преимущество в том, что очень легко добавить другие фильтрует или удаляет существующие, и если применяется только один фильтр из многих, создать запрос действительно просто.

0
baltekg 6 Мар 2021 в 11:36

Вы можете использовать сложный запрос, упомянутый Timb здесь, но есть много нулевых проверок (сложные условия, влияющие на производительность запроса), я бы использовал RawQuery, который используется для динамического запроса. хотя в нем отсутствует подсветка синтаксиса, которую предоставляет @Query. Это Ссылка предоставляет образец. и если вы используете LiveData, добавьте свойство observedEntities аннотации @RawQuery для важных сущностей.

1
mohsen sameti 6 Мар 2021 в 11:23

Это зависит от того, согласны ли вы с довольно сложным запросом, но вот что я, вероятно, сделал бы. Создайте такой метод:

@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 для текстовых полей, а затем присоединиться к этой таблице и запустить строковые фильтры с Match и другими фильтрами, как я здесь. (Если вам нужно искать специальные символы в FTS4, вам необходимо настроить токенизаторы для таблицы)

3
TimB 2 Мар 2021 в 20:57