Android Room: как правильно использовать LIKE в запросах с параметрами?
Всем привет! Я новичок в Android-разработке на Kotlin и использую Room для работы с локальной базой данных. У меня есть сущность User, и я хочу реализовать поиск пользователей по имени с использованием SQL-оператора LIKE.
Вот моя DAO-сущность:
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE name LIKE '%:searchQuery%'")
suspend fun searchUsers(searchQuery: String): List<User>
}
Я вызываю этот метод из ViewModel:
class UserViewModel(private val userDao: UserDao) : ViewModel() {
fun search(query: String) {
viewModelScope.launch {
val users = userDao.searchUsers(query)
// Обработка результатов
}
}
}
Проблема в том, что запрос не возвращает никаких результатов, хотя в базе есть записи с именами, содержащими искомую строку. Я пробовал передавать разные значения — полные имена, части строк — ничего не работает. Подозреваю, что проблема в том, как я формирую SQL-запрос с LIKE. Может быть, Room не подставляет параметр в паттерн LIKE? Или нужно как-то экранировать символы? Буду благодарен за объяснение и рабочий пример.
Заранее спасибо!
<<Проблема: Room не подставляет параметр внутрь строки паттерна LIKE
Вы правы — это типичная ошибка новичков. Room обрабатывает параметры запроса как подготовленные операторы (prepared statements). Когда вы пишете LIKE '%:searchQuery%', Room видит это как строковый литерал '%:searchQuery%', а не как паттерн с подстановкой параметра. Символ : внутри одинарных кавычек не распознаётся как привязка параметра — это просто часть строки.
Правильное решение: конкатенация в SQL или передача готового паттерна
Вам нужно формировать паттерн LIKE за пределами запроса (на стороне Kotlin) и передавать его как готовый параметр. Вот два рабочих подхода:
Вариант 1: Конкатенация в SQL-запросе (рекомендуется)
Используйте SQL-функцию конкатенации || (стандарт SQL) или специфичный для SQLite оператор ||:
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE name LIKE '%' || :searchQuery || '%'")
suspend fun searchUsers(searchQuery: String): List<User>
}
Теперь searchQuery будет корректно подставлен как параметр, а паттерн соберётся на уровне SQL. Можно использовать и другой синтаксис:
@Query("SELECT * FROM users WHERE name LIKE :pattern")
suspend fun searchUsers(pattern: String): List<User>
Но тогда вы должны передавать готовый паттерн из вызывающего кода:
val pattern = "%$query%"
val users = userDao.searchUsers(pattern)
Вариант 2: Экранирование спецсимволов (для безопасности)
Если пользователь может ввести символы % или _, их нужно экранировать, чтобы они не интерпретировались как wildcard-символы SQL. Добавьте вспомогательную функцию:
fun String.escapeForLike(): String {
return this.replace("\\", "\\\\")
.replace("%", "\\%")
.replace("_", "\\_")
}
// В DAO:
@Query("SELECT * FROM users WHERE name LIKE :pattern ESCAPE '\\'")
suspend fun searchUsers(pattern: String): List<User>
И используйте так:
val safeQuery = query.escapeForLike()
val pattern = "%$safeQuery%"
val users = userDao.searchUsers(pattern)
Полный рабочий пример
// DAO
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE name LIKE '%' || :query || '%'")
suspend fun searchUsers(query: String): List<User>
}
// ViewModel
class UserViewModel(private val userDao: UserDao) : ViewModel() {
fun search(query: String) {
viewModelScope.launch {
try {
val users = userDao.searchUsers(query)
// Обработка результатов
} catch (e: Exception) {
// Логирование ошибки
}
}
}
}
Почему это работает?
- Room использует
SQLiteStatementдля подстановки параметров — это защищает от SQL-инъекций. - Параметр
:queryсвязывается с переданным значением, а символы%добавляются уже в SQL. - При таком подходе не нужно беспокоиться о кавычках — Room обрабатывает экранирование автоматически.
Если ваша версия Room поддерживает @RawQuery, можно использовать и его, но приведённый выше способ — самый простой и надёжный для LIKE-запросов.
Альтернативный подход: использование FTS (Full-Text Search) для сложного поиска
Если вам нужен более мощный поиск с учётом релевантности, опечаток или морфологии, рассмотрите использование FTS4 или FTS5 в SQLite. Room поддерживает FTS через специальные сущности.
Пример настройки FTS
@Entity(tableName = "users_fts")
@Fts4(contentEntity = User::class)
data class UserFts(
val name: String
)
@Dao
interface UserDao {
@Query("SELECT * FROM users_fts WHERE users_fts MATCH :query")
suspend fun searchUsersFts(query: String): List<User>
}
Вызывайте так:
val ftsQuery = query.replace(" ", "* ") + "*" // Префиксный поиск
val users = userDao.searchUsersFts(ftsQuery)
Когда использовать FTS?
- Когда нужен поиск по нескольким полям сразу.
- Для поддержки нечёткого поиска (например, "джон" найдёт "Джон", "джонни").
- Для больших объёмов данных — FTS работает быстрее, чем LIKE.
Однако для простого поиска по одному полю LIKE с конкатенацией — это самый лёгкий и понятный вариант. FTS добавляет сложность в виде отдельной таблицы и необходимости синхронизации данных.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги