Dynamic Order By using Room - android

I want to create a dynamic query using room so that in one case the query returns a particular order type and during runtime if the order type changes then a new query is created and the data is returned ordered according to this type.
I am returning a DataSource.Factory object using Room.
I am using the below statement to process my query:-
if(getSortOrderType().equals(context.getString(R.string.sortByPopular))) {
dbSortType = "popularity";
} else {
dbSortType = "vote_average";
}
movieLiveData =
new LivePagedListBuilder<>(MovieDatabase
.getMovieDbInstance(context)
.getMovieDao().getAllMovies(new
SimpleSQLiteQuery("SELECT * FROM main_movie ORDER BY ? DESC",
new Object[]{dbSortType})), config)
.setBoundaryCallback(movieModelBoundaryCallback)
.build();
But, during runtime I see that the data being returned is ordered by the already set Primary Key i.e id and not according to this type that I am constructing in the above statement.
How to use the statement to return the result sorted by the sort type selected.
The Dao method used is:-
#RawQuery(observedEntities = MovieModel.class)
DataSource.Factory<Integer,MovieModel> getAllMovies(SupportSQLiteQuery query);

Okay so, I have found a simple answer to that.
I just replaced the statement --
new SimpleSQLiteQuery("SELECT * FROM main_movie ORDER BY ? DESC", new Object[]{dbSortType})
to this:-
new SimpleSQLiteQuery("SELECT * FROM main_movie ORDER BY "+ dbSortType + " DESC"))

First of all check that getAllMovies function in your DAO is like this:
#RawQuery
fun getAllMovies(query: SupportSQLiteQuery): List<MainMovie>
and if your return type is observable types like liveData, PagingSource, etc. :
#RawQuery(observedEntities = [MainMovie::class])
fun getAllMovies(query: SupportSQLiteQuery): PagingSource<Int, MainMovie>
then use this:
val myQuery=SimpleSQLiteQuery("SELECT * FROM main_movie ORDER BY "+ dbSortType + " DESC")
getMovieDao(). getAllMovies(myQuery)

Related

matching multiple title in single query using like keyword

matching multiple title in single query using like keyword
I am trying to get all records if that matches with given titles.
below is the structure of database please see
database screenshot
when i pass single like query it returns data
#Query("SELECT * FROM task WHERE task_tags LIKE '%\"title\":\"Priority\"%'")
when i try to generate query dynamically to search multiple match it return 0 data
val stringBuilder = StringBuilder()
for (i in 0 until tags.size) {
val firstQuery = "%\"title\":\"Priority\"%"
if (i == 0) {
stringBuilder.append(firstQuery)
} else stringBuilder.append(" OR '%\"title\":\"${tags[i].title}\"%'")
}
this is function I have made
#Query("SELECT * FROM task WHERE task_tags LIKE:tagQuery ")
fun getTaskByTag(stringBuilder.toString() : String): List<Task>
The single data is fine. However, you simply cannot use the second method.
First you are omitting the space after LIKE,
Then you are omitting the full test i.e. you have task_ tags LIKE ? OR ?? when it should be task_tags LIKE ? OR task_tags LIKE ?? ....
And even then, due to the way that a parameter is handled by room the entire parameter is wrapped/encased as a single string, so the OR/OR LIKE's all become part of what is being searched for as a single test.
The correct solution, as least from a database perspective, would be to not have a single column with a JSON representation of the list of the tags, but to have a table for the tags and then, as you want a many-many relationship (a task can have many tags and a single tag could be used by many tasks) an associative table and you could then do the test using a IN clause.
As a get around though, you could utilise a RawQuery where the SQL statement is built accordingly.
As an example:-
#RawQuery
fun rawQuery(qry: SimpleSQLiteQuery): Cursor
#SuppressLint("Range")
fun getTaskByManyTags(tags: List<String>): List<Task> {
val rv = ArrayList<Task>()
val sb=StringBuilder()
var afterFirst = false
for (tag in tags) {
if (afterFirst) {
sb.append(" OR task_tags ")
}
sb.append(" LIKE '%").append(tag).append("%'")
afterFirst = true
}
if (sb.isNotEmpty()) {
val csr: Cursor = rawQuery(SimpleSQLiteQuery("SELECT * FROM task WHERE task_tags $sb"))
while (csr.moveToNext()) {
rv.add(
Task(
csr.getLong(csr.getColumnIndex("tid")),
csr.getString(csr.getColumnIndex("task_title")),
csr.getString(csr.getColumnIndex("task_tags"))))
// other columns ....
}
csr.close()
}
return rv
}
Note that the complex string with the embedded double quotes is, in this example, passed rather than built into the function (relatively simple change to incorporate) e.g. could be called using
val tasks1 = taskDao.getTaskByManyTags(listOf()) would return no tasks (handling no passed tags something you would need to decide upon)
val tasks2 = taskDao.getTaskByManyTags(listOf("\"title\":\"Priority\""))
val tasks3 = taskDao.getTaskByManyTags(listOf("\"title\":\"Priority\"","\"title\":\"Priority\"","\"title\":\"Priority\"")) obviously the tags would change
Very limited testing has been undertaken (hence just the 3 columns) but the result of running all 3 (as per the above 3 invocations) against a very limited database (basically the same row) results in the expected (as per breakpoint):-
the first returns the empty list as there are no search arguments.
the second and third both return all 4 rows as "title":"Priority" is in all 4 rows
the main reason for the 3 search args was to check the syntax of multiple args, rather than whether or not the correct selections were made.
The resultant query of the last (3 passed tags) being (as extracted from the getTaskaByManyTags function):-
SELECT * FROM task WHERE task_tags LIKE '%"title":"Priority"%' OR task_tags LIKE '%"title":"Priority"%' OR task_tags LIKE '%"title":"Priority"%'

Android SQLite dynamically insert sorting (ASD or DESC), is it possible?

I'm trying to use one method for dao (Room) to get items by a specific sorting dynamically, but compiler gives an error for the next SQL query
So it's not possible? Do I have to create duplicate methods with different sorting?
you can not perform if and else logic in the sqlite query
You should use #RawQuery like this:
#Dao
interface RawDao {
#RawQuery
fun getTestItems(SupportSQLiteQuery query): DataSource.Factory
}
// Usage of RawDao
// for example set: restOfQuery = sortBy + "ASC"
val query = SimpleSQLiteQuery(
"SELECT * FROM Items ORDER BY ?",
new Object[]{ restOfQuery });
val result = rawDao.getTestItems(query);
Or another way is that you use multiple functions for multiple orderings.

Android Room DB - use static variable from another class in query

When I write a query in DAO class can I use a static variable from another class (or enum)? If it's possible, please tell me HOW?
I mean something like this:
public enum MessageState {
NOTHING,
PENDING,
SEND
}
and in query statement in DAO class:
#Query("SELECT * FROM message_db WHERE state = :MessageState.PENDING.ordinal()")
Pass your enum to the function as a default parameter:
#Query("SELECT * FROM message_db WHERE state = :state")
fun getMessages(state: Int = MessageState.PENDING.ordinal()): List<Message>
This way you won't need to provide it on every call, but achieve the same result.
You can concatenate it just like a normal String, in Java:
#Query("SELECT * FROM message_db WHERE state = " + MessageState.PENDING.ordinal() + ")"
If you are using Kotlin:
#Query("SELECT * FROM message_db WHERE state = ${MessageState.PENDING.ordinal()}")

How to use parameter fields in Room #Query?

I have a User class with a field id, so I wanted to run the following query with Room:
#Query("SELECT * FROM ticket where user_id = :user.id")
LiveData<Ticket> loadFromUser(User user);
But I am getting error marks on Android Studio on user.id and all examples I find online only use the direct parameter of the #Query method, usually a String or an int.
Is it possible to use an object's field in a Room #Query? If positive, so what's the proper way of referencing it.
You can't pass parameters like that to room. It does not support a full expression language. You have to use primitive types to pass parameters. Like this,
#Query("SELECT * FROM ticket where user_id = :user_id")
LiveData<Ticket> loadFromUser(String user_id);
A simple solution is to create two other functions, one is for user_id and one is for user as follows:
#Query("SELECT * FROM ticket where user_id = :user_id")
LiveData<Ticket> loadFromUser(String user_id);
#Transaction
LiveData<Ticket> loadFromUser(User user){
return loadFromUser(user.id);
}
in my case i used #RawQuery
in DAO you can write
#RawQuery
LiveData<Ticket> loadFromUser(SupportSQLiteQuery query);
and create your query and pass to it.
SimpleSQLiteQuery query = new SimpleSQLiteQuery("SELECT * FROM ticket where user_id = ?") , new Object[]{user.id})
and pass this query to DAO method.
userDao.loadFromUser(query)

ORMLite alias in rawQuery

Is it possible to use an alias (AS) in a query for ORMLite in Android? I am trying to use it with the following code:
String query =
"SELECT *, (duration - elapsed) AS remaining FROM KitchenTimer ORDER BY remaining";
GenericRawResults<KitchenTimer> rawResults =
getHelper().getKitchenTimerDao().queryRaw(
query, getHelper().getKitchenTimerDao().getRawRowMapper());
But when this codes gets executed it gives the following error:
java.lang.IllegalArgumentException: Unknown column name 'remaining' in table kitchentimer
java.lang.IllegalArgumentException: Unknown column name 'remaining' in table kitchentimer
The raw-row-mapper associated with your KitchenTimerDao expects the results to correspond directly with the KitchenTimer entity columns. However, since you are adding your remaining column, it doesn't no where to put that result column, hence the exception. This is a raw-query so you will need to come up with your own results mapper -- you can't use the DAO's. See the docs on raw queries.
For instance, if you want to map the results into your own object Foo then you could do something like:
String query =
"SELECT *, (duration - elapsed) AS remaining FROM KitchenTimer ORDER BY remaining";
GenericRawResults<Foo> rawResults =
orderDao.queryRaw(query, new RawRowMapper<Foo>() {
public Foo mapRow(String[] columnNames, String[] resultColumns) {
// assuming 0th field is the * and 1st field is remaining
return new Foo(resultColumns[0], Integer.parseInt(resultColumns[1]));
}
});
// page through the results
for (Foo foo : rawResults) {
System.out.println("Name " + foo.name + " has " + foo.remaining + " remaining seconds");
}
rawResults.close();
I had the same problem. I wanted to get a list of objects but adding a new attribute with an alias.
To continue using the object mapper from OrmLite I used a RawRowMapper to receive columns and results. But instead of convert all columns manually I read the alias first and remove its reference in the column arrays. Then it is possible to use the OrmLite Dao mapper.
I write it in Kotlin code:
val rawResults = dao.queryRaw<Foo>(sql, RawRowMapper { columnNames, resultColumns ->
// convert array to list
val listNames = columnNames.toMutableList()
val listResults = resultColumns.toMutableList()
// get the index of the column not included in dao
val index = listNames.indexOf(ALIAS)
if (index == -1) {
// There is an error in the request because Alias was not received
return#RawRowMapper Foo()
}
// save the result
val aliasValue = listResults[index]
// remove the name and column
listNames.removeAt(index)
listResults.removeAt(index)
// map row
val foo = dao.rawRowMapper.mapRow(
listNames.toTypedArray(),
listResults.toTypedArray()
) as Foo
// add alias value. In my case I save it in the same object
// but another way is to create outside of mapping a list and
// add this value in the list if you don't want value and object together
foo.aliasValue = aliasValue
// return the generated object
return#RawRowMapper foo
})
It is not the shortest solution but for me it is very important to keep using the same mappers. It avoid errors when an attribute is added to a table and you don't remember to update the mapping.

Categories

Resources