Как динамически запрашивать базу данных комнаты во время выполнения?

Эта проблема

Можно ли построить запрос во время выполнения?


Случай использования

@Query("SELECT * FROM playlist " +
        "WHERE playlist_title LIKE '% :playlistTitle %' " +
        "GROUP BY playlist_title " +
        "ORDER BY playlist_title " +
        "LIMIT :limit")
 List<IPlaylist> searchPlaylists(String playlistTitle, int limit);

limit часть является необязательной. То есть он должен иметь возможность выполнять один и тот же запрос с ограничением или без него.


Более сложный вариант использования

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

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

Ответ 1

В моем опыте (коротком) использовании Room это невозможно, а не из-за ограничения номера, но, как неявно комментирует @CommonsWare, ограничение на SQLite. Вам нужны два запроса, и поэтому два метода в вашем DAO.

У меня бы получилось что-то вроде:

@Query("SELECT * FROM playlist " +
    "WHERE playlist_title LIKE '% :playlistTitle %' " +
    "GROUP BY playlist_title " +
    "ORDER BY playlist_title " +
    "LIMIT :limit")
List<IPlaylist> searchPlaylists(String playlistTitle, int limit);

@Query("SELECT * FROM playlist " +
    "WHERE playlist_title LIKE '% :playlistTitle %' " +
    "GROUP BY playlist_title " +
    "ORDER BY playlist_title ")
List<IPlaylist> searchPlaylists(String playlistTitle);

Затем в другом месте вы выполните обход:

if (limit.isPresent()) {
   return playlistDao.searchPlaylists(title, limit.get());
} else {
   return playlistDao.searchPlaylists(title);
}

Это лучший вариант, о котором я могу думать в данный момент.

Ответ 2

Вместо того, чтобы писать несколько запросов, я передаю отрицательное значение в предложение limit. Потому что, если есть изменение в запросе, я должен обновить оба запроса, который более подвержен ошибкам.

Официальный документ → Если выражение LIMIT имеет отрицательное значение, то нет верхней границы числа возвращаемых строк. вы можете найти его здесь https://sqlite.org/lang_select.html и прочитать раздел о предложении limit.

Так что я бы сделал что-то подобное,

@Query("SELECT * FROM playlist " +
    "WHERE playlist_title LIKE '% :playlistTitle %' " +
    "GROUP BY playlist_title " +
    "ORDER BY playlist_title " +
    "LIMIT :limit")
List<IPlaylist> searchPlaylists(String playlistTitle, int limit);

и передайте отрицательный, если вы не хотите применять фильтр.

return playlistDao.searchPlaylists(title, limit.isPresent() ? limit.get() : -1)

Это работает в моем случае.

Обновлено [21 декабря 2018]

В случае, если вы используете kotlin, используйте значение по умолчанию.

@JvmOverloads
@Query("SELECT * FROM playlist " +
        "WHERE playlist_title LIKE '% :playlistTitle %' " +
        "GROUP BY playlist_title " +
        "ORDER BY playlist_title " +
        "LIMIT :limit")
fun searchPlaylists(playlistTitle: String, limit: Int = -1): List<IPlaylist>

@JvmOverloads чтобы сделать его совместимым с Java. Он генерирует два отдельных метода для Java.

Ответ 3

В комнате нет чего-то вроде необязательного параметра, но есть аннотация @RawQuery, где вы можете передать запрос как String, чтобы вы могли построить свой SQL-запрос во время выполнения. Думаю, это сработает для вас.

Вот пример из официальной документации:

@Dao
 interface RawDao {
     @RawQuery
     User getUser(String query);
 }

И вот как вы можете его использовать:

User user = rawDao.getUser("SELECT * FROM User WHERE id = 3 LIMIT 1");

Важно: Методы RawQuery должны возвращать непустой тип

Важно: Это доступно в Room 1.1.0-alpha3

Ответ 4

Room поддерживает аннотацию @RawQuery для построения запросов во время выполнения.


Шаг 1: Сделать метод DAO

Пометьте метод DAO аннотацией @RawQuery вместо обычной @RawQuery.

@Dao
interface BooksDao{
    @RawQuery
    List<Book> getBooks(SupportSQLiteQuery query);
}


Шаг 2: Построить запрос

Комната использует подготовленные заявления для проверки безопасности и времени компиляции. Поэтому при построении запросов нам нужно хранить строку запроса и параметры привязки отдельно.

В этом примере я использую переменную queryString для строки запроса и args для параметров связывания.

(Обратите внимание, что я использовал текстовый редактор для написания кода. Поэтому могут быть опечатки или простые синтаксические ошибки. Если вы найдете что-нибудь, сообщите мне об этом в комментариях или отредактируйте сообщение.)

// Query string
String queryString = new String();

// List of bind parameters
List<Object> args = new ArrayList();

boolean containsCondition = false;

// Beginning of query string
queryString += "SELECT * FROM BOOKS";

// Optional parts are added to query string and to args upon here

if(!authorName.isEmpty()){
    queryString += " WHERE";
    queryString += " author_name LIKE ?%";
    args.add(authorName);
    containsCondition = true;
}

if(fromDate!=null){

    if (containsCondition) {
        queryString += " AND";
    } else {
        queryString += " WHERE";
        containsCondition = true;
    }

    queryString += " publication_date AFTER ?";
    args.add(fromDate.getTime());
}

if(toDate!=null){

    if (containsCondition) {
        queryString += " AND";
    } else {
        queryString += " WHERE";
        containsCondition = true;
    }

    queryString += " publication_date BEFORE ?";
    args.add(toDate.getTime());
}

// End of query string
queryString += ";";


Шаг 3: Выполнить запрос

SimpleSQLiteQuery query = new SimpleSQLiteQuery(queryString, args.toArray());
List<Book> result = booksDao.getBooks(query);




Заметки

  • Как и обычный Query, RawQuery поддерживает возврат необработанных курсоров, сущностей, POJO и POJO со встроенными полями.
  • RawQuery поддерживает отношения

Ответ 5

Используйте SupportSQLiteQuery.

https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteQuery

Последний выпуск 1.1.1 теперь использует SupportSQLiteQuery.

Запрос с типизированными привязками. Лучше использовать этот API вместо rawQuery (String, String []), поскольку он позволяет привязывать безопасные параметры типа.

@Dao
     interface RawDao {
         @RawQuery(observedEntities = User.class)
         LiveData<List<User>> getUsers(SupportSQLiteQuery query);
     }

Использование:

     LiveData<List<User>> liveUsers = rawDao.getUsers( new 
SimpleSQLiteQuery("SELECT * FROM User ORDER BY name DESC"));

Обновите ваш gradle до 1.1.1

implementation 'android.arch.persistence.room:runtime:1.1.1'
implementation 'android.arch.lifecycle:extensions:1.1.1'
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"

Примечание: если вы обновляетесь до 1.1.1 и используете String вместо SupportSQLiteQuery,

вы получите ошибку:

RawQuery больше не позволяет передавать строку. Пожалуйста, используйте android.arch.persistence.db.SupportSQLiteQuery.

Использование SupportSQLiteQuery, как указано выше, решит проблему.

Примечание: убедитесь, что вы передали параметр запроса SupportSQLiteQuery, иначе вы получите эту ошибку:

Методы RawQuery должны иметь 1 и только 1 параметр с типом String или SupportSQLiteQuery

Ответ 6

Сделай это проще. Я покажу вам пример, используя предложение where с использованием двух переменных. Сделай это так:

  @Query("SELECT * FROM Student WHERE stdName1= :myname AND stdId1=:myid")
List<Student> fetchAllData(String myname,int myid);

stdName1 и stdId1 являются именами столбцов

Ответ 7

@Андерсон К и @Джанки Сориано, Я согласен с @CommonsWare,

Здесь есть некоторые ограничения в библиотеке комнат, а также мы можем написать полностью динамический запрос в базе данных комнаты с помощью @query() поддержки SQLite База данных

String mQuery = "SELECT * FROM foobar WHERE columnName1 IN ('value_1','value_2') and columnName2 In('value_3','value_4')";

AppDatabase appDatabase = Room.databaseBuilder(getApplicationContext(),
        AppDatabase.class, "database-name").build();

Cursor mCursor = AppDatabase.getAppDatabase(context).getOpenHelper().getReadableDatabase().query(myQuery);

Теперь вы можете преобразовать данные курсора по строке в класс POJO.