Obtaining integer from room query math result - android

I'm trying to obtain the result of a subtraction between two rows in the database. Users specify the conditions on spinners (populated with the "model" column), press a button and the query is launched.
Spinners are properly saving the position into sharedpreferences and later obtaining it.
Button function:
public int value;
//later on
TextView converter = findViewById(R.id.converter);
AppExecutors.getInstance().diskIO().execute(() -> {
LiveData<Integer> value = mDb.personDao().loaddifferconstants(spinA, spinB);
converter.setText(""+value); //quick dirty method
});
Dao
#Query("SELECT t1.const - t2.const AS result FROM person t1 JOIN person t2 WHERE t1.model == :spinA AND t2.model == :spinB")
LiveData<Integer> loaddifferconstants(String spinA , String spinB);
The query does work in DBBrowser, as a direct sql query. So I guess the error lies on how the result is processed into an integer. I tried listing the result, using both livedata integer, int, list... trying to pass it as a String... Failed.
Update 1:
Integer doesn't work either.
Actually Integer count doesn't work either, with the Dao being
#Query("SELECT COUNT(*) FROM PERSON")
int count();
Thank you

LiveData<Integer> value = mDb.personDao().loaddifferconstants(spinA, spinB);
converter.setText(""+value); //quick dirty method
value is a LiveData. This will cause the query to be executed asynchronously. By the next statement, that query will not have completed, and the LiveData will not have the query result.
Either:
Remove LiveData from loaddifferconstants() and have it simply return Integer, so the query will be executed synchronously, or
Consume the LiveData properly, by registering an observer
Since you seem to by trying to call those two lines inside your own background thread, I recommend the first approach: get rid of the LiveData. That would give you:
#Query("SELECT t1.const - t2.const AS result FROM person t1 JOIN person t2 WHERE t1.model == :spinA AND t2.model == :spinB")
Integer loaddifferconstants(String spinA , String spinB);

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"%'

Return type of GROUP BY statement in Room Database

I would like to make the following query to my database:
SELECT type, COUNT(*) FROM offerings GROUP BY type
This query works well with an Sqlite browser. Now I want to use this query in my Dao:
#Query("SELECT type, COUNT(*) FROM offerings GROUP BY type")
LiveData<Map<String, Integer>> getOfferingsGroupedByType();
But I am getting the error: ... not sure how to convert a cursor to this method's return type
How can I query a table with 2 columns? --> that is, [type, count(type)] ?
Step #1: Give a name to the count: SELECT type, COUNT(*) AS count FROM offerings GROUP BY type
Step #2: Create a Java class with suitable fields:
public class Thingy {
public String type;
public int count;
}
Step #3: Have your return type from the DAO method use that class:
#Query("SELECT type, COUNT(*) FROM offerings GROUP BY type")
LiveData<List<Thingy>> getOfferingsGroupedByType();
I don't recall Room supporting returning a Map, so you will need to handle that aspect yourself, either in the observer or via a MediatorLiveData that wraps the LiveData you get from the DAO and does the conversion.

Android Room: How to combine data from multiple SQL queries into one ViewModel

Context: I am using Android Room and Android Architecture Components to load data in a Recyclerview. The data I am loading is a table of Message objects, which have a date, and a message content.
The Goal: Upon entering the messaging room, a timestamp of the current time is saved, and I want the Recyclerview to display the 10 most recent messages found in the Room database sent before that timestamp.
The query would look something like this, where offset would be 10, and my_date would be the timestamp:
#Query("SELECT * from message_table WHERE date<:my_date ORDER BY date DESC LIMIT :offset")
LiveData<List<Message>> getOldMessages(long my_date, int offset);
Upon entry to the messaging room, I also want to instantiate a LiveData object, being observed by my messaging page Activity, which is updated every time new messages are arriving to the repository.
The query for the live messages would look something like this:
#Query("SELECT * from message_table WHERE date>:my_date ORDER BY date DESC")
LiveData<List<Message>> getNewMessages(long my_date);
Additionally, I will later need to create a functionality that allows the user to load more old messages when he scrolls to the top of the recyclerview.
My attempts:
Combined query: the easiest way would be to write a single query which fetches both the live messages and the 10 older messages. Unfortunately, while I learned that SQL has a "UNION" operator, I couldn't figure out how to use it with the specific syntax in my java DAO.
MediatorLiveData: alternatively, I could create two LiveData's through the DAO, one for each query, and then I could join them together with MediatorLiveData. I guess that this could technically work, and I could then feed my mediated data to the recyclerview adapter, but it doesn't feel right to use LiveData for the old messages query where the data is essentially static.
Non-live data for old messages: The last option I could think of is to get the LiveData object from the new messages query and observe it, and also create a function to get a simple list (List instead of LiveData>) of the old messages, and in the onChanged function of the new messages LiveData, I could combine both lists of messages manually.
Code sources:
In my messaging room activity, I observe the LiveData in the following way:
LiveData<List<Message>> newMessagesLiveData = mMessageViewModel.getNewMessages();
newMessagesLiveData.observe(this, new Observer<List<Message>>() {
#Override
public void onChanged(#Nullable final List<Message> messages) {
adapter.setMessages(messages);
RecyclerView recyclerView = findViewById(R.id.recyclerview);
recyclerView.scrollToPosition(adapter.getItemCount()-1);
}
});
Then the adapter receives the changes and updates itself:
public void setMessages(List<Message> messages){
mMessages = messages;
notifyDataSetChanged();
}
#Override
public void onBindViewHolder(MessageViewHolder holder, int position) {
position = mMessages.size()-1-position;
if (mMessages != null) {
Message current = mMessages.get(position);
holder.messageItemView.setText(current.getMessage());
} else {
holder.messageItemView.setText("No messages to display");
}
}
I am helping you with option 1. You can union same table.
When you enter for chat save the current time in a local variable like
long currentTime = System.currentTimeMillis();
Now you need to union two queries. one query for last offset messages from currentTime and one query for messages greater than currentTime.
#Query("SELECT * FROM (" +
"SELECT * from message_table WHERE date<:my_date ORDER BY date DESC LIMIT :offset) UNION SELECT * from (SELECT * from message_table WHERE date>=:my_date ORDER BY date DESC)")
LiveData<List<Message>> getMessages(long my_date, int offset);
If you want any change let me know.
Instead of UNION, I would suggest you to user "UNION ALL" as follows
SELECT * FROM (SELECT * from table WHERE id == 2)
UNION ALL
SELECT * from (SELECT * from table WHERE id == 3)
UNION ALL
SELECT * from (SELECT * from table WHERE id == 5)

Return Map object by DAO method

I would like to have method like this in my dao object
#Query("SELECT c.name, sum(p.value) FROM payments p, paymentCategories c WHERE p.categoryId = c.id GROUP BY c.name")
fun getCategoryStats(): Map<String, Float>
but i get the error
error: Not sure how to convert a Cursor to this method's return type
public abstract java.util.Map
Is it possible to change it to working version?
So it can be different type to return but the main conditions are
It must be only one query in db
I would like to avoid extra code like creating additional data structure only for this method
I'm little late for a party but maybe someone will still search for it.
From Room 2.4 we are able to use multimap return type with annotation #MapInfo which allow us to define the mapping for key and value.
In your case it will be sth like this:
#MapInfo(keyColumn = "name", valueColumn = "sum")
#Query("SELECT c.name AS name, sum(p.value) AS sum FROM payments p, paymentCategories c WHERE p.categoryId = c.id GROUP BY c.name")
fun getCategoryStats(): Map<String, Float>
More info:
https://developer.android.com/training/data-storage/room/accessing-data#multimap
While I don't think this can be done in the Dao it can easily be done when querying the LiveData in the repository or the viewModel (or where ever you are querying the list) using a transformation. Since I don't know your datanames I'm using made up ones:
val categoryStatsMap: LiveData<Map<String, Float>> =
Transformations.map(
database.categoryStatsDao.getCategoryStats()) {it ->
it.map {it.key to it.value}.toMap()
}
'it.key' and 'it.value' are the fields in the entity object that you want to use as... key and value pairs in the map.
Transformations give you a live data object based on another live data object. I don't know what the overhead is, but I assume it shouldn't be too big.

How to check if record exists or not using Anko?

I am learning to fetching data from sqlite using anko. I can print the data successfully (if the record exist) but my application always crash when the data doesn't exist.
the error says:
parseSingle accepts only cursors with a single entry
I know exactly the meaning of error, I just dont know how to solve it.
here is the code for query:
fun getUserByUid(uid: Int): UserModel
{
val data = context.database.use {
val db = context.database.readableDatabase
val columns = UserModel.COLUMN_ID + "," + UserModel.COLUMN_NAME + "," + UserModel.COLUMN_API_KEY
val query = db.select(UserModel.TABLE_NAME, columns)
.whereArgs("(uid = {userId})",
"userId" to uid)
query.exec {
val rowParser = classParser<UserModel>()
parseSingle(rowParser) // this line that trigger error exception
}
}
return data
}
I tried to find count function in query or rowParser variable to check if the record exist or not but could not find it.
From the wiki page.
https://github.com/Kotlin/anko/wiki/Anko-SQLite#parsing-query-results
Parsing query results
So we have some Cursor, and how can we parse it into regular classes? Anko provides functions parseSingle, parseOpt and parseList to do it much more easily.
Method Description
parseSingle(rowParser): T Parse exactly one row
parseOpt(rowParser): T? Parse zero or one row
parseList(rowParser): List Parse zero or more rows
Note that parseSingle() and parseOpt() will throw an exception if the received Cursor contains more than one row.

Categories

Resources