Hardcode Boolean Query In Room Database - android

I'm building an Android application that displays a list of potential matches for a user. The user can click on one to like the user, and I save all of those likes locally.
I can write a query to get the list of matches like this:
#Query("SELECT * FROM match WHERE liked = :liked ORDER BY match DESC LIMIT :limit")
fun getMatches(limit: Int = 6, liked: Boolean = true): Flowable<List<Match>>
I've learned that this works fine. However, I don't foresee any scenario where I'll ever set liked to false, and so I'm curious if there is a way to hardcode my Boolean condition? If I try:
#Query("SELECT * FROM match WHERE liked = true ORDER BY match DESC LIMIT :limit")
I get the following error at compile time:
Error:(8, 0) Gradle: error: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such column: true)
How can I hard code this Boolean in my query string?
I have also tried:
Wrapping the condition in single quotes
#Query("SELECT * FROM match WHERE liked = 'true' ORDER BY match DESC LIMIT :limit")

SQLite does not have a boolean data type. Room maps it to an INTEGER column, mapping true to 1 and false to 0.
So, I would expect this to work:
#Query("SELECT * FROM match WHERE liked = 1 ORDER BY match DESC LIMIT :limit")
Bear in mind that this behavior is undocumented. However, it shouldn't change — at least not without alarm klaxons sounding — as we'd need to use migrations to deal with any changes.

CommonWare's approach does work and also answers the OPs question directly; however, I'm not a fan of making such an assumption about the database. The assumption should be safe, but it may create unexpected work down the road if Room ever decides to change it's boolean implementation.
I'd suggest that the better approach is to not hardcode the boolean 1 or 0 into the query. If the database is behind a repository, it is still possible for the repository to expose a graceful API. Personally, I think shielding the larger codebase from the database implementation is a good thing anyways.
Dao Method (copied from OP's question)
#Query("SELECT * FROM match WHERE liked = :liked ORDER BY match DESC LIMIT :limit")
fun getMatches(limit: Int = 6, liked: Boolean = true): Flowable<List<Match>>
Repository
class Repository {
public Flowable<List<Match>> getLikedMatches() {
return dao.getMatches(6, true);
}
}
Of course, this is an opinionated option in that it assumes a certain architectural style. However, it does not make assumptions about the internal database. Even without the repository shielding the database, the call can be made into the database by passing true everywhere - also without making assumptions as to the underlying data.

You don't have to compare boolean column to a value. Just use the column value itself as a boolean expression. You can easily change your query to SELECT * FROM match WHERE liked ORDER BY match DESC LIMIT :limit.
If you want to compare to false value you can use following expression: where not liked.

#Query("SELECT * FROM searched_data_table WHERE favourit_history==1 ORDER BY lang_id DESC")
Use this query to search data from the table this will give you data in descending order with respect to your key value

Related

Changing primaryKeys order changes the order of the data saved in ROOM

I have a situation where I need some information about my results, in my code I have this data class/Room's entity.
#Entity(primaryKeys = ["searchId","page","pr_id"])
data class ProductResponse(
var searchId: Int,
val page: Int,
#Embedded(prefix = "pr_")
val products: ProductSearchFormatted
)
Where you can see in the Android Studios App Inspector:
The data is saved as spected in ROOM, when I try to load it from ROOM:
#Query(
"SELECT * FROM PagedSearchResponse WHERE searchId ==:input"
)
fun loadPagedSearchResponse(input: Int): PagingSource<Int, ProductResponse>
I just need the data in the same order that was previously saved, and got the data in different order (ordered by pr_id):
I found out that if I change the primaryKeys order, like
#Entity(primaryKeys = ["pr_id","searchId","page"])
data class ProductResponse(
var searchId: Int,
val page: Int,
#Embedded(prefix = "pr_")
val products: ProductSearchFormatted
)
Now the data's order from ROOM is correct.
Why does this happen? Does the primaryKeys order matter?
Changing primaryKeys order changes the order of the data saved in ROOM
NO it does not, the data is ALWAYS saved in the order in which it is inserted. What is changing in your case, is the ORDER in which the data is extracted. That is because you aren't saying in what ORDER you want the data to be extracted and are leaving that choice to the query planner.
Why is this happened?, does the primaryKeys order matter?.
Yes it can do, especially in the absence of other indexes. Certainly pr_id before search_id will make a difference as the order within the index will be different and that as the WHERE clause is on the search_id then it is likely that the primary key index will be used (as in both cases search_id is an initial column (see the links below))
The query planner is an AI that tries to pick the fastest and most efficient algorithm for each SQL statement.
see :-
https://www.sqlite.org/queryplanner.html
https://www.sqlite.org/optoverview.html
https://www.sqlite.org/queryplanner-ng.html
If you want data to be in ORDER then you should specify an ORDER clause (ORDER BY ....). That is the only way to guarantee an ORDER. Assuming an ORDER without an ORDER clause will very likely result in issues.
Saying that using pr_id prior to search_id makes the composite (multiplte column) index likely to be more beneficial as the pr_id (according to the data shown) is less repeated than the search_id.
The way I fix it, it was simpler than I expected.
In the entity, I added a field called "order", and in my Mediator (I'm using Paging 3) basically did this:
list.mapIndex { s,t ->
ProductResponse(
...
order = s
...
)
}
That way, I'm following the EXACT order from Backend without the need to modify any primaryKeys.

SQLite UPDATE WHERE perfomance

Let's say that I do have following DB table named LogTable
id name show
O A false
1 B true
2 C true
This table can easily hold up to 100k entries. Now I want to update all of the entries to hold show column value as true. My first attempt was:
#Query("UPDATE LogTable SET `show` = $FALSE WHERE `show` = $TRUE")
This worked, until my colleague did point out that we can theoretically omit the WHERE clause to improve performance eg.
#Query("UPDATE LogTable SET `show` = $FALSE)
And to my big surprise it works better no matter what kind of data I mock. At least I though that when we will have more show = FALSE the query with WHERE clause would be better in terms of performance.
What am I missing ? Does WHERE add in this particular case unnecessary overhead as it needs to find the entries first ?

Order Room results by Date as String

I'm working on an app where Dates are entered in the Room database as Strings, with a SimpleDateFormat fixed to '%d/%m/%Y %H:%M:%S' (e.g. "26/12/2018 10:33:31").
Without changing the structure of the database, I'd like to query some results, ordered by date. This should probably look like this (inside a Dao):
#Query("SELECT * FROM results ORDER BY stringToDate(startTime) DESC LIMIT :count")
List<Results> getLast(int count);
Unfortunately, this throws a compile error: "no such function: stringToDate", no matter if the function exists, even if it is annotate as TypeConverter in due form.
Moreover, since String is accepted as a Room type, I'm not event sure I could use TypeConverter here to begin with.
Is there any way I could order by Date as String without modifying the database structure ?
Try:
#Query("SELECT * FROM results ORDER BY strftime('%Y-%d-%m-%Y', startTime) DESC LIMIT :count")
Make sure that the format passed to strftime match your time format.
For reference formats can be found here:
https://www.tutorialspoint.com/sqlite/sqlite_date_time.htm
Can you try changing stringToDate(startTime) to datetime(startTime)?
Can you provide a sample of startTime object?

Room: how to check whether row exists

In my repository class, I want to fetch data only if none exists in a Room table. How to check whether any row exists in a table?
Use EXISTS operator, and return 1 means true and 0 means false.
If you want to check some specific row and some condition, do this trick:
#Query("SELECT EXISTS(SELECT * FROM tableName WHERE id = :id)")
fun isRowIsExist(id : Int) : Boolean
Or simple use this:
#Query("SELECT EXISTS(SELECT * FROM tableName)")
fun isExists(): Boolean
You can use EXISTS operator and return just Boolean:
#Query("SELECT EXISTS(SELECT * FROM table)")
fun hasItem(): Boolean
As ADM suggested, you might get lucky using COUNT() to count the actual rows in a table.
However, I would recommend just fetching the data anyway - if none exists Room will simply return an empty list, and this should not be less efficient than asking for the row count (if it's 0 anyway).
As a plus you will have less code to write to get the functionality you want! :-)

Room - Select query with IN condition?

Is it possible to use SQLite's IN condition with Room?
I'm trying to select a list of items from my database where the value of a certain column (in this case a TEXT column) matches any one of a set of filter values. That's pretty easily done in SQL and SQLite, by my knowledge, just by adding an IN condition to your SELECT statement (see here). However, I can't seem to make it work with Room.
I keep getting this error:
Error:(70, 25) error: no viable alternative at input 'SELECT * FROM Table WHERE column IN :filterValues'
(where the input to the DAO #Query-annotated method is called filterValues)
I have tried three different methods now:
Passing the argument as a List<String>
Passing the argument as a String[]
And lastly passing the argument as simply a String, but formatted as (value_1, value_2, ..., value_n)
The last one in particular should work easily, as it will (or at least, it should) directly translate to SELECT * FROM Table WHERE column IN (value_1, value_2, ..., value_n), which is the exact way you would manually write out the SELECT if you were just accessing the database directly.
So as I was preparing to submit this, I double-checked a bunch of the stuff I had looked up previously and found the thing I had somehow missed and would have saved this question from being necessary.
As it turns out, both of these options:
Passing the argument as a List<String>
Passing the argument as a String[]
are viable (and you can replace String with any type the database can represent, such as char or int), you simply need to change the syntax in the #Query annotation from this:
#Query("SELECT * FROM Table WHERE column IN :filterValues")
to this:
#Query("SELECT * FROM Table WHERE column IN (:filterValues)")
Easy as pie, right?
Note that the third method above (passing the argument as simply a String, but formatted as (value_1, value_2, ..., value_n)) does not appear to be supported by Room, but that's probably not a bad thing, since that's the hard way.
Since I already had the whole thing typed out, I figured I would leave the question up in case other people are have as much difficulty finding this solution as I did and stumble upon this question.
Hi you can use this query:
#Query("SELECT * FROM user WHERE uid IN(:userIds)")
public abstract List findByIds(int[] userIds);
or
#Query("SELECT * FROM user WHERE uid IN(:userIds)")
public abstract List findByIds(List<Integer> userIds);
Similarly to above answers in Kotlin you can use vararg instead of array or list:
#Query("SELECT * FROM user WHERE uid IN (:userIds)")
fun getUsers(vararg userIds: Int): List<User>?
and use it like repository.getUsers(1, 2, 3).
If needed, to convert vararg to list see https://proandroiddev.com/kotlins-vararg-and-spread-operator-4200c07d65e1 (use *: format(output, *params)).

Categories

Resources