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)).
Related
I have created a room database, and showed my data in a RecyclerView. I know how to add, delete, deleteAll, update data in room data base, but the problem is I don't know how to perform search in room data base?
I have added a search view in the tool bar. But I don't know how to add Queries in NoteDao and the remaining classes, e.g. in my adpter, MainActivity etc.
If you're able to show your Room database in the RecyclerView, this would mean you should know how to use the #Query() tag for fetching your data.
Searching is done using the same #Query tag using the WHERE keyword.
For example, if you wish to find a specific note:
#Query("SELECT * FROM notes WHERE id == :id")
public Note getNote(String id)
However, that's just the simplest way to use WHERE. Frequently, you'll be combining it with other keywords such as IN, LIKE, or BETWEEN.
So for your situation of using a search, you would most likely want to use the LIKE keyword, which can query your database for a field that contains parts of its data that matches the search criteria:
For example:
#Query("SELECT * FROM notes WHERE note_title LIKE :search OR note_message LIKE :search")
public List<Note> getSearchedNote(String search)
This would search your database and return a list of Notes that has either a Note Title or a Note Message that contains the search term you want.
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
I am having 2 cursors from different tables in an SQLite database. I am trying to put the data from the two cursors into one ListView but for different formatting for data from each cursor.
What I thought about is using a MergeCursor to combine both cursors, but the ViewBinder for my SimpleCursorAdapter will see them as a single cursor and will not be able to differentiate for formatting (unless I alter my tables, which I do not want to do).
Finally, I found 2 methods called Cursor.repond(Bundle) & Cursor.getExtras(), but the documentation on the developer console is very short and Googling these methods did not clarify their use.
I tested my idea to use resond() in my Database class for the query:
extr.putString("table", the_tab);
Cursor c_in = db.rawQuery(qry, null);
c_in.respond(extr);
return c_in;
And use getExtras() in the ViewBinder to know the table of the query and format the ListView item accordingly:
Bundle extr=cur.getExtras();
String tab= extr.getString("table");
But I am always getting an exception that tab is null.
My question after this long description is: Am I using the respond and getExtras methods correctly? And if not, is there a better approach for my problem?
If you want to use the Bundle in getExtras it seems that AbstractCursor which is extended by AbstractWindowedCursor which is extended by SQLiteCursor defines a setExtras method. It's documentation reads
Sets a android.os.Bundle that will be returned by getExtras(). null will be converted into android.os.Bundle.EMPTY.
respond is used for out-of-band communication with the Cursor according to the documentation.
So the answer is:
((AbstractCursor) cursor).setExtras(bundle);
Then you should be able to call
cursor.getExtras();
at a later time to retrieve that Bundle.
EDIT:
Upon looking further, it appears that setExtras is marked hidden for some reason, so it is public, and is intended to be used as described (and desired). See ContactsProvider2 example
So, I came up with using a CursorWrapper, and overriding the getExtras there to provide the Bundle. In my case, I create the Bundle in the CursorWrapper, so I don't need a method for setting it.
#Override
public Bundle getExtras() {
return _meta_data;
}
Try building the table name in as a field in the two SELECTs.
SELECT "A", * from tableA; SELECT "B", * from tableB;
then compute a merged cursor. Alternately,
SELECT "A", * from tableA UNION ALL SELECT "B", * from tableB;
Now each row of the cursor will have a "A" in the first column if it came from tableA and a "B" if it came from tableB. So it's easy to look at this column in your ViewBinder to make formatting decisions.
I would insist you to have a look at MergeCursor and AbstractCursor to get around your problem and get the solution. For example you can check this answer.
Here's an alternative (though you should not use it):
Class.forName("android.database.AbstractCursor")
.getMethod("setExtras", new Class[]{Bundle.class})
.invoke(c_in, extr);
I've got a fairly complicated query (multiple joins) on a normalized sqlite database. The query does a SELECT * to enable some automated attribute selection logic (so I can't eliminate the "*")
The problem I am having is that my result set contains multiple columns with the same attribute name. For example, one attribute common to each table in the query is "_id". When I go to call "cursor.getColumnIndex("_id")" the value returned is always the index of the last "_id" attribute in the result set column list (i.e. not the one I want). I'd love to be able to use my SQL alias prefixes like cursor.getColumnIndex("A._id") but that is not working.
QUESTIONs
It appears that cursor.getColumnIndex(AttributeName) returns the index of the last "AttributeName". Can anyone confirm this?
Also, any suggestions on how return the index of the 1st attribute with "AttributeName"? or better the Xth attribute having "AttributeName"?
You can do this:
SELECT _id as myID, * FROM myTable
This means the _id field will appear twice for each table in your results, but one of the two columns will have a unique name which should enable you to find it.
Unfortunately the documentation doesn't mention anything about what you need to do, so I am assuming it cannot be done.
However, you say
The query does a SELECT * to enable some automated attribute selection
logic (so I can't eliminate the "*")
What is this 'automated attribute selection logic' you speak of? Why do you require this?
An oder solution is:
"SELECT tableName.columnName FROM tableName"
and then do the same with:
cursor.getColumnIndex("tableName.columnName");
This is what MS-Access does. You can create a query and then see the generated SQL code (simply going to 'view' menu and selecting 'SQL view' from your query dessign window)
Let's say an SQLite database column contains the following value:
U Walther-Schreiber-Platz
Using a query I'd like to find this record, or any similar records, if the user searches for the following strings:
walther schreiber
u walther
walther-schreiber platz
[Any other similar strings]
However I cannot figure out any query which would do that. Especially if the user does not enter the - character.
Example:
select * from myTable where value like '%walther schreiber%'; would not return the result because of the missing -.
Thanks,
Robert
So, as I said in my comment, I think you can have a query along the lines of:
select * from myTable where LOWER(value) like <SearchValue>
I'm assuming you're collecting the <SearchValue> from the user programmatically, so would be able to do the following: <SearchValue> would need to be: The user's search string, appropriately cleansed to avoid SQL injection attacks, converted to lower case, with all of the spaces converted to '%', so that they match zero or more characters...
So you would have (for example):
select * from myTable where LOWER(value) like '%walther%schreiber%'
select * from myTable where LOWER(value) like '%walther-schreiber%platz%'
etc... however, this does assume that the word order is the same, which in your examples it is...